$DECLARE

General Form:

     $DECLARE 'name[(arg1[, arg2] ...) ] [ TO retarg] [=dllspec [, dllspecvar$] ]

Where:
name = an alphanumeric description identifying the function.
arg1[,arg2]... = DLL argument specifiers
retarg = optional return value specifier
dllspec = alpha expression for optional DLL module and entry point
dllspecvar$ = alpha variable contianing additional information for call provided at run-time

The $DECLARE statement provides direct access to native operating system functions. In the case of Windows these functions lie within Dynamic Link Libraries (DLLs) on either the client or the NT server itself. Starting with KCML 6.00 it is also possible to access functions in Unix shared libraries using $DECLARE. On all KCML server platforms, both Unix and NT, it is possible to use $DECLARE to access Windows DLL functions by directing the request through the client. $DECLARE also provides access to some intrinsic functions implemented in either the server or the client. These typically are prefixed with "KCML_".

The $DECLARE statement formally defines the name and location of the remote function in its DLL and its calling interface in terms of the number and type of the expected arguments. Given this information the function can be invoked using a normal GOSUB' call. There are a number of declarations for common intrinsic functions built into KCML and these can be called directly without any explicit $DECLARE declaration.

The dllspec string on the right hand side is an alpha expression which evaluates to a string defining the DLL module name or Unix shared library and the DLL entry point name in the form of "module.function". The module name is optional and can be omitted if the module is one of the standard modules (see below). On NT the module name case is not significant but it is for Unix shared libraries. The extension may be omitted if it is the standard for the platform (.DLL on Windows and .so on Unix). The function name must be spelt correctly in its intended case though it too can be omitted if the function name on the left hand side is spelt in the correct case. For example, the following would be used to define the MessageBox function from the module USER32.EXE:

$DECLARE 'MessageBox(INT(),STR(),STR(),INT())="USER32.MessageBox"

The module name may be omitted from the dllspec if the function is in one of the standard preloaded libraries. The standard libraries are only searched if no relevant module name is provided. If a module name is given that fails to open (e.g. the name is spelt wrong, or the library is not present on the machine), then the standard libraries will not be searched. For what constitutes a relevant module name, see below. The standard library search list for Windows is

USER32.DLL
KERNEL32.DLL
GDI32.DLL
SHELL32.DLL
ADVAPI32.DLL
GSWDLL32.DLL

and for Windows CE it is

coredll.dll
gwes.exe

while for Unix is

libc.so

The function name may also be omitted if it is spelt with the right case in the declaration and KCML is preserving case. Normally KCML preserves in the programs symbol table the case used in a $DECLARE though this can be disabled with a $OPTIONS LIST setting. Thus all that is needed for the above definition is

$DECLARE 'MessageBox(INT(),STR(),STR(),INT())

Windows calling conventions

By default $DECLARE expects to invoke functions formally exported with the tradition Windows DLL convention of __declspec(dllexport) __stdcall where the function is expected to clean up the stack on exit. However it can also invoke functions exported with the standard C calling convention of __declspec(dllexport) without the __stdcall where the caller does the stack cleanup. To specify this convention append an exclamation mark to the function name e.g.

$DECLARE 'MyFunc(INT(),STR()) = "mylib.dll.MyFunc!"

This convention only applies in Windows and is never necessary for Unix. Getting the error S24.62 for a stack mismatch is a good indication that you are using the wrong convention.

Executing on the server

By default $DECLARE functions are executed by the client as this works for both Unix and NT servers. However a program can force a $DECLARE function to execute on the server rather than the client by either prefixing the module name with an asterisk, i.e:

$DECLARE ‘SQLAllocEnv(RETURN INT())="*ODBC32.SQLAllocEnv"

The same effect can be achieved by setting the HEX(80) bit in byte 35 of $OPTIONS RUN for the duration of the call. The former method defines the function as always executing on the server whereas the latter method allows a dual mode function that can execute on either the server or the client. This second method may be more flexible but it requires explicit programming to ensure that the bit is set appropriately for each call. It is also necessary to preserve the other bits in byte 35 when the HEX(80) bit is toggled. This is best done with code of the form

REM force server execution STR($OPTIONS RUN,35,1) = OR HEX(80) ... REM force client execution STR($OPTIONS RUN,35,1) = AND HEX(7F)

New Syntax

As of KCML 7.06 (from build 19017), $DECLARE can accept a new style of string. Instead of indicating specific requirements (e.g. executing on the server) using punctuation marks, text arguments can now be used. These take the form of a semicolon seperated list of parameters, of which there are two types. String parameters expect to be followed by an argument (e.g. fn=messagebox), while boolean parameters don't (e.g. ccall) - the prescence of a boolean parameter indicates true, and they are taken to be false if absent.

A full list of parameters is shown in the table below.

fnstrThe name of the target function e.g. messagebox.
fnsuffixstrSuffix applied to the function name.
libstrThe name of the module containing the target function e.g. user32.
ccallboolCall the function with the standard C calling convention of __declspec(dllexport), rather than the default __declspec(dllexport) __stdcall. Using this calling convention requires the caller to clean up the stack on exit.
serverboolExecute the function on the server rather than the client. Alternatively, use byte 35 of $OPTIONS RUN as above.
aixstrUsed if required module has a different name on AIX. Server side calls only.
unixstrUsed if required module has a different name on Unix. Server side calls only.
linuxstrUsed if required module has a different name on Linux. Server side calls only. If the module name specified with the linux keyword can not be found, LD_LIBRARY_PATH, /usr/lib and /usr/local/lib are searched for libraries with version numbers appended to the requested name. For example, if linux=libcurl.so; is specified and libcurl.so can not be found, but libcurl.so.3 can be, this will be used instead. This only applies when the name specified ends in .so.
linux_x86strSame as linux, but is tested first when KCML is the 32-bit x86 linux version. Introduced in KCML 7.21.
linux_x64strSame as linux, but is tested first when KCML is the 64-bit x64 linux version. Introduced in KCML 7.21.
windowsstrUsed if required module has a different name on Windows. Server side calls only.
signedlibraryboolUse this keyword to indicate that the signature of the library containing the $DECLARE must be checked. For a more detailed discussion, see below.

At minimum, the fn parameter is required. Parameters must be in lower case (although their values can be upper case if required). lib may be neglected as long as the module is one of those searched be default (see list above).

The platform specific keywords are used to set the module name in cases where it differs from platform to platform. Any number of these can be included (from none to all of them). On a given platform, the platform specific keyword is used if present. If not, AIX and Linux machines will look for any library name specified with the unix keyword. All platforms will try and use the module name specified with lib if nothing previously succeeded. Because of this ordering, the two example $DECLAREs for curl_easy_setopt (see below) will behave identically.

The standard libraries listed above are only searched if no relevant module name is provided. A relevant module name is one that applies to the platform the program is executing on. If the lib keyword is used, this is relevant anywhere and will prevent standard libraries being searched on any platform. Specifying a module name using the old style syntax will have the same effect. However, if the only module name used is specified with the windows keyword, this will stop the standard libraries being searched when executing on a windows machine, but not on a Unix machine, because no module name relevant to a Unix machine has been provided.

From KCML 7.17 an additional string variable can also be used to specify parameters when using the new syntax. This was originally designed for the libicu library which includes the version number of the library in all function names. By setting function="ucnv_open"; in the $DECLARE string and fnsuffix=_4_2; in the variable the function ucnv_open_4_2 will be called. For example

DIM libicu$="fnserver=_4_2;"
$DECLARE 'ucnv_open(STR(),RETURN INT()) TO PTR() ="server; fn=ucnv_open; lib=libicui18n;", libicu$

The application will still need to determine the values for fnserver which could vary depending on versions of the library installed.

A couple of examples are shown below.

$DECLARE 'MyFunc(INT(),STR()) = "lib=mylib.dll; fn=MyFunc; server;"
$DECLARE 'GetWindowText(INT(),RETURN STR(),INT())="fn=GetWindowText"
$DECLARE 'curl_easy_setopt(INT(),INT(),...)="fn=curl_easy_setopt; windows=libcurl; unix=libcurl; aix=libcurl.a(libcurl.so.4); server;"
$DECLARE 'curl_easy_setopt(INT(),INT(),...)="fn=curl_easy_setopt; lib=libcurl; aix=libcurl.a(libcurl.so.4); server;"

SignedLibrary

The signedlibrary keyword listed above merits further discussion. It is intended to be used for $DECLARE statements located within KCML libraries compiled by kc6/kmake. If the keyword is used in a $DECLARE in any other location, it will error. It can also only be used server side. Attempting to use it on a client side $DECLARE will produce an error. Including this keyword means that the function call should only succeed if the library has been signed in a valid way.

When the signedlibrary keyword is present, and used in an appropriate place, it causes a LibInfo struct (see below) to be generated and a pointer to it passed as the first argument to the external function being called. A LibInfo struct contains the starting address of the KCML library within shared memory, the length of the code within the library, and the size of the whole library file. The external function being called must expect a LibInfo pointer as its first argument. This means that the declaration of the external function must have one more argument than listed in the $DECLARE statement. An example is shown below.

Using the signedlibrary keyword on a $DECLARE within a library will cause it to be marked as private, so it can only be called by functions within the library. It will also cause all future $DECLAREs within the library to be marked as private.

A LibInfo struct is defined as follows:

struct LibInfo {
	void * start;
	size_t libsize;
	size_t filesize;
}

$DECLARE statement:

$DECLARE 'Func(INT(),INT()) = "lib=mylib.dll; fn=Func; server; signedlibrary"

Corresponding C funtion declaration:

void Func(LibInfo * info, int a, int b)

It is the responsibility of the called function to ensure the signature is valid. The LibInfo structure provides enough information to locate the starting address and length of the signature and test it appropriately.

Controlling how arguments are passed

If the function expects arguments or returns a value then KCML must be informed of their type and how to pass them to the DLL. This is done with arguments to $DECLARE that define the type of the corresponding parameter in the GOSUB’ call using a number of keywords viz.

INT(2) Short (2 byte) integer.
INT(-2) Signed short (2 byte) integer.
INT(4) Long (4 byte) integer.
INT(-4) Signed long (4 byte) integer.
INT() Natural machine integer (2 bytes for 3.x, 4 bytes for NT)
INT(-) Signed natural machine integer (2 bytes for 3.x, 4 bytes for NT)
PTR() For server-side $DECLARE represents a pointer parameter or result. When using a 64-bit KCML this has to be used for pointers and this is the correct type to use on all platforms for pointers. However, on 32-bit KCML INT() will suffice and existing declarations may need to be converted to PTR() to run on 64-bit KCML.
NUM() Double precision floating point.
STR() Null terminated string (trailing blanks are automatically stripped).
DIM() Structure (the parameter is passed in its entirety).
DIM(n) Structure (the structure size must be greater than or equal to n).
'prototype Specify a prototype of a callback function for server-side $DECLARE.

Prior to KCML6 there was also a STR(DIM) keyword which could be used to pass a KCML string array as an array of string pointers. Because of internal changes in KCML this functionality is no longer available and will result in an S24 error at runtime. Such strings should now be passed as a structure.

By default, arguments are passed by value to the remote function so that the values of arguments are not altered. Two prefixes are available for other behaviour:

RETURN No value is passed to the subroutine, but data is returned. This prefix may not be used on integral or floating-point types or with string arrays.
TO RETURN Data is passed to the remote function and returned from it. This prefix may not be used on integral or floating-point types or with string arrays.

If the argument is a pointer to an int or double then it is necessary to prefix the variable in the call with the BYREF or RETURN keyword to tell KCML to push the address before calling the function. This is only necessary for pointers to numbers as strings and structures are always passed as pointers whereas the default action for numbers is to pass the value. Note it is still necessary to specify RETURN or TO RETURN for this argument in the $DECLARE definition if the object pointer to will be changed. Thus to open a registry key:

$DECLARE 'RegOpenKey(INT(), STR(), RETURN INT()) 'RegOpenKey(HKEY_LOCAL_MACHINE, key$, BYREF hkey)

Where strings or structures are expected by the DLL function it is permissable to pass 0 to indicate a NULL pointer.

If a function result is specified, it must be of numeric type. If none is specified, then int is assumed (the majority of functions are of type int). It is possible to use either GOSUB 'name or to use 'name() from within an expression to call a function, the return value being ignored in the case of a GOSUB'.

. To be noted that, for KCML 7.03, the function result allows string type, the int type is still set as default.

$DECLARE is interpreted only at resolve time when an entry for the subroutine name is placed in the symbol table. A subsequent LOAD statement will remove the symbol. $DECLARE statements in the currently selected global partition make the subroutines names available to the foreground partition according to the normal rules for finding subroutines in globals.

If the DLL function cannot be found at runtime within any of the search DLLs a P38 error will result. Incorrect or invalid arguments give an S24 error.

For more information about $DECLARE see $DECLARE syntax in this manual. For information about DLLs and the Windows API see the Microsoft Platform SDK documentation or the Microsoft Developer network CD. Details of special DLL functions exported by Kclient may be found in the Developer Information section of the Kclient manual.

Callbacks in server-side $DECLARE:

Some server-side $DECLARE functions can take a reference to a subroutine that can be called during the function operation. For instance, the curl library can call a function when the header or part of the body is received. From KCML 7.17 it is possible to specifiy the name of a prototype in the $DECLARE declaration and then pass BYREF the name of thge DEFSUB to be called as a callback. Currently only a limited number of parameters are supported - plain numbers are passed as integers and strings can be passed BYREF. The string can be typed to either a record or the length can be specified to be the value of one of the other parameters. So for the curl body callback:

PUBLIC PROTOTYPE 'cb(BYREF a$, len, count, user) $DECLARE 'curl_easy_setopt_cb(ptr(), INT(), 'cb) = "*libcurl.curl_easy_setopt" 'curl_easy_setopt_cb(curl_handle, _CURLOPT_WRITEFUNCTION, BYREF 'Test) PRIVATE CALLBACK 'Test(BYREF a$count, len, count, user)

Example:

KCML can retrieve the current title and replace it with one of its own using the GetWindowText() and SetWindowText() functions in the Windows API:

$DECLARE 'GetWindowText(INT(),RETURN STR(),INT())="GetWindowText"
$DECLARE 'SetWindowText(INT(),STR())="SetWindowText"
$DECLARE 'GetFocus()="GetFocus"
$DECLARE 'GetParent(INT())="GetParent"
DIM old$32,hwnd
hwnd = 'GetParent('GetFocus())
'GetWindowText(hwnd,old$,len(str(old$)))
'SetWindowText(hwnd,"New title")

The use of 'GetParent() is necessary here as both KCML uses multiple overlapping windows and the window with focus is a child of the parent which owns the title bar.

Performance considerations:

To get the best performance out of calls to Windows routines in the client, byte 35 of the $OPTIONS RUN system variable can be set to change the behaviour of calls to remote functions. This byte uses a bit pattern mechanism to set the various options. The available bits are as follows:

HEX(00) Always wait for a reply from the routine, this is the default case.
HEX(01) Do not wait for a reply from the routine. With this bit set the routine must be called from a GOSUB’ statement and not from within an expression.

For example, to instruct KCML to not wait for a reply from Windows remote functions where a return value is not required you could execute the following:

STR($OPTIONS RUN,35,1) = OR HEX(01)
'SetWindowText("Hello World!")
STR($OPTIONS RUN,35,1) = AND HEX(FE)

This allows improved buffering and is considerably faster. However, you cannot use this asynchronous mode if a return value is necessary and the following program result in an S24 error at the value of title$ must be returned:

STR($OPTIONS RUN,35,1) = OR HEX(01)
'GetWindowText(hwnd, title$)
STR($OPTIONS RUN,35,1) = AND HEX(FE)

Syntax examples:

$DECLARE 'ourname=".GetParent"
$DECLARE 'GetWindowRect(INT(),RETURN DIM())
$DECLARE 'DisplayTif(STR())="TIFF.DisplayFile"
$DECLARE 'WinHelp(INT(), STR(), INT(),INT(4))
$DECLARE 'GetComputerName(RETURN STR(), TO RETURN INT())
$DECLARE 'HtmlHelp(INT(),STR(),INT(),INT())="hhctrl.ocx.HtmlHelp"
$DECLARE 'chmod(STR(), INT())="*"

See also:

GOSUB',
BYREF,
$DECLARE syntax,
'MessageBox,
Built in client $DECLARE declarations Built in server $DECLARE declarations The $DECLARE usage on libcurl library