| Rapid-Q Documentation by William Yu (c)1999-2000 | Chapter 9 |
ParamStr$() - Array of string parameters ParamVal() - Array of numeric parameters ParamValCount - Number of numeric parameters passed ParamStrCount - Number of string parameters passedAs a simple example, we'll just test to see that it works:
SUBI TestSUBI (...)
PRINT "String Parameters: "; ParamStrCount
FOR I = 1 TO ParamStrCount
PRINT I;" "; ParamStr$(I)
NEXT I
PRINT "Numeric Parameters: "; ParamValCount
FOR I = 1 TO ParamValCount
PRINT I;" "; ParamVal(I)
NEXT I
END SUBI
TestSUBI "Hello", 1234, "Hmmm", "Yeah...", 9876, 1*2*3*4, UCASE$("Last one")
You'll probably notice that the ParamStr$() and ParamVal() arrays aren't zero-based (ie.
their first value starts at 1). This may seem strange, since everything in Rapid-Q is zero-based.
Anyway, besides that, something equally strange is the use of (...) in replace of
parameter names. It doesn't really matter what you put in the brackets, as long as
it's a valid token (one word). Try creating a FUNCTIONI, it's no different
from creating a standard FUNCTION except you replace your formal parameter names
with (...).
FUNCTIONI FindMax (...) AS DOUBLE
DIM Largest AS DOUBLE
DIM I AS BYTE
Largest = -999999999999
FOR I = 1 TO ParamValCount
IF ParamVal(I) > Largest THEN
Largest = ParamVal(I)
END IF
NEXT
FindMax = Largest ' or Result = Largest
END FUNCTIONI
The function goes through the entire list of numeric parameters and checks
which one is the largest. Any string parameters are properly ignored.
Now let's test it:
PRINT "Largest number is: "; FindMax(523, 12.4, 602, 45, -1200) PRINT "Largest number is: "; FindMax(523, 12.4, FindMax(602, 45, -1200))You can comfortably embed your FUNCTIONI if you like. There's really nothing to it. Just remember the keywords you need to access the internal stack, and away you go!
DECLARE SUB Test LIB "TEST.DLL" ALIAS "MyFunc" (S AS STRING)So you must be wondering, which is case sensitive, Test or MyFunc. Well, Test is the one you'll be calling in your own program, so you know that can't be it, MyFunc is the one contained in the DLL, so make sure you typed that in correctly! The above SUB will point to the memory address of your DLL (when loaded), and execute the code within. As you can see, there's only one parameter for our MyFunc routine. There are only 2 extra keywords introduced here, LIB and ALIAS. After LIB should be a string corresponding to the path of your DLL. If your DLL resides in your $PATH setting, you don't need to specify any path (ie. if your TEST.DLL resides in C:\WINDOWS\SYSTEM). The second keyword ALIAS is to specify the function/subroutine you can execute in that DLL. Make sure you check your case, this is very important. If you're not sure of the case, you can use a DLL viewer (such as Quick View, more on this later). What are the valid datatypes to pass?
DECLARE SUB MyFunc LIB "TEST.DLL"
As long as your function matches that of the DLL function, you can ignore using ALIAS.
However, this DOES NOT apply to Rapid-Q, you can't do this, so don't even try.
MyFunc("Hello")

DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _
(hWnd AS LONG, lpRect AS RECT) AS LONG
RECT is a structure of the form:
TYPE RECT
Left AS LONG
Top AS LONG
Right AS LONG
Bottom AS LONG
END TYPE
Naturally, you would like this to work:
DIM R AS RECT
GetWindowRect(MainForm.Handle, R)
YES, THIS WORKS NOW.
However, Rapid-Q stores user defined types noncontiguously, ie. not as one
whole block as required by the DLL call, so this call is invalid.
So what does work? A simple workaround is necessary, using the ever useful
QMEMORYSTREAM component:
'' Notice the change in lpRect
DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _
(hWnd AS LONG, lpRect AS LONG) AS LONG
DIM R AS RECT
DIM M AS QMEMORYSTREAM
M.WriteUDT(R)
GetWindowRect(MainForm.Handle, M.Pointer)
M.Position
M.ReadUDT(R)
The first thing we do is change the parameters of the DLL Function.
We just changed the type of lpRect to LONG. This is because we want to pass
a pointer (which is just a number). Here we use QMEMORYSTREAM to store the
user defined type (using M.WriteUDT) in one contiguous block for our DLL call.
All we have to do is pass the pointer to this block of memory and the DLL
will read from this memory location. After calling the function we should
retrieve the returned data (if necessary, in this case GetWindowRect returns the
rect structure of the Window). This is rather simple, but what if there are
nested TYPEs within a TYPE?
TYPE Struct1
A AS INTEGER
END TYPE
TYPE Struct2
S AS Struct1
I AS INTEGER
END TYPE
If a DLL call requires a TYPE, which may have nested TYPEs, then you can
do the same kind of conversion:
TYPE Struct1
A AS INTEGER
END TYPE
TYPE Struct2
S AS LONG
I AS INTEGER
END TYPE
DIM S1 AS Struct1
DIM S2 AS Struct2
DIM M1 AS QMEMORYSTREAM
DIM M2 AS QMEMORYSTREAM
M1.WriteUDT(S1)
S2.S = M1.Pointer
M2.WriteUDT(S2)
'' Call DLL function...
Now the last case we have to consider is strings within TYPEs.
We can't just declare a string anymore, we have to declare a pointer to a string,
for example:
TYPE TStruct
ST AS STRING
Other AS INTEGER
END TYPE
This would not be valid since ST would be passed by value (in which case it can
consume any number of bytes in memory). However, most DLL calls want strings
passed by reference (ie. just give it the pointer to the string). A conversion
is necessary, using VARPTR:
TYPE TStruct
ST AS LONG
Other AS INTEGER
END TYPE
DIM Struct AS TStruct
DIM S AS STRING
S = SPACE$(100) '' Allocate some space for string
Struct.ST = VARPTR(S)
'' To get back the string use VARPTR$
S = VARPTR$(Struct.ST)
Please note that this string conversion is only necessary for TYPEs and not necessary
for normal DLL calls. For normal DLL calls which have STRING as parameters, just pass
the string and not the pointer, since Rapid-Q will translate this automatically for you.
You can, of course, do the conversion yourself, just remember to change the STRING
parameter to something like LONG, and pass VARPTR(S$) instead of the actual string S$.
| C data type | Pascal | VB | Rapid-Q |
| ATOM | SHORT | byval as INTEGER | SHORT |
| BOOL | BOOLEAN | byval as LONG | LONG |
| BYTE | BYTE | byval as BYTE | BYTE |
| CHAR | CHAR | byval as BYTE | BYTE |
| CHAR[20] | STRING[20] | as STRING | N/A in API Declarations |
| COLORREF | LONGINT | byval as LONG | LONG |
| DWORD | DWORD | byval as LONG | DWORD |
| Windows handles ie. HDC | Windows Handles | byval as LONG | LONG |
| INT, UINT | INTEGER, DWORD | byval as LONG | INTEGER, DWORD |
| LONG | LONGINT | byval as LONG | LONG |
| LPARAM | LONGINT | byval as LONG | LONG |
| LPDWORD | ^DWORD | as LONG | BYREF as DWORD |
| LPINT, LPUINT | ^INTEGER | as LONG | BYREF as LONG |
| LPRECT | ^TRECT | as ANY | QRECT |
| LPSTR, LPCSTR | PCHAR | byval as STRING | BYREF as STRING |
| LPVOID | LONGINT | as ANY | LONG |
| LPWORD | ^WORD | as INTEGER | BYREF as WORD |
| LRESULT | LONGINT | byval as LONG | LONG |
| NULL | NIL | byval as LONG | LONG |
| SHORT | SHORT | byval as INTEGER | SHORT |
| WORD | WORD | byval as INTEGER | WORD |
| WPARAM | LONGINT | byval as LONG | LONG |
In VB:
DECLARE SUB Test LIB "USER32" ALIAS "What" _
(byval L AS LONG, byval S AS STRING)
Test (1230, "Hello world!")
In Rapid-Q:
DECLARE SUB Test LIB "USER32" ALIAS "What" _
(byval L AS LONG, byval S AS STRING)
Test (1230, "Hello world!")
Nothing different there, note that Rapid-Q doesn't use BYVAL, so you can ignore
putting them there. In the above example, the string is passed by reference, ie.
the address of the string is passed, not the string itself.
Here's an example which passes the whole string (not the address, so it involves
OLE to allocate space for the string), this works fine in VB but not in Rapid-Q:
In VB:
DECLARE SUB Test LIB "USER32" ALIAS "What" _
(byval L AS LONG, S AS STRING)
Test (1230, "Hello world!")
In Rapid-Q:
Not possible, since it doesn't use OLE to allocate the string.
How about NULL strings? Here's another situation (please note that I don't use
VB much so it may be possible to declare S as STRING and pass it a vbNullString,
I'm not really sure so I'll play it safe):
In VB:
DECLARE SUB Test LIB "USER32" ALIAS "What" _
(byval L AS LONG, byval S AS LONG)
Test (1230, 0&)
In Rapid-Q:
DECLARE SUB Test LIB "USER32" ALIAS "What" _
(byval L AS LONG, byval S AS LONG)
Test (1230, 0&)
So what if we want to pass a string instead of a NULL string, then you'd need to
use VARPTR:
In VB or Rapid-Q:
DIM S AS STRING
S = "Hello world!"
Test (1230, VARPTR(S))
A NULL string basically means an address of 0.
VARPTR just returns the address of a variable.
How about other pointers, like LPWORD? A WORD is just an unsigned 16-bit number,
VB only supports signed 16-bit numbers called INTEGERs, but the general idea is
to remove the byval keyword when passing variables by reference:
In VB (doesn't support WORD, so use next best):
DECLARE SUB Test LIB "USER32" ALIAS "Another" _
(L AS INTEGER)
DIM L AS INTEGER
Test (L)
In Rapid-Q:
DECLARE SUB Test LIB "USER32" ALIAS "Another" _
(BYREF L AS WORD)
DIM L AS WORD
Test (L)
In Rapid-Q, use BYREF when passing variables by reference, in VB you don't need to
use BYREF, as the above example demonstrates.
| Prev Chapter | Up | Contents | Next Chapter |