Rapid-Q Documentation by William Yu (c)1999-2000, appended by John Kelly | 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 correct case! 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 file name (and path if needed) 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 the folder as your application or 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"However, this DOES NOT apply to Rapid-Q, you can't do this, so don't even try.
MyFunc("Hello")
'RECT is a structure of the form: TYPE RECT Left AS LONG Top AS LONG Right AS LONG Bottom AS LONG END TYPE DECLARE FUNCTION GetWindowRect LIB "USER32" ALIAS "GetWindowRect" _ (hWnd AS LONG, lpRect AS RECT) AS LONG 'note lpRect is really a LONG expecting a value to be an address in memoryNow you write your code like this:
DIM R AS RECT GetWindowRect(MainForm.Handle, R)Sometimes in Windows programming you need to pass a NULL instead of the address of a UDT, for example to use a default. A NULL is just a long of value 0. To avoid getting a compile error, just change the type in the delaration:
DECLARE FUNCTION SpecialFunction LIB "Func.Dll" ALIAS "SpecialFunction"_
(hWnd AS LONG, lpRect AS LONG) AS LONG 'tell the program to expect a long value
SpecialFunction(MainForm.Handle, 0&) 'now this works
SpecialFunction(MainForm.Handle, R) 'and this works too
We just changed
the type of lpRect to LONG. This is because we want to pass a pointer (which
is just a number). This will also work for passing the UDT normally as
wellTYPE 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 'actually sends a Long Pointer Zero-Terminated 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$.
HANDLE
hFile,
// handle to file to write to
LPCVOID
lpBuffer,
// pointer to data to write to file
DWORD
nNumberOfBytesToWrite, // number of bytes to write
LPDWORD lpNumberOfBytesWritten, // pointer to number of bytes
written
LPOVERLAPPED
lpOverlapped // pointer to structure
needed for overlapped I/O
);
and now the same version in
RapidQ:
DECLARE FUNCTION WriteFile LIB "kernel32" ALIAS
"WriteFile"_
(hFile AS LONG,
_
'handle to file to write to
lpBuffer AS STRING,
_
'pointer to data to write to file
nNumberOfBytesToWrite AS
DWORD, _ 'number of bytes to
write
BYREF lpNumberOfBytesWritten AS DWORD, _ 'pointer
to number of bytes written
lpOverlapped AS OVERLAPPED) AS
LONG 'pointer to structure needed for
overlapped I/O
Notice how variable types (BOOL, HANDLE, LPCVOID,
etc.) are stated before the variable name (hFile, lpBuffer, etc.). Now Let's
take the example of a more complictated example, creating an stream of
data for an Audio-Visual-Interlaced file. The function is contained in a DLL
called AVIFIL32.DLL in your Windows system Folder.
The
AVIFileCreateStream function creates a new stream in an existing file
and creates an interface to the new stream. The declaration in C is like
this:
STDAPI AVIFileCreateStream(
PAVIFILE pfile,
PAVISTREAM * ppavi,
AVISTREAMINFO * psi
); //
semicolon ends the entire command.
STDAPI means this is a FUNCTION that exists externally,
and returns a LONG value (Often 'HRESULT' is used instead, but it all
really means a LONG value is returned).
AVIFileCreateSteam is the
name of the function.
pfile is a variable of type PAVFILE, which
actually a variable holding the value holding a pointer. The 'p'
reminds you it is a pointer.
ppavi is passed by reference (the *
symbol means 'give me the address of the variable)
psi is a UDT of
type AVISTREAMINFO and is passed to the API by reference (* means
pointer).
Here is how you make the same Declare
function in RapidQ:
Declare
Function AVIFileCreateStream Lib "avifil32.dll" ALIAS
"AVIFileCreateStreamA"_
(ByVal pfile AS LONG,
_
Byref ppavi AS LONG, _
ByRef
psi As AVISTREAMINFO) AS LONG
Notice there are big differences,
especially since the actual name in the DLL is AVIFileCreateStreamA,
but you will always call this function in Rapid-Q by
AVIFileCreateStream.
Here is another example, showing how to pass
a string to a Windows API:STDAPI AVIFileOpen( PAVIFILE *
ppfile, LPCTSTR szFile, UINT mode, CLSID
pclsidHandler );
Here is how you
make the same Declare function in RapidQ:
Declare Function AVIFileOpen Lib "avifil32.dll"
ALIAS "AVIFileOpen"_
(ByRef ppfile AS LONG,_
'the * in the C code
means ByRef
ByVal szFile As String,
_ 'szFile
= 'string with zero terminated character' of file name
ByVal
uMode AS
LONG,_
'the API wants a UINT, or an unsigned integer, but LONG is
ok
_ByVal pclsidHandler AS LONG) AS LONG
'CLSID is a pointer, but we will pass a NULL, so
use ByVAL
As you can see, translating C code can be
tricky. Luckily, many API declarations have already been done for Visual
Basic, and converting from Visual Basic to RapidQ is much easier than C to
RapidQ. There is even a tool in FreeBasic called SWIG that converts C include
files to FreeBasic code. To help understand how to convert between different
variable types, 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 | Visual Basic ByRef is default |
Rapid-Q ByVal is default for most |
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 HWD, HRESULT, HMENU,etc. |
Windows Handles | byval as LONG | LONG |
INT, UINT | INTEGER, DWORD | byval as LONG | INTEGER, DWORD |
LONG | LONGINT | byval as LONG | LONG |
Pointers to a variable that are passed by value LPVOID, LPDWORD, LPINT, LPUNIT, etc. |
LONGINT | byval as LONG | BYVAL as LONG |
Pointers to a long LPVOID * , LPDWORD *, LPINT *, etc |
^DWORD | as LONG | BYREF as LONG |
LPARAM | LONGINT | byval as LONG | LONG |
LPINT, LPUINT | ^INTEGER | as LONG | BYREF as LONG |
LPRECT | ^TRECT | as ANY | QRECT |
LPSTR, LPCSTR, LPCTSTR | PCHAR | byval as STRING | as STRING or BYVAL 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 | BYVAL as LONG (use 0& in call) |
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 |