Specifics of Using 32- and 64-bit DLLs

Applies to TestComplete 14.93, last modified on September 27, 2021

This topic describes specifics of using external dynamic link libraries of different “bitness” (32- and 64-bit) in TestComplete tests.

Using 32- and 64-bit DLLs in TestComplete - Basic Concepts

TestComplete can call routines from both 32- and 64-bit DLLs (note that 64-bit libraries can be used only in 64-bit operating systems). In order for a library to be loaded into the TestComplete hosting process, the “bitness” of the hosting process must match the “bitness” of the library. That is, a 32-bit dynamic library can only be loaded into a 32-bit hosting process, and a 64-bit library can only be loaded into a 64-bit hosting process, respectively. Otherwise, the library will not be loaded into the hosting process and TestComplete will not be able to call routines from this library. TestComplete can create up to two hosting processes simultaneously (a 32-bit process for 32-bit DLLs and a 64-bit process for 64-bit DLLs) if both 32- and 64-bit libraries are used by a TestComplete test.

Since 32-bit operating systems support only 32-bit DLLs, TestComplete creates only a 32-bit hosting process to which only 32-bit DLLs can be loaded in such operating systems. In 64-bit operating systems, 64-bit DLLs can be used along with 32-bit DLLs. If your test calls functions only from 32-bit or from 64-bit DLLs, TestComplete creates one hosting process and loads all libraries into it. However, if your test calls functions from both 32- and 64-bit DLLs, TestComplete needs two hosting processes with the appropriate “bitness” and each DLL needs to be loaded into the appropriate hosting process.

If you want to call routines only from 32- or 64-bit dynamic libraries in one test, you can use the DLL program object that contains a number of methods for loading dynamic libraries and calling their internal routines (see Calling DLL Functions From Tests - Tutorial for details on using this object). When you first call the DLL.Load method from your test to load a dynamic library during test execution, TestComplete automatically creates a hosting process with the “bitness” of the library to be loaded. In this case, a program environment for loading dynamic libraries of the same “bitness” is created by default after loading the library. If you are going to use only libraries of this “bitness” in your test (that is, only 32- or 64-bit libraries), there is no problem: All libraries will be loaded into the default program environment, that is, into the same hosting process. You can work with them by using the abilities provided by the DLL program object.

However, if you try calling the DLL.Load method to load a library with a “bitness” that differs from the “bitness” of the previously loaded libraries, TestComplete will not create a new hosting process with the corresponding “bitness” and an error will occur. For instance, if you load one or more 32-bit libraries by calling the DLL.Load method from your test, TestComplete will create one 32-bit hosting process common for all these libraries and you will not be able to load 64-bit libraries by calling DLL.Load from the same test. So, if you need to use both 32- and 64-bit libraries, you need to explicitly create appropriate program environments yourself. In the next section of the topic, we will explain how you can create such an environment in your tests.

Follow the recommendations below to decide whether you need to explicitly create program environments or load your libraries directly by using the members of the DLL program object without creating environments:

  • If you are sure that you are going to work only with libraries of the same “bitness” (either 32- or 64-bit) in your test, you can load dynamic libraries and call their routines by using the methods of the DLL program object (see Calling DLL Functions From Tests - Tutorial).

  • If you need to use both 32- and 64-bit libraries in the same test or if you are going to run your test on operating systems of different “bitness” where libraries of the appropriate “bitness” should be used and the types of the called DLL routines’ parameters have different size depending on the operating system’s “bitness” (for instance, the HWND data type), or if you currently call routines located in libraries of the same “bitness” from your test, but there is a probability that you will need to use libraries of a different “bitness” in the same test, we recommend creating program environments for your DLLs and calling routines only via these environments (see below).

If the DLL.New method is called earlier than the DLL.Load method in a test (for instance, to create a custom data structure or an array), TestComplete also creates a hosting process. However, in this case, TestComplete creates a 32-bit hosting process, by default. Therefore, you will not be able to load 64-bit libraries into the created 32-bit hosting process by calling the DLL.Load method (an error will occur in this case). So, if you are going to load 64-bit dynamic libraries, keep this information in mind and do not call the DLL.New method before loading a library with the DLL.Load method.
The WOW64 subsystem may redirect calls to some system libraries when you work with 32-bit applications. To load a library of a particular bitness, specify the full path to the required library.

Creating Environments for Loading DLLs

To create an environment for loading DLLs of a certain “bitness”, you can use either the DLL.DefineEnvironment or DLL.DefineEnvironmentByDLL method. The methods return an IDLLAccessProcess object that provides scripting access to the created program environment. When calling these methods, you can specify the “bitness” of the hosting process to be created. Using the DLL.DefineEnvironment method, you can specify the desired “bitness” via the method’s parameter. The DLL.DefineEnvironmentByDLL method defines an environment for a hosting process with the same “bitness” as the DLL specified via the method’s parameter has.

The IDLLAccessProcess object returned by the above-mentioned methods provides the same methods for defining and loading DLLs, defining data types and creating new variables of these types as the DLL object does (see DLL Object and IDLLAccessProcess Object for more information). The main difference is that you can specify the “bitness” of the hosting process by creating an IDLLAccessProcess object via the DLL.DefineEnvironment or DLL.DefineEnvironmentByDLL method and load libraries into the needed environment (that is, into the hosting process of the appropriate “bitness”). By calling methods of the returned IDLLAccessProcess object, you can work with the libraries of the same “bitness” that are loaded into the appropriate hosting process. Thus, you can create one environment for 32-bit DLLs and another environment for 64-bit libraries and work with libraries of a certain “bitness” via the appropriate environment.

Do not use the methods of the DLL and IDLLAccessProcess objects simultaneously in one script. Use only the methods of the IDLLAccessProcess object if you define environments for DLL loading, or use only the methods of the DLL object if you do not define environments and work only with 32-bit or 64-bit DLLs (this concerns the New, Load, DefineType and DefineDLL methods that are provided both by the DLL and IDLLAccessProcess objects).

The following example demonstrates how you can create a program environment for loading dynamic link libraries and call routines from them. To learn how you can call routines from DLLs by using the methods of the DLL program object without creating an environment explicitly, see Calling DLL Functions From Tests - Tutorial.

JavaScript, JScript

function CallRoutineFromDLL()
{
  var Def_Environment, Def_DLL;
  var Lib, LpStr;
 
  // Defines a 32-bit environment
  Def_Environment = DLL.DefineEnvironment(true);
  // -- or --
  // Def_Environment = DLL.DefineEnvironmentByDLL("USER32");

  // Defines the dll type
  Def_DLL = Def_Environment.DefineDLL("USER32");
 
  // Registers the function type in TestComplete
  Def_DLL.DefineProc("GetWindowTextA",
                            vt_ui4,
                            vt_lpstr,
                            vt_i4,
                            vt_i4);
 
  // Creates the string parameter
  LpStr = Def_Environment.New("LPSTR", 256);
 
  // Loads the dll
  Lib = Def_Environment.Load("USER32");
 
  // Gets the window text
  Lib.GetWindowTextA(1381552, LpStr, 256);
  Log.Message(LpStr.Text);
}

Python

def CallRoutineFromDLL():
    # Defines a 32-bit environment
  Def_Environment = DLL.DefineEnvironment(True)
  # -- or --
  # Def_Environment = DLL.DefineEnvironmentByDLL("USER32")

  # Defines the dll type
  Def_DLL = Def_Environment.DefineDLL("USER32")
 
  # Registers the function type in TestComplete
  Def_DLL.DefineProc("GetWindowTextA",
                            VT_UI4,
                            VT_LPSTR,
                            VT_I4,
                            VT_I4)
 
  # Creates the string parameter
  LpStr = Def_Environment.New("LPSTR", 256)
 
  # Loads the dll
  Lib = Def_Environment.Load("USER32")
 
  # Gets the window text
  Lib.GetWindowTextA(1381552, LpStr, 256)
  Log.Message(LpStr.Text)

VBScript

Sub CallRoutineFromDLL
 
  ' Defines a 32-bit environment
  Set Def_Environment = DLL.DefineEnvironment(True)
  ' -- or --
  ' Set Def_Environment = DLL.DefineEnvironmentByDLL("USER32")
   
  ' Defines the dll type
  Set Def_DLL = Def_Environment.DefineDLL("USER32")
 
  ' Registers the function type in TestComplete
  Call Def_DLL.DefineProc("GetWindowTextA", _
                        vt_ui4, _
                        vt_lpstr, _
                        vt_i4, _
                        vt_i4)
 
  ' Creates the string parameter
  Set LpStr = Def_Environment.New("LPSTR", 256)
 
  ' Loads the dll
  Set Lib = Def_Environment.Load("USER32")
 
  ' Gets the window text
  Call Lib.GetWindowTextA(1381552, LpStr, 256)
  Call Log.Message(LpStr.Text)
 
End Sub

DelphiScript

procedure CallRoutineFromDLL;
var
  Def_DLL, Def_Environment, Lib : OleVariant;
  LpStr: OleVariant;
begin
 
  // Defines a 32-bit environment
  Def_Environment := DLL.DefineEnvironment(True);
  // -- or --
  // Def_Environment := DLL.DefineEnvironmentByDLL('USER32');
   
  // Defines the dll type
  Def_DLL := Def_Environment.DefineDLL('USER32');
 
  // Registers the function type in TestComplete
  Def_DLL.DefineProc('GetWindowTextA',
                    vt_ui4,
                    vt_lpstr,
                    vt_i4,
                    vt_i4);
 
  // Creates the string parameter
  LpStr := Def_Environment.New('LPSTR', 256);
 
  // Loads the dll
  Lib := Def_Environment.Load('USER32');
  
  // Gets the window text
  Lib.GetWindowTextA(1381552, LpStr, 256);
  Log.Message(LpStr.Text);
end;

C++Script, C#Script

function CallRoutineFromDLL()
{
  var Def_Environment, Def_DLL;
  var Lib, LpStr;
 
  // Defines a 32-bit environment
  Def_Environment = DLL["DefineEnvironment"](true);
  // -- or --
  // Def_Environment = DLL["DefineEnvironmentByDLL"]("USER32");
   
  // Defines the dll type
  Def_DLL = Def_Environment["DefineDLL"]("USER32");
 
  // Registers the function type in TestComplete
  Def_DLL["DefineProc"]("GetWindowTextA",
                            vt_ui4,
                            vt_lpstr,
                            vt_i4,
                            vt_i4);
 
  // Creates the string parameter
  LpStr = Def_Environment["New"]("LPSTR", 256);
 
  // Loads the dll
  Lib = Def_Environment["Load"]("USER32");
 
  // Gets the window text
  Lib["GetWindowTextA"](1381552, LpStr, 256);
  Log["Message"](LpStr["Text"]);
}

Note that TestComplete can create only one 32-bit hosting process and the appropriate program environment for 32-bit DLLs and one 64-bit hosting process with the appropriate environment for 64-bit DLLs. Even if you call the DLL.DefineEnvironment or DLL.DefineEnvironmentByDLL method several times specifying the same “bitness” of the hosting process to be created, all the calls will return the same IDLLAccessProcess object that will communicate with the same hosting process.

Furthermore, as stated above, TestComplete creates a helper hosting process when the DLL.Load (IDLLAccessProcess.Load) or DLL.New (IDLLAccessProcess.New) method is called. If a hosting process with the required “bitness” has already been created, TestComplete uses this previously created process and does not create a new one. TestComplete terminates the hosting process (or processes if both 32-bit and 64-bit DLLs are used) when the test playback is finished.

See Also

Calling DLL Functions From Tests
Calling DLL Functions From Tests - Overview
Calling DLL Functions From Tests - Tutorial

Highlight search results