Library Calling Conventions in BCX

C_DECLARE statement

Purpose:

The BCX statement C_DECLARE is used to expose a SUB or FUNCTION procedure that has been exported from an external Dynamic Link Library (DLL) or to expose a procedure that has been compiled and placed in an .obj file or library.

Syntax 1:

C_DECLARE [ SUB | FUNCTION ] ProcName(Paramlist)

Parameters:

  • Data type: Arguments
    Paramlist follows the same rules as parameters in a standard SUB or FUNCTION declaration. The parameters used in the C_DECLARE Paramlist are dummies but must communicate the data type, either by using a data type sigil ( $ % ! # ) or by using the AS DataType clause.

Remarks:

_cdecl is the default C calling convention. The stack is cleaned up by the calling function, so vararg functions can be used. Arguments are pushed on the stack from right to left.

C_DECLARE adds the prototypes to the C code, plus it allows you to declare the function data type and its arguments using BASIC syntax, instead of C.

C_DECLARE is not compatible with LOADLIBRARY. If LOADLIBRARY is to be used then DECLARE must be used as the declaration specification for the functions in the DLL.

For example ...

C_DECLARE FUNCTION FOO (A AS UINT) AS INTEGER

translates to C code

int __cdecl FOO(UINT);

which will not work with LOADLIBRARY.

Instead, you would need to inline:

int(*FOO)(UINT);

Optional arguments are allowed, although not all compilers support optional arguments for syntax 2. The OPTIONAL keyword is not needed and should not be used.

C_DECLARE supports variable arguments (...).

The C_DECLARE statement can be used in the development of .obj files, however, that can be difficult to implement. Every time a different compiler is used, there is a whole new puzzle to sort out. And linking .obj files created with disparate compiler systems is usually problematic. Here is an example using the Microsoft compiler for implementation. It has three parts:

  1. a standalone function that will occupy its own .obj file.
  2. a test program that C_DECLARE's and later calls the imported (linked) function
  3. a batch file to build the example.

Copy and save as Add_Two_Ints.bas, the code for the standalone function.

$NOMAIN

FUNCTION Add_Two_Ints (A, B) AS INTEGER EXPORT
 FUNCTION = A + B
END FUNCTION

Copy and save the Test.bas snippet

C_DECLARE FUNCTION Add_Two_Ints (A, B) AS INTEGER
PRINT Add_Two_Ints(3, 5)
PAUSE

And finally, copy and save as Build.bat, the following batch file to compile the project with a Microsoft VS140 version compiler.

@CLS

:***********************************************
: Create two C files using BCX
:***********************************************

@BC Test
@BC Add_Two_Ints

:***********************************************
: Adjust the following for your compiler system
:***********************************************

@CALL "%VS140COMNTOOLS%..\..\VC\vcvarsall.bat" X86

:***********************************************
: Compile steps
: NOTE:   /Gd  use __cdecl calling convention
:***********************************************

@cl.exe /c /Gd Add_Two_Ints.c
@cl.exe /c /Gd Test.c

:***********************************************
: Link step
:***********************************************

@link.exe Test.obj Add_Two_Ints.obj /OUT:Test.exe /RELEASE /MACHINE:X86 /SUBSYSTEM:CONSOLE

@ECHO All Done!

DECLARE statement

Purpose:

The BCX statement DECLARE provides a procedure declaration specification that allows the function to be used with the _stdcall calling convention.

Syntax 1:

DECLARE [ SUB | FUNCTION ] ProcName(Paramlist)

Syntax 2:

DECLARE [ SUB | FUNCTION ] ProcName AS STRING _
                            LIB "DllName.Dll" _
                             ALIAS "ProcName" _
                                  (Paramlist)

Remarks:

Optional arguments are allowed, although not all compilers support optional arguments for syntax 2. The OPTIONAL keyword is not needed and should not be used.

Once you C_DECLARE or DECLARE a SUB or FUNCTION inside your program, you can call it from any other SUB or FUNCTION. In a CONSOLE app, you can place DECLARE statements anywhere in your source program.

In a GUI program, you must place it a SUB or FUNCTION so that the initialization code has some place valid to run. The smart place to put your DECLARE in a GUI is inside your WinMain FUNCTION, or if you are using the simplified BCX_XXXXX GUI commands, inside the SUB FORMLOAD .

In a DLL app, you should place the DECLARE inside DllMain.

Example 1:

Here is a complete example which calls a DLL function which returns a string. The example is in three sections, the DLL, a program to call the function from the DLL and a batch file to compile the programs.

The first section is the BCX DLL code. Cut and save this as RESPOND.BAS

$DLL STDCALL

FUNCTION Respond (Buf$) EXPORT
 Buf$ = Buf$ & " The Eagle has landed"
 FUNCTION = LEN(Buf$)
END FUNCTION

The second section is the program which will call the function in the DLL. Cut and save this as TESTDLL.BAS

DIM a$, l

DECLARE FUNCTION Respond LIB "respond.dll" DECLARE "Respond"(foo$) AS INTEGER

a$ = "Boy Howdy"
l = LEN(a$)
? l;" ";a$

l = Respond(a$)
? l;" ";a$

PAUSE

Here, in the third section, is a batch file to translate, compile and link the RESPOND.BAS and TESTDLL.bas.

If Pelles C is used as the compiler, cut and save the following as BUILD.BAT

BC Respond
POCC /Ze /Gn /Zx Respond.c
POLINK /dll Respond.obj
BC TestDLL
POCC /Gd /Ze /Zx TestDLL.c
POLINK TestDLL.obj

$LIBERROR directive

Purpose:

$LIBERROR specifies the file to which errors regarding library and function failures are to be logged.

Syntax:

$LIBERROR "C:\dev\BCX\LibError.log"

Parameters:

  • Data type: STRING
    LibError.log is a string literal containing the path to, and the name of, the file to which errors regarding library and function failures are to be logged.

Example 1:

This example is similar to the one above with an addition of the $LIBERROR directive and a change in TESTLIBERROR.bas from the name respond.dll to a nonexistent noresponse.dll. When compiled and run this example will write the errors to the LibError.log. The example is in three sections, the DLL, a program to call the function from the DLL and a batch file to compile the programs.

The first section is the BCX DLL code. Cut and save this as RESPONDELIBERROR.BAS

$DLL STDCALL

FUNCTION Respond (Buf$) EXPORT
 Buf$ = Buf$ & " The Eagle has landed"
 FUNCTION = LEN(Buf$)
END FUNCTION

The second section is the program which will call the function in the DLL. Cut and save this as TESTLIBERROR.BAS. Be sure to modify the path following the $LIBERROR directive to suit your setup.

$LIBERROR "C:\t\LibError.log"

DIM a$, l

DECLARE FUNCTION Respond LIB "noresponse.dll" DECLARE "Respond"(foo$) AS INTEGER

a$ = "Boy Howdy"
l = LEN(a$)
? l;" ";a$

l = Respond(a$)
? l;" ";a$

Here, in the third section, is a batch file to translate, compile and link the RESPONDE.bas and TESTLIBERROR.bas.

If Pelles C is used as the compiler, cut and save the following as BUILDLIBERROR.BAT. Be sure to modify the path to the Povars32.bat file to suit your setup.

CALL Povars32.bat
BC RESPONDELIBERROR
POCC /Ze /Gn /Zx RESPONDELIBERROR.c
POLINK /dll RESPONDELIBERROR.obj
BC TESTLIBERROR
POCC /Gd /Ze /Zx TESTLIBERROR.c
POLINK TESTLIBERROR.obj

Run BUILDLIBERROR.BAT. When compiled, run TESTLIBERROR. The output should be similar to the following

[03/31/15 11:04:14] Failed to load responde.dll
[03/31/15 11:04:14] Failed to find process Respond

LIB statement

Purpose:

BCX provides a universal method for calling both _stdcall and _cdecl 32 bit DLL functions irrespective of the differences between the two calling conventions.

Syntax:

[RetVal =] DLLFunction(LIB "DLLName[.dll]" [, Parameters, ...])

Parameters:

  • RetVal [OPTIONAL] return value from function being called.
  • DLLFunction Name of the function being called. The name should appear as it is exported from the DLL according to its specifications. Explicit ALIASes as used with DECLARE constructs are not permitted.
  • DLLName Name of the DLL containing the function being called(the extension part is optional). Contrary to DECLARE constructs which load DLLs into memory the moment DLLFunction is declared, this method loads them into memory only if the function is actually called.
  • Parameters [OPTIONAL] parameter list for function being called. Nested calls to other DLL functions may also be used directly as parameters (see Example 2 below).

Remarks:

Unlike the conventional DECLARE and C_DECLARE statements, the dynamic DLL function calling engine provides the following advantages:

To be able to ensure this non-language-specific flexibility, the DLLs being called should meet the following reasonable requirements:

The overwhelming majority of commonly used Windows API, VisualBasic, Delphi, and third-party DLLs are based on these simple rules.

👉 While the DECLARE and C_DECLARE convention is still supported by BCX for backward compatibility reasons, both conventions should not be used simultaneously in the same code because this will render the dynacall engine's memory management facilities ineffective.

Example 1:

MessageBox(LIB "user32", NULL, "Your Msg", "Your Title", MB_OK)

BCX_DYNACALL procedure

Purpose:

BCX_DYNACALL is a user accessible runtime variant of the of the BCX translator internally used BCX_DynaCall function. This user accessible variant provides, for the user, an easier runtime execution of functions not known at compile time.

Syntax:

[RetVal =] BCX_DYNACALL(DLL_Name AS STRING, _
                     DLLFunction AS STRING, _
                   NumberOFArgs AS INTEGER, _
                                  ArgArray)

Parameters:

  • RetVal [OPTIONAL] return value from function being called.
  • Data type: STRING
    DLL_Name Name of the DLL containing the function being called (the extension part is optional). Contrary to the DECLARE constructs which load DLLs into memory at the moment that the DLLFunction is declared, this method loads them into memory only if the function is actually called.
  • Data type: STRING
    DLLFunction Name of the function being called. The name should appear as it is exported from the DLL according to its specifications. Explicit ALIASes as used with DECLARE constructs are not permitted.
  • Data type: INTEGER
    NumberOFParams Number of DLLFunction parameters.
  • Data type: Identifier
    ArgArray Array to hold the arguments of the DLLFunction parameters. This array can be defined as equal or greater than the maximum number of arguments that a function may use.

Remarks:

This variation is mainly to allow a user's program to have the ability to setup functions at runtime. For instance, the functions could be read from a configuration file. The easiest way is just to define an array that is equal or greater than the maximum number of arguments that a function may use. The arguments are then, and must be, cast as integer.

Example:

DIM AnyType[4] AS INT_PTR    ' INT_PTR works for 32-bit and 64-bit executables 
AnyType[0] = (INT_PTR) NULL
AnyType[1] = (INT_PTR) "Your Title"
AnyType[2] = (INT_PTR) "Your Msg"
AnyType[3] = (INT_PTR) MB_OK
BCX_DYNACALL("user32", "MessageBox", 4, AnyType)

STDCALL statement

Purpose:

STDCALL is a reserved BCX keyword used in two different contexts.

Remarks:

Win32 API functions and user-defined callbacks use the _stdcall calling convention. _stdcall is used as well by Visual BASIC, and Delphi. When _stdcall is used, the stack is cleaned up by called function, so compiler makes vararg functions cdecl, and a function prototype is required. Arguments pushed on stack right to left.

Syntax 1:

$DLL STDCALL

Syntax 2:

SUB Foo (Bloof AS DOUBLE) STDCALL