DLL Commands and Related Callback Functions DLL Type: Command Scope: All OS: Windows (Win32S), Windows95, NT The DLL group of commands provides SPL access to 32 bit Windows (Win32S, Windows95, and NT) Dynamic Link Libraries (DLL). For example, an SPL program can display a message dialog box by calling the Windows USER32.DLL library function: MessageBoxA. NOTE: Since DLL functions vary widely, it is up to the programmer to determine the appropriate calling conventions and return values. Use of DLL functions requires great care. If a DLL function is called with the wrong number of arguments, the stack may become corrupted and the subsequent operation of SmartWare will be unpredictable. There is no error-checking or other protection for this. Using DLLs - General Information -------------------------------- Basically, there are three library commands. The first step in calling a DLL function is to load the library. DLL LOAD "dll_name" #dll_handle If the command is successful, a value greater than zero is placed in the handle variable. The handle is then used to call functions in the loaded library. There are actually several variations of the CALL command, but in its most basic form, only the handle and function name are required. DLL CALL #dll_handle FUNCTION "function_name" Note that the details for calling DLL functions and getting return values from them can be complex. Refer to the DLL CALL command for information. When a DLL is no longer needed, the library can be unloaded. DLL UNLOAD #dll_handle If left loaded, libraries are automatically unloaded when SmartWare exits. See also: DLL CALL, DLL LOAD, DLL UNLOAD, and DLL Callback Functions. DLL CALL Formats: DLL CALL handle FUNCTION function DLL CALL handle FUNCTION function STACK template expression(s) DLL CALL handle ORDINAL ordinal DLL CALL handle ORDINAL ordinal STACK template expression(s) This command calls an exported function from a Windows Dynamic Link Library (DLL). The handle variable (derived from a DLL LOAD command) indicates the library that contains the function. Functions can be specified by name or by ordinal number. DLL Calling Conventions ----------------------- It is up to the programmer to determine the appropriate calling conventions used by DLL functions. The following table describes some of the possible conventions: Type Uses Comment --------- --------- -------------------------------------------- Pascal stack Parameters are passed to functions via the stack. First item pushed onto the stack is the first item. This is a common convention for Windows 3.1 DLLs. CDECL stack Parameters are passed to functions via the stack. Last item pushed onto the stack is the first item. This is a common convention for Win32, Windows95, and NT DLLs. Register register Parameters are passed to functions via the registers. In this case, the STACK option is not used. Instead, set the registers with the SETREG function prior to the DLL CALL command. The STACK option is used to place parameter values on the stack (refer to the Pascal and CDECL conventions above) rather than passing them through registers. Elements on the stack are pushed as 32 bit values (i.e., four bytes at a time) in reverse order (CDECL convention). The examples below use the CDECL convention. The information placed on the stack is specified using a template (refer to the section titled Buffer/Memory Templates in the Project Processing manual). Basically, a template is made up of a variety of variable types such as LONG, POINTER, DOUBLE, etc. This is the same format used by the INTERRUPT, PACK, and UNPACK commands. The amount of data must be an even number of bytes, with a maximum of 64 bytes. See Example # 2. Return Values ------------- Register values are saved for access by the GETREG function. In most cases, the DLL function sets the EAX register. If the value is greater than 32 bits, the EDX register will be used. Example # 1 ----------- In this example, the USER32 library is loaded and the MessageBoxA function is called. This DLL function takes four parameters: a LONG, two POINTERS, and another LONG which are specified in the template as "LPPL" of the DLL CALL command. The template is followed by the parameter values. Note that the style parameter tells MessageBoxA to display both the OK and Cancel buttons. To find out which button the user pressed, the AX register is inspected with GETREG. MessageBoxA is called again, this time with only the OK button. Finally, the USER32 DLL is unloaded. '------- Example # 1 ----------------------------------- MAIN LOCAL #dllhandle $title, $msg, #style DLL LOAD "user32.dll" #dllhandle $msg = "Select OK or Cancel" $title = "SPL Message 1" #style = 1 ' OK and Cancel buttons DLL CALL #dllhandle FUNCTION "MessageBoxA" STACK "LPPL" 0 DOSPTR($msg) DOSPTR($title) #style CASE GETREG(ax) WHEN 1 $msg = "OK was selected" WHEN 2 $msg = "Cancel was selected" OTHERWISE $msg = "Unknown selection" END CASE $title = "SPL Message 2" #style = 0 ' OK button only DLL CALL #dllhandle FUNCTION "MessageBoxA" STACK "LPPL" 0 DOSPTR($msg) DOSPTR($title) #style DLL UNLOAD #dllhandle END MAIN Example # 2 ----------- The second example creates an SPL "wrapper" for the MessageBoxA and CustomColorA DLL functions. These wrapper functions do not load the DLLs, but instead accept the library handle as a parameter - this is to avoid excessive loading and unloading of libraries. MAIN is used to load the DLLs, define a color array, call the wrappers, and unload the libraries. The DllMessageBox function is straight-forward. It's basically a generalized version of the first example. The DllCustomColors function is a little bit more complicated because CustomColorA uses two structures. The first structure, represented by the buffer variable $customcolor, defines and returns the 16 custom colors. The second structure contains the parameters for CustomColorA, including $customcolor. A pointer to a 16 element array, defined in MAIN, is passed to DllCustomColors. Within DllCustomColors, the value of each element is read and then PACKed into the $customcolor buffer variable. This, and the other CustomColorA parameters are PACKed into the $parameters buffer variable. CustomColorA is called. Parameters are passed as a pointer using the DOSPTR function on the $parameters buffer variable. Internally, CustomColorA places the new colors into $customcolor. This is then UNPACKed and written to each element of the original array. Because, the #color[] array now contains the color values defined by the user, CustomColorA presets those values the second time DllCustomColors is called. '------- Example # 2 ----------------------------------- GLOBAL DllMessageBox(4) GLOBAL DllCustomColors(2) MAIN LOCAL Huser LOCAL Hcomdlg #colors[16] DLL LOAD "user32.dll" Huser DLL LOAD "comdlg32.dll" Hcomdlg IF DllCustomColors( Hcomdlg, ARRAYPTR( #colors[1] ) ) = 0 DllMessageBox( Huser, "Message", "Cancelled", 0 ) EXIT MAIN END IF IF DllMessageBox( Huser, "Message", "Edit Again?", 1 ) = 1 DllCustomColors( Hcomdlg, ARRAYPTR( #colors[1] ) ) END IF DLL UNLOAD Huser DLL UNLOAD Hcomdlg END MAIN '------------------------------------------------------- FUNCTION DllMessageBox( #hnd, $title, $msg, #style ) '#style - 1 = OK and Cancel buttons - 0 = OK button only DLL CALL #hnd FUNCTION "MessageBoxA" STACK "LPPL" 0 DOSPTR($msg) DOSPTR($title) #style RETURN GETREG(ax) END FUNCTION '------------------------------------------------------- FUNCTION DllCustomColors( #hnd, AP_colors ) LOCAL $parameters $customcolor #ctr #ptr #hold BUFFER $customcolor SIZE 64 BUFFER $parameters SIZE 36 #ptr = 1 FOR #ctr = 1 TO 16 #hold = READPTR( AP_colors, #ctr ) PACK $customcolor[#ptr] "L" #hold #ptr = #ptr + 4 END FOR PACK $parameters "LLLLPLLLL" 36 0 0 0 DOSPTR($customcolor) 2 0 0 0 DLL CALL #hnd FUNCTION "ChooseColorA" STACK "P" DOSPTR($parameters) #ptr = 1 FOR #ctr = 1 TO 16 UNPACK $customcolor[#ptr] "L" #hold WRITEPTR( AP_colors, #ctr, #hold ) #ptr = #ptr + 4 END FOR RETURN GETREG(ax) END FUNCTION Notes ----- Both USER32 and COMDLG32, used in the examples, are standard 32 bit Windows DLLs. The Windows SDK documentation, as well as other publications, cover the standard Windows DLL function interface. The suffix A used in DLL function names, such as MessageBoxA, indicates the use of ASCII rather than Unicode where W is used as the suffix. These are typically applied when functions use strings. Note that your documentation may or may not include the suffix. See also: DLL, DLL LOAD, DLL UNLOAD and DLL Callback Functions. DLL LOAD Format: DLL LOAD dll_name handle This command loads a Windows Dynamic Link Library (DLL). Libraries must be 32 bit (i.e., Win32S, Windows95, or NT). DLL LOAD "dll_name" #dll_handle If the command is successful, a value greater than zero is placed in the handle variable. This handle is used in all subsequent operations with the specified library. If the specified library is already loaded, the same handle is returned. See also: DLL, DLL CALL, DLL UNLOAD. DLL UNLOAD Format: DLL UNLOAD handle This command unloads a Windows Dynamic Link Library (DLL). DLL UNLOAD #dll_handle If left loaded, libraries are automatically unloaded when SmartWare exits. If the library was loaded by a process other than the DLL LOAD command, this command will not unload the library but instead decrements an internal counter that was originally setup or incremented by DLL LOAD. In cases where the DLL LOAD command was not used, the handle is simply invalid. See also: DLL, DLL CALL, DLL LOAD. DLL Callback Functions Callbacks provide SPL control over executing Windows Dynamic Link Libraries (DLLs). If you are not familiar with the DLL commands, read that section before continuing here. NOTE: Due to the way Windows handles messages, using callbacks with some standard DLL functions may result in unpredictable behavior - possibly halting the program. The DialogBox function, which uses callbacks, is an example of this. -There will be more information in the next version of this document. Because of the limitation described above, DLLs should be created by the programmer. For this reason, the C source code for the DLLSAMP library has been included in the example below. Using DLL Callbacks - General Information ----------------------------------------- DLL callbacks are set up in SPL programs with the CALLBACK_CREATE function before calling a DLL function. This creates a stub and "announces" that a named public function can be accessed by the DLL. Typically, the execution path taken is: an SPL program loads a library, names a public function as a callback and then calls a DLL function (that can handle callbacks). The DLL function, in turn, does whatever it needs to do - including calling the public SPL function named with CALLBACK_CREATE. Callbacks are removed with the CALLBACK_DELETE function. Example (part 1) ---------------- In the SPL code example, the DLLSAMP library is loaded, the public function CallbackTest is announced and the DLL function _dll_entry is called twice. The CallbackTest function is also defined in the same program. It's job is to find out whether the DLL function was passed the message "First Call" or some other message. If "First Call" is detected, the AX register is set to one, otherwise AX is set to zero. The first _dll_entry call passes the "First Call" message and so the CallbackTest function places a one in the AX register. The "magic" message is not passed in the second call to _dll_entry thus, zero is placed in AX. Note that callback functions must have one and only one parameter. This parameter is a buffer variable which is UNPACKed and read within the callback. In this way, multiple parameters are passed to the callback. '------- SPL Code Example ------------------------------ PUBLIC CallbackTest(1) MAIN LOCAL $msg #dllhandle #callback_test DLL LOAD "dllsamp.dll" #dllhandle #callback_test = CALLBACK_CREATE( "CallbackTest", 1, 0 ) $msg = "First Call" DLL CALL #dllhandle FUNCTION "_dll_entry" STACK "PP" DOSPTR($msg) #callback_test $msg = "Second Call" DLL CALL #dllhandle FUNCTION "_dll_entry" STACK "PP" DOSPTR($msg) #callback_test CALLBACK_DELETE( #callback_test ) DLL UNLOAD #dllhandle END MAIN '------------------------------------------------------- FUNCTION CallbackTest( $stack ) LOCAL $msg $pmsg UNPACK $stack "P" $pmsg PEEK 0 $pmsg "S" $msg IF $msg == "First Call" SETREG(ax, 1) ELSE SETREG(ax, 0) END IF END FUNCTION Example (part 2) ---------------- The API entry function, LibMain simply displays a message box when it is loaded and unloaded. In practice, this would not be done. The dll_entry function executes the SPL callback function (in this case, CallbackTest) and passes it a message (CallbackTest takes one parameter). The return value from the callback is tested (in this case, the value is one when the passed message is "First Call"). The buf variable is then given the value "Callback returned 1" or "Callback failed" based on this test. Finally, the MessageBox function is called to display the results. '------- DLL Source Example ---------------------------- #include #include int global_var = 1; int APIENTRY LibMain(HANDLE hdll, DWORD reason, LPVOID reserved) { switch (reason) { case DLL_PROCESS_ATTACH: MessageBox(NULL, "DLL_PROCESS_ATTACH", "Load DLL", MB_OK); break; case DLL_PROCESS_DETACH: MessageBox(NULL, "DLL_PROCESS_DETACH", "Unload DLL", MB_OK); } return (1); } void __cdecl dll_entry(char *msg, int __cdecl (*callback)(char*)) { char buf[80]; if ((*callback)(msg) == 1) sprintf(buf, "Callback returned 1"); else sprintf(buf, "Callback failed"); MessageBox(NULL, buf, msg, MB_OK); } CALLBACK_CREATE Format: CALLBACK_CREATE( string, numeric, numeric ) Scope: All - Windows (Win32S), Windows95, NT Returns: Numeric DLL callbacks are set up in SPL programs with the CALLBACK_CREATE function before calling a DLL function. This creates a stub and "announces" that a named public function can be accessed by the DLL. Note that callback functions must have one and only one parameter. This parameter is a buffer variable which is UNPACKed and read within the callback. In this way, multiple parameters are passed to the callback. NOTE: The named callback function must be an existing public function. The second parameter is the number of items placed on the stack. In the example used in DLL Callback Functions, there is only one item, message, placed on the stack. The third parameter is the number of items popped off the stack by the callback. In some cases, it is up to the DLL function to pop items, in others, it's the callback's job. CALLBACK_CREATE returns zero if unsuccessful or a handle which is later used by the CALLBACK_DELETE function. See also: DLL, DLL Callback Functions, CALLBACK_DELETE. CALLBACK_DELETE Format: CALLBACK_DELETE( handle ) Scope: All - Windows (Win32S), Windows95, NT Returns: Numeric Callbacks created with CALLBACK_CREATE are removed with the CALLBACK_DELETE function. The handle parameter indicates the callback to remove. This value is returned by CALLBACK_CREATE. NOTE: Unpredictable results may occur if CALLBACK_DELETE is issued for a function that is not a callback or has already been removed. See also: DLL, DLL Callback Functions, CALLBACK_CREATE.