31 March 2009

ByRef parameters are Pointers

Yes, that is right. A ByRef parameter is effectively a Pointer [To]. Let's see what this means. A pointer variable of some data type behaves exactly as a variable instance of that type. A variable pdbl declared As Pointer To Double behaves exactly as a Double. Not only is it used in the same way as a normal instance of a Double, but anything GFA-BASIC 32 allows to be done with a Double data type is allowed with pdbl (a pointer to double). Once a pointer has been initialized (given a memory address) the compiler interpretates the pdbl pointer variable as a variable of type Double. For the programmer the pdbl is the same as a normal Double variable. The compiler on the other hand accepts pdbl as a Double on each occasion it processes it, but it generates different code to perform operations on pdbl. The problem is that the compiler isn't aware of the exact location of pdbl's data location. Variables that are instantiated using Global, Local, Static, and of course Dim, are included in the program (exe). They have a reserved portion of memory that the compiler know about. All code generated by the compiler can operate on this memory location directly. The assembler code generated for accessing 'normal' variables is therefore quite different than assembler code generated for pointers. For pointers GFA-BASIC 32 generates C-like pointer code; it stores an address (data location) in a register and uses this value to indirectly access the data location. When the GFA-BASIC 32 compiler parses the code, it marks all un-addressed variables before generating code and when it comes to create assembler instructions it generates pointer code, assuming the actual address will be known at runtime. There are only two type of variables the memory address isn't known from at compile time: Pointer To and ByRef. Investigation showed that both types are marked the same way when they are encountered during compiling. A ByRef procedure argument is in essence a Pointer To variable. Initializing a pointer The pointer pdbl can be initialized at runtime only. The pointer pdbl initially points to address 0 (null). To be used practically, the pointer variable must be assigned a memory address. The compiler uses this memory address to operate on. Normally, the memory address of GFA-BASIC 32 variables is 'handled' through the VarPtr function (or its shortcut V:). Essentially, a pointer lacks a 'VarPtr' address. (It could have been an idea when GFA-BASIC supported a 'VarPtr(var)=addr' command to set the memory address. However, in case of variables that use a descriptor (String, arrays) there would also have to be an 'ArrPtr()= addr' to set the memory address for such a pointer.) To initialise a pointer a new keyword was introduced: Pointer(pvar)=. (Note - This still requires the programmer to be aware what memory to set with Pointer(). A Pointer To String will require a pointer to the descriptor of another string!) I discussed this command multiple times and won't go into that here. The question is how is the apllicable on ByRef arguments? A ByRef variable is a Pointer To variable and there is only one way to set the VarPtr address of a variable: the Pointer()= command. This command is implicitly invoked when a variable is passed to a procedure taking a ByRef argument. To get access to the passed argument the compiler generates code apropriate for the data type, but the memory address of the actual data isn't known at compile time. The ByRef variable is a local variable on the stack without a VarPtr address. The compiler than generates pointer-operation-code to process this ByRef variable, exactly the same code as generated for a Pointer To variable. This is in contrast with a ByVal variable. Here the compiler generates code to operate on the variable's memory directly, because its memory address is known in advance. (A local variable is located relative to the stack entry point (esp) of a procedure. The stack pointer is copied to ebx which is used by the compiler to locate the local variables. A local variable is recognizable in assembler instructions like mov eax, 112[ebx]. ). So, for the compiler the ByVal and ByRef keywords have two different meanings. It tells the compiler how to pass the argument to the subroutine and determines the type of code to generate. This is VERY important to realize when you are dealing with pointers in general. In case of low-level programming you must realize what exactly you are receiving in your subroutine. You need to ask yourself whether you need pointer code or normal code to be generated for that variable. Remember the ListView custom sort soutine I presented earlier? Windows passes the lParam member of the LV_ITEM structure to the compare function, which holds a pointer to a ListItem COM object. Do you remember to function's prototype? Here its is.
Function CompareDates(ByVal lngParam1 As ListItem,
  ByVal lngParam2 As ListItem, ByVal iCol As Long) As Long
Why does it need to be declared as ByVal, rather than as ByRef? Because it isn't all that simple (understatement). Some data types force GFA-BASIC 32 to generate pointer code by default, these include user-defined-types (Type) and Ocx/COM objects. GFA-BASIC 32 generates pointer-to code for the ListItem COM object by default. When the parameter would be declared ByRef, the value passed by Windows would force GFA-BASIC to generate code that uses lngParam as a pointer to a Listitem pointer. Hence Excpetion Errors! Ok enough for now, but its not end!

No comments:

Post a Comment