Using Custom Data Structures in DLL Function Calls

Applies to TestComplete 14.72, last modified on April 22, 2021

Functions in dynamic link libraries use parameters of different data types. Some functions require passing values of simple data types like integer or boolean through their parameters, while other functions require passing custom structures. This topic explains how you can use custom data structures when calling DLL functions from TestComplete tests.

First of all, to create a data structure, you must define its data type in your test. For this purpose, call the DLL.DefineType method. It registers the new data type (normally, a structure) and returns its ID. The ID can be used later to define members of other structure types or DLL function parameters.

When calling DLL.DefineType, you need to specify the name of the new data type (the name is used later to create a variable of this type) and the list of structure members. The members are specified as Member type, Member name pairs separated by commas. The member names are used to access the structure members in scripts. That is why they must agree with the naming rules of the scripting language you are using. Also, member names must be unique within the structure. Member types are specified by special constants built in TestComplete (see DefineProc Method for the list of these predefined constants) or by IDs returned by previous calls to the DefineType method when defining other custom data types. Note that if a structure member is a pointer to a variable of some data type, you must specify the additional vt_byref constant when defining the member type (see the example below).

The following example illustrates how you can define a custom structure with members of different data types:

JavaScript, JScript

var StructID = DLL.DefineType("MyStruct", // name of the new structure
                          vt_i4, "IntMember", // integer member
                          vt_byref | vt_r4, "FloatPointerMember", // pointer to float
                          MyStruct2ID, "MyStruct2Member"); // structure MyStruct2

Python

StructID = DLL.DefineType("MyStruct", # name of the new structure
                          VT_I4, "IntMember", # integer member
                          VT_BYREF | VT_R4, "FloatPointerMember", # pointer to float
                          MyStruct2ID, "MyStruct2Member") # structure MyStruct2

VBScript

StructID = DLL.DefineType("MyStruct", _ ' name of the new structure
                          vt_i4, "IntMember", _ ' integer member
                          vt_byref or vt_r4, "FloatPointerMember", _ ' pointer to float
                          MyStruct2ID, "MyStruct2Member") ' structure MyStruct2

DelphiScript

StructID := DLL.DefineType('MyStruct', // name of the new structure
                          vt_i4, 'IntMember', // integer member
                          vt_byref or vt_r4, 'FloatPointerMember', // pointer to float
                          MyStruct2ID, 'MyStruct2Member'); // structure MyStruct2

C++Script, C#Script

var StructID = DLL["DefineType"]("MyStruct", // name of the new structure
                          vt_i4, "IntMember", // integer member
                          vt_byref | vt_r4, "FloatPointerMember", // pointer to float
                          MyStruct2ID, "MyStruct2Member"); // structure MyStruct2
Make sure that you correctly specified the member types. Incorrect definition of data types may cause TestComplete to shut down when passing such structures to DLL functions (this happens because erroneous definitions may cause incorrect stack processing).

From the Parameter Types for C++ Routines and Structures and Parameter Types for Delphi Routines and Structures topics, you can learn which data type constants built in TestComplete correspond to some standard data types frequently used in C++ and Delphi applications.

Compilers can align structure members at certain addresses multiplied by 1, 2, 4 or more bytes. For instance, the double word (4 byte) alignment is often used in applications. The alignment may enlarge the actual size of structure members whose data type normally takes less memory than the alignment value. For instance, Boolean occupies one byte. In case of double word alignment, the actual size of a Boolean member will be four bytes. You should remember this and use appropriate data type constants (in this case, vt_b4 instead of vt_b1 for a Boolean member with double word alignment) when defining members of custom structures since TestComplete does not align structure members itself when passing them to a DLL function being called.

The compilers use alignment for those structures that are defined by default, that is, without the use of the #pragma pack(...) statement in C++ code or packed record in Delphi. When creating a data type for the “default” structures in scripts, pay attention to the size of the structure members.

The DefineType method only registers the data type but does not create a variable of this type. To create a structure of the new type, call the DLL.New method. It creates and returns an object that represents the specified structure type in tests. The memory block allocated for the structure is filled with zero values. To access structure members and initialize them from tests, use the member names passed to the DefineType method when defining the structure type. The example below demonstrates how you can create and initialize a structure variable:

JavaScript, JScript

var struct1 = DLL.New("MyStruct");

struct1.IntMember = 42;
struct1.FloatPointerMember = pFloatVar;
struct1.MyStruct2Member = MyStruct2Var;

Python

struct1 = DLL.New("MyStruct")

struct1.IntMember = 42
struct1.FloatPointerMember = pFloatVar
struct1.MyStruct2Member = MyStruct2Var

VBScript

Set struct1 = DLL.New("MyStruct")

struct1.IntMember = 42
struct1.FloatPointerMember = pFloatVar
Set struct1.MyStruct2Member = MyStruct2Var

DelphiScript

struct1 := DLL.New('MyStruct');

struct1.IntMember := 42;
struct1.FloatPointerMember := pFloatVar;
struct1.MyStruct2Member := MyStruct2Var;

C++Script, C#Script

var struct1 = DLL["New"]("MyStruct");

struct1["IntMember"] = 42;
struct1["FloatPointerMember"] = pFloatVar;
struct1["MyStruct2Member"] = MyStruct2Var;

Note that the DLL.New method has the second parameter, Count, that is optional and not used in the example above. This parameter specifies the number of instances of the data type to create. Using the second parameter, you can create an array of elements of the specified data type. See Using Arrays in DLL Function Calls for details.

Note: The above-mentioned DLL.DefineType and DLL.New methods are used if you are going to work with 32-bit DLLs or with 64-bit DLLs only and do not create an environment for loading DLLs of certain “bitness” and calling routines from them (see Specifics of Using 32- and 64-bit DLLs). However, if you explicitly create and use such an environment for DLLs, you need to call the IDLLAccessProcess.DefineType and IDLLAccessProcess.New methods respectively of an IDLLAccessProcess object that represents an environment for DLLs.

For instance, if you are using a 32-bit environment for 32-bit DLLs, you should create structures in the following manner:

JavaScript, JScript

var MyEnvironment = DLL.DefineEnvironment(true);
var StructID = MyEnvironment.DefineType("MyStruct",
                          vt_i4, "IntMember",
                          vt_byref | vt_r4, "FloatPointerMember",
                          MyStruct2ID, "MyStruct2Member");
var struct1 = MyEnvironment.New("MyStruct");

struct1.IntMember = 42;
struct1.FloatPointerMember = pFloatVar;
struct1.MyStruct2Member = MyStruct2Var;

Python

MyEnvironment = DLL.DefineEnvironment(True)
StructID = MyEnvironment.DefineType("MyStruct",
                          VT_I4, "IntMember",
                          VT_BYREF | VT_R4, "FloatPointerMember",
                          MyStruct2ID, "MyStruct2Member")
struct1 = MyEnvironment.New("MyStruct")

struct1.IntMember = 42
struct1.FloatPointerMember = pFloatVar
struct1.MyStruct2Member = MyStruct2Var;

VBScript

Set MyEnvironment = DLL.DefineEnvironment(True)
StructID = MyEnvironment.DefineType("MyStruct", _
                          vt_i4, "IntMember", _
                          vt_byref or vt_r4, "FloatPointerMember", _
                          MyStruct2ID, "MyStruct2Member")
Set struct1 = MyEnvironment.New("MyStruct")

struct1.IntMember = 42
struct1.FloatPointerMember = pFloatVar
Set struct1.MyStruct2Member = MyStruct2Var

DelphiScript

MyEnvironment := DLL.DefineEnvironment(True);
StructID := MyEnvironment.DefineType('MyStruct',
                          vt_i4, 'IntMember',
                          vt_byref or vt_r4, 'FloatPointerMember',
                          MyStruct2ID, 'MyStruct2Member');
struct1 := MyEnvironment.New('MyStruct');

struct1.IntMember := 42;
struct1.FloatPointerMember := pFloatVar;
struct1.MyStruct2Member := MyStruct2Var;

C++Script, C#Script

var MyEnvironment = DLL.DefineEnvironment(true);
var StructID = MyEnvironment["DefineType"]("MyStruct",
                          vt_i4, "IntMember",
                          vt_byref | vt_r4, "FloatPointerMember",
                          MyStruct2ID, "MyStruct2Member");
var struct1 = MyEnvironment["New"]("MyStruct");

struct1["IntMember"] = 42;
struct1["FloatPointerMember"] = pFloatVar;
struct1["MyStruct2Member"] = MyStruct2Var;

After you defined the structure type, created and initialized a variable of this type, you can pass the structure through the parameters of the needed DLL function in the same manner as you pass parameters of any other data type. See Calling DLL Functions From Tests - Tutorial for more information on this.

Note: Currently, it is impossible to call DLL routines that use structures containing array-type members (for instance, string or array fields):

Delphi

// A definition of structure in the DLL source
type
  TMyStruct1 = record
    FInteger:Integer;
    CharArray: array[0..9] of Char; // The array is used
  end;
...
// This routine will not operate properly when it is called from TestComplete
procedure AcceptStructure(const AValue: TMyStruct1); stdcall;

C++

// A definition of structure in the DLL source
struct TMyStruct1{
  int FInteger;
  char CharArray [10]; // The array is used
};
...
// This routine will not operate properly when it is called from TestComplete
void __stdcall AcceptStructure(TMyStruct1 AValue);

When you try to do this, you will not get an error, but the array-type field will be undefined even if you assign a value to it.

You can workaround this limitation by using pointer-type members:

Delphi

// A definition of structure in the DLL source
type
  TMyStruct2 = record
    FInteger:Integer;
    CharArrPtr: PChar; // The pointer is used
  end;
...
// This routine will function correctly in TestComplete
procedure AcceptStructure(const AValue: TMyStruct2); stdcall;

C++

// A definition of structure in the DLL source
struct TMyStruct2{
  int FInteger;
  char * CharArrPtr; // The pointer is used
};
...
// This routine will function correctly in TestComplete
void __stdcall AcceptStructure(TMyStruct2 AValue);

See Also

Calling DLL Functions From Tests - Tutorial
Calling DLL Functions From Tests
Using String Parameters in DLL Function Calls

Highlight search results