-
Traditional SUBs or FUNCTIONs have a fixed number of parameters we can pass, but SUBI and FUNCTIONI can accept an infinite number (255) of variable parameters. You're probably wondering how we can retrieve these parameters, well, they are actually stored in an internal stack. So what you'll have to do is probe this stack for the correct parameter. Here are the keywords used to do that:
ParamStr$() - Array of string parameters
ParamVal() - Array of numeric parameters
ParamValCount - Number of numeric parameters passed
ParamStrCount - Number of string parameters passed
As 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 (...).
- 9.2 More on FUNCTIONI
-
To expand our knowledge, let's do more examples, here's one that will find the maximum number from the parameters you provide it.
FUNCTIONI FindMax (...) AS DOUBLE
DIM Largest AS DOUBLE
DIM I AS BYTE
Largest = -99999
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!
- 9.3 Introduction to DLLs
- What is a DLL? It's a Dynamic Link Library which contain exported functions which can be used by any programming language that support DLLs. So you could write your DLL in Delphi or C++ and use those functions in Rapid-Q. DLLs work by loading in the same process space as your program, so you have access to their exported functions. DLLs in Rapid-Q can only be dynamically linked at run-time (ie. executed at run-time).
- 9.4 How to call a DLL
-
There is an example called DLL.BAS included in your Rapid-Q distribution. Included is also a DLL called PASCAL.DLL. You may want to take a look at it. To call DLLs in Rapid-Q is almost identical to the way you call DLLs in PowerBASIC or VisualBASIC. It may look nasty, but I've decided to conform to that standard... One thing to note is that CASE MATTERS! Yes, that's right, if you miscapitalized your function, ka-boom! Oops, not to confuse you even more, but there's actually 2 functions. One which you will call in your program, and the other is the one contained in the DLL. These two functions/subs must have matching parameters, or ka-boom!
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?
-
SHORT/WORD, INTEGER/LONG/DWORD, DOUBLE, STRING, QRECT, QNOTIFYICONDATA, and any UDT you create.
Unlike some implementations which force you to pass your STRING as a pointer, Rapid-Q will do all this for you, so you need not worry. Whenever you see LPZSTR as a parameter, that just means Long Pointer Zero-Terminated String. You can safely pass any string variable. You may notice that some BASIC languages (like VB) will ignore ALIAS whenever the function matches the same function you're declaring. For example:
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.
-
Calling a DLL function or calling your own function is exactly the same, there's nothing more you need to know:
MyFunc("Hello")
- 9.5 Using Quick View
-
As mentioned before, if you're not sure of the exact case of your function, it's a good idea to take a look using Quick View.
Quick View is included with Windows (you don't need to download it). To invoke Quick View, load up Windows Explorer (or click on "My Computer"), then find a .DLL file, right click on it, and select Quick View from the menu. Look for an Export Table, that's where you'll find all the available functions. There are probably better DLL viewers available, but I won't advertise them here. Most of the time, Quick View is all you'll need.
- 9.6 Writing your own DLLs
- As of this writing, it's not possible to write DLLs in Rapid-Q. Most languages are capable of creating DLLs, the only problem I see is STRINGs, especially if you're using VisualBasic or PowerBasic. Please use pointers to strings in your parameters to avoid using OLE to allocate the string itself. Rapid-Q won't use OLE to allocate the string, since I don't anticipate many DLLs that don't use pointers to strings.
- 9.7 Using unsupported types in DLL call
-
THIS SECTION IS NULL AND VOID WITH THE INTRODUCTION OF STRUCT, BUT CAN STILL BE USED IF YOU LIKE. Rapid-Q has a different storage mechanism for user defined TYPEs which renders it useless for DLL calls that require certain TYPE parameters. However, all is not lost. There is a cheap workaround which works just fine, but does need a little getting used to. DLL functions that require TYPE parameters need to be passed a pointer to that TYPE, so how is this accomplished? Let's look at an example of a DLL function which requires a TYPE parameter:
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$.
- 9.8 API conversion table
-
A lot of people have requested an API guide to convert their WinAPI declarations in C or VB to use with Rapid-Q, so here is a basic summary with some brief explanations. Special thanks to Mayuresh S. Kadu for this WIN32 API in VB guide.
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 |
For those data types in red, they are pointers to the variable. In Rapid-Q, this requires using VARPTR in passing the address of the variable, and not the value of the variable. Here's some sample conversions:
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 ChapterContentsNext Chapter