Win32Forth

Calling Windows Procedures

The Basics

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

Basics of DLLs

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.

Words used in Win32Forth

There are very few words required to make calls to external DLLs in Win32Forth. They are:

Step 1. Get the documentation for the DLL

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.

Step2. Tell Win32Forth about the DLL

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.

Step 3. Tell Win32Forth about the exported routines

To define the function in the DLL, use the PROC statement. Here’s a sample Windows call from the documentation:


AllocConsole
The AllocConsole function allocates a new console for the calling process.

BOOL AllocConsole(void);

Parameters This function has no parameters.

Return Values If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.

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

Step 4. Call the function

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.

More advanced features

Parameter order

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.

Procedure names

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

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:

  • BOOL (0 for TRUE or OK, 1 for FALSE or Not OK). To change these into Forth TRUE / FALSE values, follow with a 0= or ABORT" . For instance
  • CALL AllocConsole 0= IF ( all is OK )

    Or

    CALL FreeConsole ABORT" FreeConsole failed"

  • INT, HANDLE, etc; all these are unsigned or signed 32 bit (DWORD) values on the top of the stack. Use them as you normally would for Forth words.
  • VOID the 32 bit (DWORD) value on top of the stack is of no significance and should be discarded ( using DROP ).
  • Others: see below.

    Interpreting function parameters

    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.

    Using ABS>REL and REL>ABS (obsolete feature)

    ABS>REL and REL>ABS are obsolete. Don't use; ignore them if you see them in existing code. They do nothing.

    Using AS

    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 ).

    Dangerous tricks

    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 $