|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 FUNCTIONIThe 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.
DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _ (hWnd AS LONG, lpRect AS RECT) AS LONGRECT is a structure of the form:
TYPE RECT Left AS LONG Top AS LONG Right AS LONG Bottom AS LONG END TYPENaturally, 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 TYPEIf 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 TYPEThis 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||STRING||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|
|LPSTR, LPCSTR||PCHAR||byval as STRING||BYREF as STRING|
|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||Contents||Next Chapter|