Making Window’s calls from Win32Forth is easy – when you know how. This tutorial introduces the key words used to interface with DLLs and provides some simple samples to illustrate.
This assumes that you are using a version of Win32Forth that supports CALL and PROC statements from the kernel. To check if this is so, type .LIBS . The output should look like the output on the right. (The meaning of each of the columns will be explained later, and note that the addresses and the list of libraries may be different on your machine, as they vary by operating system and version of Win32Forth.)
.libs
Location Handle Type Name
-------- -------- ---- -----------
000450D8 71710000 APP COMCTL32.DLL
0002C91C 76B30000 APP COMDLG32.DLL
0001794C 10000000 APP WINCON.DLL
00017934 782F0000 APP SHELL32.DLL
0001791C 7C2D0000 APP ADVAPI32.DLL
00017908 77F40000 APP GDI32.DLL
00013490 00D20000 APP PRINT.DLL
0000E6D8 63180000 APP SHLWAPI.DLL
0000BFA0 7C570000 KERN KERNEL32.DLL
0000B0FC 77E10000 APP USER32.DLL
0000A970 00D40000 APP CONSOLE.DLL
DLLs (Dynamic Link Libraries) are just like other programs run on your PC, with a few key differences.
DLLs generally provide pre-packaged functions that can be used by other programs, and to use DLLs effectively in your programs, you will need the documentation for them.
There are very few words required to make calls to external DLLs in Win32Forth. They are:
Without documentation for your DLL, it will not be possible to work out the names of the functions that the DLL contains, nor their parameters and return values. Valuable documentation on the key Window’s DLLs can be found at msdn.microsoft.com, and there are a number of third party documents that cover them too. For other DLLs, you will need to refer to the documentation that comes with it.
Win32Forth needs to be told the name of the DLL so that it can load it. The statement WINLIBRARY KERNEL32.DLL allows W32F to load the DLL, ready to call the routines exported within it. The search path for DLLs varies by operating system (95/98/ME/NT/2000/XP), but in general specifying only the DLL name requires the DLL to be in a system directory, or the directory where Win32Forth was started. Most DLLs can be found this way, and it’s unusual to see any path name specified. WINLIBRARY can be repeated for the same library as often as you wish; only one copy will be loaded. The .LIBS command shows the libraries that have been specified and loaded.
Location Handle Type Name
-------- -------- ---- -----------
0003EA5C -noload- APP TESTIT.DLL
00014C08 10000000 APP WINCON.DLL
. . .
This word is normally only typed from the console. This shows TESTIT.DLL has been specified but not yet loaded. If you get errors calling routines that W32F can’t find, but you think should be found in your library, check the handle address. If it’s still -noload-, then W32F couldn’t find the library. See CALL for more details.
To define the function in the DLL, use the PROC statement. Here’s a sample Windows call from the documentation:
|
This function is declared as
0 PROC AllocConsole
that is, the function takes 0 parameters and is called AllocConsole. Note the case – PROC and CALL statements are case-sensitive! You MUST spell the name as it is shown. However, the number of parameters is currently only for documentation purposes right now. Valid values are 0 through 127 – don’t specify negative numbers, as they are used to indicate special system procedures.
This step is optional – you don’t need to have a PROC statement, as W32F will dynamically define the PROC during a CALL statement. There’s a limit to the number of PROCs that can be dynamically defined, currently around 100 to 150. Defined procedures can be seen with a .PROCS command. Restrict the output to a substring (case not important).
.PROCS ALLOC
Location ProcName Prm ProcEP LibName
-------- -------- --- -------- --------
0003EA5C AllocConsole 0
00012814 GlobalLock 77E977E4 KERNEL32.DLL
000127F4 GlobalAlloc 77E96646 KERNEL32.DLL
0000B830 HeapReAlloc 4
0000B7F8 HeapAlloc 3 77FCB10F KERNEL32.DLL
Allocated Space: 12,288 bytes
Free Space Left: 6,997 bytes
Displayed 5 of 184 procedures defined
Now we’ve defined the library, and optionally defined the procedure, we can call the function with a CALL AllocConsole
The case IS important – and must match the documentation. After this call, you should have an open DOS console and a 0 on the stack, the return code from the function.
Parameters to Windows calls are passed on the stack IN REVERSE ORDER to the documentation. So, for example, if the documentation says BOOL Function (A, B, C); then we must code it as C B A CALL Function. Reversing the parameters is essential to getting the call to work.
Some functions appear to have as many as 3 names. For example, there’s CharUpperBuff, CharUpperBuffA and CharUpperBuffW. In fact, there’s only two. Look at this sample output from .PROCS:
Location ProcName Prm ProcEP LibName
-------- -------- --- -------- --------
0003EA98 CharUpperBuffW 2 77E1236F USER32.DLL
0003EA74 CharUpperBuffA 2 77E1263D USER32.DLL
00003350 CharUpperBuff 2 77E1263D USER32.DLL
The ProcEP column shows the entry point of the function. The CharUpperBuff and CharUpperBuffA functions are the same function called CharUpperBuffA, and refer to ASCII character functions, where a character is a byte (8 bits) wide.
The W function is a UNICODE, or 2-byte function. In general, you will want to use the A function as W32F is a byte character system. Any W32F PROC or CALL to a function with both an A and W variant will automatically load the A version without you having to specify it. If you REALLY want the W function, you must explicitly refer to it as (for example) 2 PROC CharUpperBuffW.
Return values from function calls generally appear as a single entry on the top of the stack after the call. As most functions are written in C or C++ which can’t return multiple values, other techniques are used when a function needs to return more than one value.
Return values are:
CALL AllocConsole 0= IF ( all is OK )
Or
CALL FreeConsole ABORT" FreeConsole failed"
Others: see below.
Function parameters come in various flavours, and they not only need to be in the right order, but they must be of the correct type for the call to work. Here’s how to specify various parameters. An example:
BOOL ReadFile( HANDLE hFile, // handle to file LPVOID
lpBuffer, // data buffer
DWORD nNumberOfBytesToRead, // number of bytes to read
LPDWORD lpNumberOfBytesRead, // number of bytes read
LPOVERLAPPED lpOverlapped // overlapped buffer
);
becomes
0 VALUE HANDLE
CREATE BUFFER 255 ALLOT
20 VALUE BYTESTOREAD
VARIABLE BYTESREAD
: MYFUNC
0 \ null address for overlapped buffer
BYTESREAD \ bytes read
BYTESTOREAD \ bytes to read
BUFFER \ address of buffer
HANDLE \ value of handle
CALL ReadFile ;
: UPPERCASE ( caddr -- caddr )
DUP COUNT SWAP
call CharUpperBuff DROP ;
This function takes a counted string, changes it into (addr len), swaps to (len addr). This is needed, as the count is last, the string pointer first, for the call. The function returns a value on the stack (in this case n) which we’re not interested in. Actually, the above is not really a null terminated string call. Here’s one:
int lstrlen(LPCTSTR lpString);
This will count the characters in a string. So, Z" ABCDEFG" CALL lstrlen returns 7, the length of the string. The following string types in W32F are null (0) terminated, and can safely be used in calls:
If you wish to create your own string buffer, here’s a technique that creates a null terminated string.
CREATE MYSTR 256 ALLOT \ my string
S" ABCDEGF" MYSTR PLACE \ move string to MYSTR
MYSTR +NULL \ add null on the end
MYSTR CALL lstrlen \ use in call.
ABS>REL and REL>ABS are obsolete. Don't use; ignore them if you see them in existing code. They do nothing.
Sometimes it's useful to have a Forth word that can be used to obtain the XT of the procedure. Also some DLL procedure names can be rather long and obscure, so a more meaningful, shorter name would be useful.The following
1 PROC ExitThread AS EXIT-TASK
can be used to add EXIT-TASK to the Current Wordlist. AS should follow the procedure declaration ( after any comments if necessary ).
This is a dangerous trick. Use it ONLY when you’re comfortable with calling DLL functions. For the function BOOL function(LPWORD param); (assume it returns 1, and places a 10 in the area pointed to), then you can use the following:
0 SP@ CALL function
Immediately before the call, the stack looks like 0 | addr of previous cell |
After the function is called, the stack will have the values 10 | 1
No VARIABLE required! The ReadFile call above could be written as;
: MYFUNC 0 \ null address for overlapped buffer
0 SP@ \ bytes read
BYTESTOREAD \ bytes to read
BUFFER \ address of buffer
HANDLE \ value of handle
CALL ReadFile ;
This will return 2 values on the stack, the # of bytes read and the return code, in that order.
Document $Id: p-windlls.htm,v 1.1 2004/12/21 00:18:57 alex_mcdonald Exp $