Results 1 to 2 of 2

Thread: Introduction to Method Pointers

  1. #1
    Join Date
    Feb 2009
    Location
    Nebraska
    Posts
    68
    Mentioned
    0 Post(s)
    Quoted
    0 Post(s)

    Default Introduction to Method Pointers

    In scripts that use forms you might have noticed a peculiar syntax using the '@' operator that you don't see anywhere else. It might have looked something like this:

    SCAR Code:
    with aButton do
    begin
      Caption := 'Click Me';
      OnClick := @AButtonOnClick;
    end;

    You may have recognized that this was in some way connecting the procedure 'AButtonOnClick' to the click action of the button, but you might not have know what was going on behind the scenes or that you can use the same technique in your own code.

    The TButton class has a number of properties that it makes available for the programmer. Some of these manage values with simple types like Integer or string, while others, which are called 'events' when they appear on a component like a button, appear a bit more complicated and can be assigned a procedure name. What is really happening is actually quite straightforward, once you understand what is really happening.

    As you may already know, in Pascal code you can declare your own variable types using types that the compiler already knows about (how compilers create completely new types and then learn to use them in order to compile themselves is a fascinating subject, but well beyond the scope of this article). This is how the TPointArray type commonly used in SRL might be created:

    SCAR Code:
    type
      TPointArray = array of TPoint;

    The compiler also knows how to create types that refer not to simple data types like integers, but to procedures and functions.

    In structured programming a variable used to refer to a procedure or function may be called a 'procedural variable', and the type of such a variable a 'procedural type'. In object-oriented programming procedures and functions that are part of an object are called 'methods' and so types and variables that refer to them can be called 'method types' or 'method variables'. The value held by a method variable is usually called a method pointer. (Why exactly you need method pointers in an object-oriented language is an interesting topic having to do with polymorphism, among other things, but it is also beyond the scope of this article.)

    In the context of SCAR scripting since we don't actually have classes defined in script one could debate whether or not the procedures and functions are actually methods (there are valid points for and against), but either way, 'methods' is a lot less cumbersome to type and say than 'procedures and functions' so that's the convention I'm going to use.

    A method variable is simply a variable that has a type derived from some kind of method type. It may be declared using an implicit syntax:

    SCAR Code:
    var
      aMethodVar : procedure( Sender: TObject );

    Or it might be done using syntax with an explicit method type:

    SCAR Code:
    var
      aMethodVar: TNotifyEvent;

    The result is the same either way; the variable can be assigned a value that is a method pointer.

    Any time you have a procedure in your script that procedure has an address. Normally you don't directly use the address yourself; you just use the name directly. Think of it like a building. There is a building called "The White House" that sits at a particular place. Its address is "1600 Pennsylvania Ave", but you don't need to use that most of the time, because everybody knows what you're talking about if you just call it by its name.

    However, if you want to add a level of indirection to your code in order to do something fancy you might need to use the address of a procedure. “Indirection” here just means that you aren't calling a procedure directly by name. This might sound exotic, but it's just describing what is commonly done with controls on forms. The TButton object knows it will need to call some method when it is clicked, but it doesn't know what the name of that method will be. By making the call indirectly through a method variable we give other programmers the freedom to call their method whatever they want when they use our code. As long as they assign the pointer to their method to our method variable, it doesn't matter what it is called.

    When you are using method variables it is useful to have a method type specifically for the kind of method you intend to use. This is part of the type-safety of the Pascal language. If you have a method variable of type TNotifyEvent and you accidentally try to assign an incompatible value of type TKeyPressEvent the compiler will generate an error to protect you from the unexpected results that would be likely to occur if it were to go ahead and call the wrong kind of method.

    To declare your own method types you'll use syntax that is very much like declaring a procedure, but you will leave out the procedure name:

    SCAR Code:
    type
      TBuildingMethod = procedure( anIdea: string );

    Now when you declare method variables that you intend to hold references to a procedure with a signature that matches the TBuildingMethod signature (that is to say, a procedure with a matching parameter list, in this example, a single string parameter), you can use your new type name:

    SCAR Code:
    var
      aBuilding : TBuildingMethod;

    To use such a variable you'll first need a method that is type-compatible (again, that just means it has a matching parameter list, so the compiler won't complain when you try to use the two together). We'll declare some methods for locations where we know we can find the President:

    SCAR Code:
    procedure TheWhiteHouse( anIdea: string );
    begin
      WriteLn( 'To The White House with: ' + anIdea );
    end;

    Here's another one:

    SCAR Code:
    procedure Starbucks( anIdea: string );
    begin
      WriteLn( 'To Starbucks with: ' + anIdea );
    end;

    Let's say you wanted to send the President a post card with your idea on how to save the economy. You could just write "The White House" on the address label, and it would get there. But what if the President isn't there right then? Maybe he's over at Starbucks getting a cuppa. You'd need a way to write something other than "The White House". Obviously you could just write "Starbucks", if you knew that that is where he was going to be. But if you don't know beforehand where he'll be, you'd want to set things up so that you could easily do either one.

    The address label on your postcard represents your variable, 'aBuilding'. The address that you put on the label (either 'The White House' or 'Starbucks') is the address of the building that you intend. You can swap out the contents of the address label depending on which building is your target at any given moment. If Mr. President is out getting that Skinny Triple Venti White Mocha you write "Starbucks" on the address label; if he's hard at work in the Oval Office "The White House" goes on the label. Either way you use the same address label, so you don't have to have a whole stack of labels for places you think he might be, and you won't ever find yourself unable to send a message because you didn't expect to find him giving a speech over at Harvard.

    Next we need to assign the method pointer to the variable and call it. So how do we tell the compiler that we're trying to get the address of the method instead of calling a function that returns a value? Our example uses a procedure which does not return a value, so one could infer that we want the address if we tried to assign the procedure to a variable, but you can also use method types that are functions. Since these are expected to return values there may be some ambiguity there, particularly if the function's return value is a method pointer!

    This is where the '@' operator comes in. It tells the compiler that we want the address of the named method:

    SCAR Code:
    aBuilding := @TheWhiteHouse;

    In this case we've noticed that Mr. President is in his office, so we've set our building to TheWhiteHouse. Now we can send our idea to the appropriate location by calling the method using the aBuilding variable:

    SCAR Code:
    aBuilding( 'Big Bad Bank' );

    If we have another idea later but we notice that the POTUS is now chatting up the barista and sipping a frothy 'chino, we can update the method variable and call aBuilding again and the idea will still end up in the right place:

    SCAR Code:
    aBuilding := @Starbucks;
      aBuilding( 'Dump Mark-to-Market!' );

    One minor detail you might need to know about is that when you need to call a method with no parameters via a method variable, you'll need to put empty parentheses at the end so that the script engine knows that you're trying to call the method. While Pascal doesn't require it, as a matter of style it isn't a bad idea to always include the parentheses on method calls, even if no parameters are contained in them. This lets anyone who is reading the code quickly see that the identifier is a procedure or function and not a variable or property.

    SCAR Code:
    var
      NoParams : procedure;

    procedure IGotNothin();
    begin
    end;

    begin
      NoParams := @IGotNothin;

      // This works
      NoParams();

      // This doesn't
      NoParams;
    end.

    Let's put this all together. What we've got so far is a method type, a variable of that type, a couple of methods that are type-compatible and some code to call them. We'll wrap it up with a program header and main function and put in some comments so we know what's going on.

    SCAR Code:
    program MethodPointers;
    // SCAR V3.20rc

    type
      // Declare procedure types for the procedures that need to be called
      // Note that the 'procedure' keyword and parameter list are here,
      // but no procedure name.
      TBuildingMethod = procedure( anIdea: string );

    // Here's a couple of procedures for standard locations
    // with signatures that match the TBuildingMethod type
    procedure TheWhiteHouse( anIdea: string );
    begin
      WriteLn( 'To The White House with: ' + anIdea);
    end;

    procedure Starbucks( anIdea: string );
    begin
      WriteLn( 'To Starbucks with: ' + anIdea );
    end;

    var
      // This variable can hold method pointers that are compatible with TBuildingMethod
     aBuilding : TBuildingMethod;
     
    begin
      // The President is at the White House, so set up the method variable
      // to point to our TheWhiteHouse method. The @ operator lets the
      // compiler know it should return the method pointer.
      aBuilding := @TheWhiteHouse;

      // Call the method using the method variable
      aBuilding( 'Big Bad Bank' );

      // We've got another idea, but the president has moved, so
      // update our pointer and do the call again
      aBuilding := @Starbucks;
      aBuilding( 'Dump Mark-to-Market!' );
    end.

    The output of this script is:
    Code:
    To The White House with: Big Bad Bank
    To Starbucks with: Dump Mark-to-Market!
    This example is obviously pretty simple. You most likely wouldn't do this in a single script file because you'll already know what the various method names are. This technique becomes useful when you are writing code that will be in a reusable module where you won't necessarily know what methods it will need to call.

    To demonstrate this we can split our script into a reusable module that provides functions for sending the President ideas and a main script that uses it. To the library we'll add a procedure that handles wrapping the user's idea in an appropriately presidential greeting before forwarding it to the specified location. This will show how the library function can interact with the new routines we'll declare in our main script, routines with names that it won't know, and won't need to know.

    SCAR Code:
    // begin POTUSideas.scar include
    {$define POTUSideas}

    type
      // Declare procedure types for the procedures that need to be called
      // Note that the 'procedure' keyword and parameter list are here,
      // but no procedure name.
      TBuildingMethod = procedure( anIdea: string );

    // Here's a couple of procedures for standard locations
    // with signatures that match the TBuildingMethod type
    procedure TheWhiteHouse( anIdea: string );
    begin
      WriteLn( 'To The White House with: ' + anIdea);
    end;

    procedure Starbucks( anIdea: string );
    begin
      WriteLn( 'To Starbucks with: ' + anIdea );
    end;

    // It would be sadly inappropriate to allow just anyone to send messages
    // to the President without some sort of introductory statement. This
    // wrapper function ensures that one is added.
    procedure SendTheManAnIdea( Where: TBuildingMethod; anIdea: string );
    var
      anIdeaWrapper : string;
    begin
      anIdeaWrapper := 'Dear Sir, someone has submitted the following: ' + anIdea;
      Where( anIdeaWrapper );
    end;
    // End POTUSideas.scar include

    Now we update our main script with another building that is specific to our needs. Notice that here I'm not declaring a local variable for the method pointer, instead I can just pass it directly to the wrapper procedure which has a parameter of the appropriate type. This is essentially the same thing; within the SendTheManAnIdea function the 'Where' parameter is our method variable, we just don't have an assignment using the ':=” operator.

    SCAR Code:
    program MethodPointers;
    // SCAR V3.20rc

    {.include POTUSideas.scar}

    // Harvard, stronghold of Conservative thinking, didn't expect
    // to find our Liberal man there, but that's ok, just create a new
    // method to handle the situation
    procedure Harvard( anIdea: string );
    begin
      WriteLn( 'To Harvard with: ' + anIdea );
    end;

    begin
      // The President is at the White House, so send the idea there.
      SendTheManAnIdea(@TheWhiteHouse, 'Big Bad Bank' );

      // We've got another idea, but the president has moved
      SendTheManAnIdea(@Starbucks, 'Dump Mark-to-Market!' );

      // Dude really gets around! That's ok, we've adapted to his new
      // non-standard location at Harvard by adding our own routine
      // which we can now call.
      SendTheManAnIdea(@Harvard, 'Throw money from helicopters!' );
    end.

    The output of this script is:
    Code:
    To The White House with: Dear Sir, someone has submitted the following: Big Bad Bank
    To Starbucks with: Dear Sir, someone has submitted the following: Dump Mark-to-Market!
    To Harvard with: Dear Sir, someone has submitted the following: Throw money from helicopters!
    Now we can include our POTUSideas.scar file into many different scripts and the routines inside it can call back out to our new methods, such as Harvard(), without needing updates to know that they exist.

    We could add new stuff to POTUSideas.scar every time, but if we only need Harvard() once it would be silly to have it included in every script that includes POTUSideas. We could copy the contents of POTUSideas into our script, but then if there are updates to POTUSideas we'd have to remember to merge them into our script.

    When used properly method variables can increase our flexibility when writing reusable modules and make possible a wide range of features. You should take care that you don't use them unnecessarily though, used improperly they can make your code more prone to difficult-to-debug errors and they can make it harder to understand the code.
    Grippy has approximately 30,000 hours of Delphi coding experience. srsly.

  2. #2
    Join Date
    Feb 2006
    Location
    Helsinki, Finland
    Posts
    1,395
    Mentioned
    30 Post(s)
    Quoted
    107 Post(s)

    Default

    Sweeeeeeeet job, Grippy! You explained it all really well.

    Please consider writing even more tutorials..

    Thanks for using your amazing coding experience!

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Similar Threads

  1. c++(pointers) help
    By hackncrack1 in forum C/C++ Help and Tutorials
    Replies: 10
    Last Post: 05-25-2009, 01:38 PM
  2. Method Pointers
    By Grippy in forum OSR Help
    Replies: 9
    Last Post: 03-11-2009, 07:12 AM
  3. Pointers?
    By mat_de_b in forum C/C++ Help and Tutorials
    Replies: 8
    Last Post: 11-05-2007, 09:40 PM

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •