DynamicWrapperX v1.0

Author: Yuri Popov.
Licence: freeware.

Contents

Introduction

DynamicWrapperX is an ActiveX component (COM server) inspired by DynamicWrapper, which I wrote as an attempt to better implement the idea. It allows to call functions exported by DLL libraries, in particular Windows API functions, from scripts in JScript and VBScript. This component is not a modification of the original DynamicWrapper, it was written from scratch in the GoAsm assembly language. So far I have tested it under Windows XP SP1 and Windows 98 SE.

New features and changes:

---------------------------------------------------------------------------

P.S. Though I have tried to test DynamicWrapperX in various situations, I can’t ensure its faultless work. Use it at your own risk.

Top

Registration in the system

Register the component in one of these two ways:
regsvr32.exe path-to-the-component\dynwrapx.dll - for all users.
regsvr32.exe /i path-to-the-component\dynwrapx.dll - for the current user.

If dynwrapx.dll is located in the System32, System, Windows directory, the current directory or one of those listed in the PATH environment variable, the path is optional. After registering dynwrapx.dll for the current user, the DynamicWrapperX object will only be available to this user.

Unregister the component as follows:
regsvr32.exe /u path-to-the-component\dynwrapx.dll - for all users.
regsvr32.exe /u /i path-to-the-component\dynwrapx.dll - for the current user.

On Windows 98 you will possibly have to specify the full path to regsvr32.exe (the System folder). Also, registration for the current user makes no sense here because the component won’t work.

Top

Built-in methods summary

[1]  Register( DllName, FuncName [, i=ParamTypes] [, r=RetValType] ) - registers a function from a dll as an object method.
[2]  RegisterCallback( FuncRef [, i=ParamTypes] [, r=RetValType] ) - registers a script function as a callback function.
[3]  NumGet( Address [, Offset] [, Type] ) - reads a number from memory.
[4]  NumPut( Var, Address [, Offset] [, Type] ) - writes a number to memory.
[5]  StrPtr( Var [, Type] ) - returns a pointer to a string, in a numerical variable.
[6]  StrGet( Address [, Type] ) - reads the string at a specified address.
[7]  Space( Count [, Char] ) - creates a string of a specified length.

Top

Register method

This method registers a function exported by the specified dll as an object method. After that you can call the function in the same way as the built-in methods, i.e. through its name after a point. The syntax of the Register method hasn’t changed much compared with the original DynamicWrapper. The flag parameter (f=...) has been removed and is ignored if present. The i= prefix still denotes the function's parameters and r= denotes its return value.

JScript

DX = new ActiveXObject("DynamicWrapperX");                  // Create an object instance.
DX.Register("user32.dll", "MessageBoxW", "i=hwwu", "r=l");  // Register a dll function.
res = DX.MessageBoxW(0, "Hello, world!", "Test", 4);        // Call the function.

VBScript

Set DX = CreateObject("DynamicWrapperX")                    ' Create an object instance.
DX.Register "user32.dll", "MessageBoxW", "i=hwwu", "r=l"    ' Register a dll function.
res = DX.MessageBoxW(0, "Hello, world!", "Test", 4)         ' Call the function.

The library name without a path causes searching by name. The search starts with the libraries already loaded for the process and continues on disk. Here is the default disk search order under Windows XP:

  1. The application's directory (in this case the application is either wscript.exe or cscript.exe).
  2. The current directory.
  3. System32.
  4. System.
  5. Windows.
  6. The directories listed in the PATH environment variable.

If the library resides in a file with the 'dll' extension, this extension is optional, i.e. in the above example we could have written just "user32". If the file that contains the library has no extension, you should put a point after the name. For example, "mylib."

The function name may vary depending on whether you need the Unicode version of a function or its ANSI counterpart. This normally applies only to the functions having string parameters or return values. For example, the function named MessageBox in Windows API documentation actually exists in two variants: MessageBoxW (for Unicode strings) and MessageBoxA (for ANSI ones). I've left the traditional search logic unchanged. That is, if you specified MessageBox and such a function hasn't been found in user32.dll, the search will be automatically repeated for MessageBoxA. The names of Unicode functions should be specified accurately, putting 'W' at the end of them.

The parameter list can be omitted only if the function takes no parameters (by design).

The return value can be omitted whenever you don't need it, regardless of whether the function returns any value or not.

JScript

DX = new ActiveXObject("DynamicWrapperX");
DX.Register("kernel32", "GetCommandLine", "r=s");      // This function has no parameters.
CmdLine = DX.GetCommandLine();                         // The command that started the process.
WScript.Echo(CmdLine);

VBScript

Set DX = CreateObject("DynamicWrapperX")
DX.Register "kernel32", "Beep", "i=uu"      ' Beep returns a value but it is not needed.
DX.Beep 800, 1000                           ' A sound through the PC speaker (beeper).

Types of input parameters and return values

l - signed 32-bit integer - LONG, INT, LPARAM, LRESULT, etc, value range: -2147483648 ... 2147483647;
u - unsigned 32-bit integer - ULONG, UINT, DWORD, WPARAM, ... , value range: 0 ... 4294967295;
h - handle - HANDLE, HWND, HMODULE, HINSTANCE, HICON, ... , value range: -2147483648 ... 4294967295;
p - pointer; for numbers it is the same as 'u' but can also be used to pass an object (IDispatch *) or a string;
n - signed 16-bit integer - SHORT, value range: -32768 ... 32767;
t - unsigned 16-bit integer - USHORT, WORD, WCHAR, OLECHAR, ... , value range: 0 ... 65535;
c - signed 8-bit integer - CHAR, value range: -128 ... 127;
b - unsigned 8-bit integer - UCHAR, BYTE, ... , value range: 0 ... 255;
f - single-precision floating-point number (32 bits) - FLOAT;
d - double-precision floating-point number (64 bits) - DOUBLE;
w - Unicode string - BSTR, LPWSTR, LPOLESTR, OLECHAR *, WCHAR *, ...;
s - ANSI/Windows string (default codepage) - LPSTR, LPCSTR, CHAR *, ...;
z - OEM/DOS string (default codepage) - LPSTR, LPCSTR, CHAR *, ...

Top

Output parameters

L - pointer to the specified number (its address in memory) - LONG *, LPLONG, etc;
H - same as above - HANDLE *, PHANDLE, LPHANDLE, ...;
U - same as above - ULONG *, LPDWORD, ...;
P - same as above;
N - same as above - SHORT *;
T - same as above - USHORT *, LPWORD, WCHAR *, OLECHAR *, ...;
C - same as above - CHAR *, ...;
B - same as above - UCHAR *, LPBYTE, ...;
F - same as above - FLOAT *, PFLOAT;
D - same as above - DOUBLE *, PDOUBLE;
W - output Unicode string;
S - output ANSI string;
Z - output OEM string.

Output parameters are ones whose value is to be changed by the called function. In such a case you need to pass the API function a pointer to a number rather than the number itself. You are not required to somehow obtain the pointer before passing it, it is done automatically for parameters declared with capital letters. Just pass an ordinary numeric variable, and the API function will receive a pointer to the number which this variable holds. On output strings see About strings below.

Note for JScript: to use a number as output, declare it using the "new" operator. For example:
a = new Number(0);

Doing the same to strings (either input or output) is not needed and, moreover, deprecated because the String object will contain a pointer not to an ordinary string but to a "versioned stream", which is something I don't yet understand.

Top

About strings

Strings in JScript and VBScript are of the BSTR type. These are Unicode strings, i.e. the code of each character takes 2 bytes. The last character is followed by a terminator (two zero bytes), and the first character is preceded by a 4-byte number that contains the length of the string in bytes (excluding zero bytes at the end of the string). A script string variable holds a pointer to such a string, which is the address of the string's first character (i.e. the bytes that contain the string length remain "behind-the-scenes").

To pass a string you have the following three ways:

1) An input string: w, s, z. Strings passed as s or z are copied and converted into the relevant encoding. The API function receives a pointer to such a copy. As soon as the function returns, the memory used for the copy is released. In the case of w the function receives a pointer to the original (Unicode) string.

2) An output string: W, S, Z. Here with all three types you pass a pointer to the original location of the string. S and Z strings are previously converted into the relevant encoding but without copying. When the function returns, the contents of S and Z strings are converted back into Unicode and their length is measured. W strings only have their length measured. The length (in bytes) is recorded in front of the string. The last operation is needed to avoid glitches while, for example, this and other strings will be concatenated further in the script. Since the output string, like all output parameters, is intended to be written to by the called function, make sure its length is sufficient.

3) A pointer: p. This is the simplest way. Here you pass a pointer to the original string, without conversion. After the function returns, no conversion or length correction is made. So if the function has written something to this string, that data will remain there unchanged.

This might look the same as w, but there is a difference. Parameters declared as p accept not only string variables but also numeric ones.

Returning a string as p, we get a numeric variable holding a pointer to the string returned by the API function. Returning a string as w, s or z, we get a string variable holding a pointer to a copy of the string returned by the API function. Strings returned as s and z are copied with conversion. The original strings are currently not freed. This is because I am uncertain whether it is safe to do so. If you are concerned about memory leak caused by that, consider using the p type for strings returned by API functions wherever possible.

API functions that take string arguments typically exist in two variants - for example, MessageBoxA and MessageBoxW. It appears more reasonable in scripts to use the Unicode versions (those having 'W' at the end of the name) because this way you avoid conversion to and from Unicode.

As for Windows 98, both script engines use Unicode strings there as well, but not all API functions exported with the 'W' ending really work. Many of them are just stubs and do nothing but return 0. For example, MessageBoxW works as expected but lstrcmpiW does not though it is present in kernel32.

Top

RegisterCallback method

This method takes a reference to a script function and transforms it into a pointer that can be passed to an API function. Then that API function can use this pointer to call the script function. EnumWindows, for example, requires such a pointer to a callback procedure for its work. For each window that it finds, it calls the callback procedure, passing it the window handle. Then if the callback procedure returns 1, enumeration continues, and if 0, it stops.

A script function reference by itself can't serve this purpose because functions in JScript and VBScript are objects and their references are pointers to IDispatch interfaces. So the reference is passed to RegisterCallback, and the API function receives a pointer to one of the intermediary procedures inside dynwrapx.dll, which will translate calls to the script function and transfer its return values back to the API function. There are 16 of such procedures in dynwrapx.dll, that is there can be no more than 16 callback functions in a script.

In JScript the name of a function (without parentheses) will serve as its reference, and in VBScript you will have to use GetRef beforehand. In addition to the function's reference, you may have to specify the types of its parameters (if any) and its return value - just as with the Register method (but only small letters can be used).

JScript

DX = new ActiveXObject("DynamicWrapperX");

DX.Register("user32", "EnumWindows",    "i=pl");
DX.Register("user32", "GetWindowTextW", "i=hWl");          // Unicode variant.
// DX.Register("user32", "GetWindowText", "i=hSl");        // ANSI variant.

pCbkFunc = DX.RegisterCallback(CbkEnumWin, "i=hl", "r=l"); // Register CbkEnumWin
                                                           // as a callback procedure
                                                           // and get its pointer.
n=0, m=0, WinList="";

Title = DX.Space(256);              // Buffer for the window titles (an output string).

DX.EnumWindows(pCbkFunc, 0);        // Call EnumWindows and pass it the pointer
                                    // to the callback procedure.

WScript.Echo("Windows in total: " + m + "\nWith a title: " + n + "\n\n" + WinList);


// ............... The callback function itself ....................

function CbkEnumWin(hwnd, lparam)
{
  DX.GetWindowTextW(hwnd, Title, 256);
  // DX.GetWindowText(hwnd, Title, 256);  // ANSI variant.
  if(Title.length > 0) {  // Add the title to the list if its length is greater than 0.
    WinList += hwnd + "\t" + Title + "\n";
    ++n;
  }
  ++m;
  return 1;              // Returning 0 will stop the calls.
}

VBScript

Set DX = CreateObject("DynamicWrapperX")

DX.Register "user32", "EnumWindows",    "i=pl"
DX.Register "user32", "GetWindowTextW", "i=hWl"     ' Unicode variant.
' DX.Register "user32", "GetWindowText", "i=hSl"    ' ANSI variant.

Set Ref = GetRef("CbkEnumWin")  ' Get a reference to the function.

pCbkFunc = DX.RegisterCallback(Ref, "i=hl", "r=l")  ' Register CbkEnumWin
                                                    ' as a callback procedure
                                                    ' and get its pointer.
n = 0 : m = 0 : WinList = ""
Title = Space(256)              ' Buffer for the window titles (an output string).

DX.EnumWindows pCbkFunc, 0      ' Call EnumWindows and pass it the pointer
                                ' to the callback procedure.       

WScript.Echo "Windows in total: " & m & vbCrLf & "With a title: " & n & _
              vbCrLf & vbCrLf & WinList


' ................ The callback function itself .......................

Function CbkEnumWin(hwnd, lparam)
  DX.GetWindowTextW hwnd, Title, 256
  ' DX.GetWindowText hwnd, Title, 256   ' ANSI variant.
  If Len(Title) > 0 Then  ' Add the title to the list if its length is greater than 0.
    WinList = WinList & hwnd & vbTab & Title & vbCrLf
    n = n+1
  End If
  m = m+1
  CbkEnumWin = 1          ' Returning 0 will stop the calls.
End Function

Top

Other methods

Note: parameters in brackets are optional, but you can't omit a parameter if the one following it is present.

NumGet( Address [, Offset] [, Type] ) - reads a number from memory. Address - a base address. Offset - a displacement (in bytes) from the base, positive or negative (0 by default): it can be used in loops for reading/writing sequences of numbers. Type - the type of the retrieved number ("l" by default). Only small letters can be used. The number is put in the value returned by the method.

NumPut( Var, Address [, Offset] [, Type] ) - writes a number to memory. Var - either a literal number or the name of a variable holding it. The rest is similar to NumGet. The return value of the method will be the address just after the last written byte.

With both methods above, Address can be either a number or a string, in the latter case the string pointer will serve as the base address. This allows to use strings as memory buffers for storing any data - structures, arrays, etc.

StrPtr( Var [, Type] ) - returns a pointer to a string. Var - a string variable or constant. Type - the type of the destination string. Can be: w (by default), s, z. For s and z the string is previously converted (in place).

StrGet( Address [, Type] ) - reads the string at the specified address. Returns a copy of the string. Address can be either a numeric variable or a string one. Type - the type of the source string. Can be: w (by default), s, z. The options s and z are useful for reading ANSI or OEM strings, in those cases the returned copy will have been converted into Unicode.

Space( Count [, Char] ) - creates a string (BSTR) of the specified length. Returns a string variable. Count - the number of Unicode (two-byte) characters in the string. Char - a character to fill the string with. By default the string is filled with spaces, just as the Space function in VBScript does. To use nulls instead of spaces, specify Char as an empty string ("").

JScript

DX = new ActiveXObject("DynamicWrapperX");
str = "Hello, world! It's me.";

// Reading from memory. Read the character codes of the string.

codes = "";

for(i=0; i < str.length; ++i)
  codes += DX.NumGet(str, i*2, "t") + " "; // i is multiplied by 2 because the offset
                                           // must be in bytes and "t" is a two-byte type.
WScript.Echo("Character codes:\n" + codes);

// Reading from and writing to memory. The string is read and then written in reverse order.

len = str.length;
buf = DX.Space(len);                   // Buffer for writing.

for(i=0, j=len-1; i < len; ++i, --j) { // len-1 is the index of the last character.
  code = DX.NumGet(str, i*2, "t");     // Read from left to right (the offset grows).
  DX.NumPut(code, buf, j*2, "t");      // Write from right to left (the offset decreases).
}
WScript.Echo("Reversed string:\n" + buf);

// Other.

ptr = DX.StrPtr(str);                  // Get a string pointer into a numeric
                                       // variable, the string remains in Unicode.
WScript.Echo("Address of the string: " + ptr);

ptr = DX.StrPtr(str, "z");             // Get a pointer to the same string
                                       // previously converted to OEM/DOS.
str1 = DX.StrGet(ptr, "z");            // Read that string with
                                       // conversion back to Unicode.
WScript.Echo("Restored string:\n" + str1);

VBScript

Set DX = CreateObject("DynamicWrapperX")
str = "Hello, world! It's me."

' Reading from memory. Read the character codes of the string.

strlen = Len(str)
codes = ""

For i=0 To strlen-1
  codes = codes & DX.NumGet(str, i*2, "t") & " "  ' i is multiplied by 2 because the offset must
Next                                              ' be in bytes and "t" is a two-byte type.

WScript.Echo "Character codes:" & vbCrLf & codes

' Reading from and writing to memory. The string is read and then written in reverse order.

buf = Space(strlen)                 ' Buffer for writing.
j = strlen-1                        ' strlen-1 is the index of the last character.

For i=0 To strlen-1
  code = DX.NumGet(str, i*2, "t")   ' Read from left to right (the offset grows).
  DX.NumPut code, buf, j*2, "t"     ' Write from right to left (the offset decreases).
  j = j-1
Next

WScript.Echo "Reversed string:" & vbCrLf & buf

' Other.

ptr = DX.StrPtr(str)                 ' Get a string pointer into a numeric
                                     ' variable, the string remains in Unicode.      
WScript.Echo "Address of the string: " & ptr

ptr = DX.StrPtr(str, "z")            ' Get a pointer to the same string
                                     ' previously converted to OEM/DOS.
str1 = DX.StrGet(ptr, "z")           ' Read that string with conversion back to Unicode.

WScript.Echo "Restored string:" & vbCrLf & str1

Top

Download

The library can be downloaded here (version 1.0.0.0 from 07.10.2008, archive of 14 024 byte). You can find the library and Help file in html format (i.e. the article you're reading) in the archive. You can also ask your questions and leave your comments and suggestions on Russian forum.

Top