CALL DWORD statement

Purpose

Invoke a procedure (Sub or Function) indirectly.

Syntax

CALL DWORD dwpointer [{BDECL | CDECL | SDECL} ()]
CALL DWORD dwpointer USING abc([arguments]) [TO result_var]

Remarks

CALL DWORD is an essential ingredient for implementing run-time (explicit) dynamic linking of DLLs, rather than the more common load-time (implicit) dynamic linking.  This provides a way of constructing calls to APIs and DLLs that may not be present in all versions of Windows.  This technique ensures that an application can start up successfully, even if Windows cannot resolve the API or DLL function.  For more information on explicit linking, please review the Restrictions and Example code sections below.

The CALL DWORD statement has the following parts:

dwpointer

A Double-word, Long-integer, or pointer variable that contains the address of the entry point of a procedure (Sub or Function).

BDECL

Specifies that the declared procedure uses the BASIC/Pascal calling convention.  When a procedure calls a BDECL procedure, it passes its parameters on the stack left to right.

It is the responsibility of the called procedure to clean up the stack before returning to the calling procedure.  Therefore, all PowerBASIC Subs and Functions that specify the BDECL convention automatically clean up the stack before execution returns to the calling code.

CDECL

Specifies that the declared procedure uses the C calling convention.  When a procedure calls a CDECL procedure, it passes its parameters on the stack right to left.

In addition, the calling procedure cleans up the stack upon return from the called procedure.  When PowerBASIC code calls Subs and Functions using the CDECL convention, the stack is cleaned automatically after execution returns from the called code.

In the event the called procedure is imported or exported, PowerBASIC will automatically create a lowercase ALIAS, prefixed with an underscore.  Any periods in the name are replaced with underscores at the same time.

The following shows how the ALIAS name would be created by using a DECLARE statement as the example format:

DECLARE SUB C_Function CDECL ()
DECLARE SUB C_Function CDECL ALIAS "_c_function" ()

When declaring a CDECL SUB or FUNCTION, you can specify trailing parameters as OPTIONAL, using a set of brackets [..]:

DECLARE SUB KerPlunk CDECL (x%, y% [, z%])

Note that the comma separating the y% parameter from the optional z% parameter is inside the brackets.  The following calling sequences would then be valid:

CALL KerPlunk (x%, y%)
CALL KerPlunk (x%, y%, z%)

Optional parameters must be the last parameters designated in the list.  The following is not valid:

DECLARE SUB KerPlunk CDECL ([x%,] y%, z%)

Because the SUB or FUNCTION being called does not know how many parameters are being passed at the time it is called, you should pass the number of parameters as one of the required parameters in the list.

SDECL

This is the default if neither BDECL nor CDECL are specified.  SDECL (and its synonym STDCALL) specifies that the declared procedure uses the "Standard Calling Convention" as defined by Microsoft.  When calling an SDECL procedure, parameters are passed on the stack right to left.

PowerBASIC Subs and Functions that use the SDECL/STDCALL convention automatically clean up the stack before execution returns to the calling code.

USING

This option is used to define a model procedure declaration which matches all of the calling conventions desired to be used to invoke the target Sub/Function.  For example, the following two calls to the function MySubCall are equivalent:

DECLARE SUB MySubCall(Param1%, Param2%)
DIM PtrMySubCall AS DWORD
PtrMySubCall= CODEPTR(MySubCall)
...
CALL MySubCall(x1%, x2%)
CALL DWORD PtrMySubCall USING MySubCall(x1%, x2%)

arguments

An optional, comma-delimited list of variables, expressions, and constants to be passed to the procedure as parameters.  In the CALL DWORD context, enclosing parentheses are required.  The number and type of parameters passed must agree with the arguments of the procedure named by the USING clause.  See CALL for more information on parameter passing methods.

For information on using optional parameters, please see DECLARE, FUNCTION and SUB topics.

TO result_var

When calling a Function (which returns a value), the TO keyword offers a way to assign the function return value to result_var.  TO cannot be used without a USING clause also being used (and hence a DECLARE statement too), since PowerBASIC must be able to resolve the return data type.

If you call a Function through CALL DWORD with the USING option (but without a TO clause), the compiler can automatically discard the function result for you.  However, this is safe only if the return value is of integer-class (Byte, Word, Integer, Long, and Dword).  If the return value is a string or a floating-point value, you must employ the USING clause (in conjunction with an appropriate DECLARE statement) to avoid memory leaks.

Restrictions

A thread Function may not be directly called or executed, except by a THREAD  CREATE statement.

DECLARE statements and the USING option

If you employ the USING option to specify a DECLARE statement that describes the parameter list, make sure the DECLARE statement does not include a LIB clause.  Otherwise, the application will become linked to the DLL, just as if you had called the routine directly, rather than using CALL DWORD.

To solve the problem of accidental implicit-linking, remove the LIB and the ALIAS clauses from the DECLARE statement, and if necessary, rename the function to ensure it has a unique name (i.e., to avoid conflicts with DECLARE statements in WIN32API.INC, etc).  For example, consider the following "standard" DECLARE statement:

DECLARE FUNCTION GetDiskFreeSpaceEx LIB "KERNEL32.DLL" ALIAS
  "GetDiskFreeSpaceExA" (lpPath AS ASCIIZ, lpFreeToCaller AS
   QUAD, lpTotalBytes AS QUAD, lpTotalFreeBytes AS QUAD) AS LONG

This particular API is not available on early versions of Windows 95; therefore, it is standard practice to use explicit (run-time) linking to call this function, if it is available.  This is achieved through the LoadLibrary API (to determine if the DLL is available), followed by a call to GetProcAddress to obtain a code pointer to the actual API function (if the DLL includes the function).

Once a valid pointer has been obtained, the API can be directly called with the CALL DWORD statement, using a modified DECLARE to form a template for the USING option.  The modified DECLARE may look like this:

DECLARE FUNCTION MyDiskFreeSpaceEx(lpPath AS ASCIIZ,  

    lpFreeToCaller AS QUAD, lpTotalBytes AS QUAD, lpTotalFreeBytes

    AS QUAD) AS LONG

Please review the Example code section below to see this how this modified DECLARE statement is used with the CALL DWORD statement.

See also

CALL, CODEPTR, DECLARE, FUNCTION/END FUNCTION, SUB/END SUB, THREAD CREATE

Example

' Uses the DECLARE statement shown above

DIM hLib    AS DWORD

DIM pAddr   AS DWORD

DIM szDrv   AS ASCIIZ * %MAX_PATH

DIM lResult AS LONG

szDrv = "C:\"

hLib = LoadLibrary("KERNEL32.DLL")

pAddr = GetProcAddress(hLib, "GetDiskFreeSpaceExA")

IF pAddr <> 0 THEN _

  CALL DWORD pAddr USING MyDiskFreeSpaceEx(szDrv, FreeToUserQuota&&, SizeOfDisk&&, TotalFree&&) TO lResult

FreeLibrary hLib