Calling external DLL libraries from WME scripts

(Note: this functionality is aimed at advanced users with programming experience)

Declaration

WME allows your scripts to call external functions from DLL libraries. This way you can either call Win32 API functions or write your own extension DLL libraries (plug-ins).

To use an external function from a script you must "declare" it first. The declaration syntax is:

external "dll_name" [calling_convention] [return_type] function_name ( [parameters] );

(components enclosed in square brackets are optional)

dll_name - The name of the DLL library, enclosed in quotes. It can also contain a relative or absolute path (not recommended). Please refer to the WIN32 API documentation for the information on how Windows searches for a DLL library.

calling_convention - This specifies which calling convention does the specified function use. It can be either stdcall or cdecl. If you don't specify a calling convention, WME uses stdcall by default. The stdcall convention is used by Win32 API, while your custom DLL library written in C/C++ will probably use cdecl convention. Please refer to you compiler documentation for more information about calling conventions.

return_type - Specifies a data type of the value returned by the function. If the function doesn't return a value (its return type is void) or if you don't want to use its return value, you don't need to specify any return type. See below for the list of supported data types.

function_name - This is the name of the function as exported by the DLL library. It must match the name exactly otherwise WME will fail to call the function.
 - Note 1: If you are using C++ to write your DLL, be sure to declare your function as extern "C" otherwise your DLL will export a "decorated" C++ name.
 - Note 2: If you are declaring a Win32 API function, remember that all functions working with strings are provided in two variations, ANSI and Unicode. Thus Windows DLLs export two functions: FunctionNameA (for ANSI) and FunctionNameW (for Unicode). WME currently doesn't support Unicode, therefore you must use the "A" (ANSI) functions.

parameters - This is the list of parameters delimited by commas. Parameters are defined by their types followed (optionally) by their names. You don't need to specify parameter names, they are not used for anything. See below for the list of supported data types.

Data types

WME only allows you to pass primitive types to the DLL functions. While WME script uses untyped variables, they are converted to a requested primitive data type when passed to a DLL function.

Supported data types are:

int 32 bit integer number, equivalent to int, long or DWORD
bool logical value, true or false
byte 8 bit value, equivalent to BYTE or unsigned char
string array of 8 bit characters, equivalent to char* or LPSTR or LPCSTR (see below)
float 32 bit floating-point value, equivalent to float of FLOAT
double 64 bit floating-point value, equivalent to double
membuffer 32 bit memory pointer value; it must be represented by WME's MemBuffer object

You can also use short and long data types, they are synonymous to int.

Strings in WME scripts have variable length, dependent on the value they currently contain. But some DLL functions are able to return you a string value into a provided buffer of a fixed size. In this case you can't use ordinary string variables, but you must construct a String object and specify the requested buffer size:

var MyFixedString = new String(256); // this will create a 255 characters long string buffer

Note: You can query string buffer size using the string object's Capacity attribute.

DLL internal data

When WME calls an external DLL function, it only loads the DLL immediately before the call and unloads the DLL immediately after. Therefore the DLL cannot hold any internal data between subsequent calls to its functions, because they would be lost after unloading the library. This can be overcome by explicitly loading your DLL from a script using the LoadLibraryA API function and keeping it loaded all the time, but you must remember that the player can save/load game at anytime, therefore your loaded DLL can get unloaded anyway. You can use game's BeforeSave and AfterLoad events to store/restore private data before saving and after loading game.

Passing structures to/from DLL functions

Some of the API functions and possibly your own DLL functions may require passing structures to them. WME provides a built-in MemBuffer object type for this purpose. By creating a MemBuffer object in a script you reserve a memory space which can be then passed to a DLL function. MemBuffer objects provide a number of methods for writing/reading values of different types to/from them thus simulating the "struct" keyword known from some other programming languages. See the MemBuffer scripting reference for more details.

A word of warning

By using external DLL functions, you are skating on a thin ice. While normally WME tries to catch developer's mistakes to prevent fatal errors and crashes, using the external functions you can crash the engine quite easily. Keep this in mind and be very careful about the passed parameters, their order and about the calling convention.

Example

The following example demonstrates declaring and calling several Win32 API functions from a WME script.

// declaration
external "user32.dll" MessageBeep(int uType);
external "kernel32.dll" long GetTempPathA(int nBufferLength, string lpBuffer);
external "kernel32.dll" int LoadLibraryA(string);
external "kernel32.dll" FreeLibrary(int);

function MyDllTest()
{
  // MessageBeep plays a sound
  MessageBeep(0);
  
  // GetTempPath returns the Windows' temporary folder
  var TempPath = new String(256);
  GetTempPathA(TempPath.Capacity, TempPath);
  Game.Msg("Windows temporary folder is: " + TempPath);

  // load our custom DLL and unload it immediately
  var hDll = LoadLibraryA("MyDll.dll");
  FreeLibrary(hDll);
}