- Great, so what are function pointers? I know a lot people are tired of terminology, only because the definition is probably a lot easier to understand/grasp. It helps to understand how things are stored in memory and addressed. For example, a function (ie. SUB or FUNCTION) occupies space in some address space somewhere. A pointer to that function is just the address of it. So if I know the address of the function, I can jump into that "process space" and execute the code within. This is the low level way of thinking, first push the parameters, and then jump to (ie. CALL) the function. So if pointers are only numbers, how do we find the address of the function? This is the question that will be considered next.
- 11.2 Defining function pointers
-
If you have prior experience with function pointers (ie. in C) there are a few differences you'll have to get accustomed to. In Rapid-Q, since function pointers are just numbers, you can use any valid numeric datatype to define your function pointers. Seems odd, yes, but pointers are only numbers. Try avoiding BYTE, since the address of your function may be greater than 255, using BYTE may not be large enough. I suggest sticking with INTEGER/LONG.
DECLARE SUB Test (I AS INTEGER)
DIM FPtr AS INTEGER
BIND FPtr TO Test
Well, we use BIND in Rapid-Q to bind our variable FPtr to the function Test. This will do 2 things, obviously FPtr will point to the address space of Test, but it also binds the number of parameters to FPtr, this is important for the compiler so that it knows how many parameters to pass/accept. To call our function, we have to use the reserved word CALLFUNC:
CALLFUNC(FPtr, 120)
This calls our SUB Test with 1 parameter. Obviously this isn't a very useful example, so consider when it is useful...
DECLARE SUB Test1 (I AS INTEGER)
DECLARE SUB Test2 (I AS INTEGER)
DIM FPtr(1 TO 2) AS INTEGER
BIND FPtr(1) TO Test1
BIND FPtr(2) TO Test2
When you're binding arrays, please note that the first function that is bound will be the TEMPLATE for all your other elements. Confusing? Okay, let me put this into words people can understand. :) Our array is FPtr, our BIND bounds FPtr(1) to SUB Test1. FPtr is now BOUND. What this means is that if you BIND any other FPtr(i), the function MUST HAVE MATCHING NUMBER OF PARAMETERS. Maybe an example would help:
DECLARE SUB Test1 (I AS INTEGER)
DECLARE SUB Test2 (I AS INTEGER, J AS STRING)
DIM FPtr(1 TO 2) AS INTEGER
BIND FPtr(1) TO Test1
BIND FPtr(2) TO Test2
The second BIND is incorrect, since Test2 does not have matching parameters with Test1. You will receive an error message if you do this. Notice that I say matching NUMBER of parameters, you really don't need matching TYPE parameters. Don't do this though, results may vary.
- 11.3 Using function pointers properly
-
It will be clear (once you've used function pointers for a while) that there are times when using function pointers is necessary, and times when they're not. I think every situation is different, but consider this situation:
SELECT CASE I
CASE 1
Process1("1", 44)
CASE 2
Process2("2", 55)
CASE 3
Process3("3", 66)
CASE 4
:
:
etc...
You can generalize this situation, for example, a menu with several options, or perhaps a parser (yes Rapid-Q does use function pointers to parse your code). As you may notice, that's a lot of comparisons, especially if you're writing a parser where there's so many keywords (over 50 say). We can easily eliminate these comparisons by using function pointers, consequently speeding up your program. See FUNCPTR.BAS for a concrete example.
BIND FPtr(1) AS Process1
BIND FPtr(2) AS Process2
BIND FPtr(3) AS Process3
BIND FPtr(4) AS Process4
BIND FPtr(5) AS Process5
BIND FPtr(6) AS Process6
BIND FPtr(7) AS Process7
x = VAL(INPUT$(1))
CALLFUNC(FPtr(x), 33)
None of the code you see above is executable as is, but I think you get the general idea. We don't need any case statements as you can see (this is assuming you've partitioned your function pointers properly).
- 11.4 What is not supported in Rapid-Q
-
Function pointers as arguments is not supported. As funny as that sounds, this is actually very useful. Unfortunately I didn't get a chance to implement this (yet). You can pass the function pointer value as an argument, but as you can see, you won't be able to call your function. The only way would be to define a global function pointer as your template, and then assign that to your argument. It's easier to see with an example:
DECLARE FUNCTION MyFuncTemp (X AS LONG) AS LONG
DECLARE FUNCTION Func1 (X AS LONG) AS LONG
DECLARE FUNCTION Func2 (X AS LONG) AS LONG
DIM Template AS INTEGER
DIM I(100) AS INTEGER
BIND Template TO MyFuncTemp
BIND I(1) TO Func1
BIND I(2) TO Func2
SUB Test (FPtr AS INTEGER)
Template = FPtr
PRINT CALLFUNC(Template, 10)
END SUB
Test I(1)
That's about the only way you can work around this limitation.
Prev ChapterContentsNext Chapter