Calling Functions From .NET Assemblies

Applies to TestComplete 14.30, last modified on November 21, 2019
Information in this topic applies to desktop applications only.

In TestComplete, you can call routines that reside in any .NET assembly, from your scripts. The assembly can be supplied with the .NET Framework or a third-party .NET application.

Requirements

  • A license for the TestComplete Desktop module.

  • The .NET Classes Support feature enabled in TestComplete.

    If you experience issues with accessing .NET classes, select File | Install Extensions from the TestComplete main menu and check whether the .NET Classes Support feature is enabled.

Calling .NET routines via dotNET Object

You can use a special dotNET object to access .NET assemblies, types and type members.

Configuring Project’s CLR Bridge Settings

To call routines from a .NET assembly via the dotNET object:

  1. Make the assembly available to the script engine.

    Add the needed assembly to the project’s CLR Bridge list. This list contains .NET assemblies whose functions will be available to your scripts through the dotNET object:

    • Select Tools | Current Project Properties from the TestComplete main menu.

    • Select the CLR Bridge category.

    • To add one or more assembles from the global assembly cache (GAC), click Browse GAC and select the needed assemblies.

      To add one or more assemblies from the disk, click Browse Files and select the needed files.

    Notes:

    • TestComplete always has access to the mscorlib.dll assembly (even though it is not in the CLR Bridge list). You can always use mscorlib.dll’s classes in your tests.

    • After you remove an assembly from the list, click Reload to unlock the assembly’s file. Otherwise, TestComplete will keep the assembly locked for read-write access until you exit TestComplete.

  2. Specify the type of hosting process.

    TestComplete loads .NET assemblies into a helper hosting process, tcHostingProcess.exe. TestComplete can work with both 32- and 64-bit assemblies, and the bitness of the hosting process must match the bitness of the assembly. Otherwise, TestComplete will not be able to load the assembly to the hosting process.

    The Preferred architecture of the assembly hosting process setting defines the bitness of the hosting process. The possible values are:

    • 32-bit

    • 64-bit

    • The same as the operating system

    Notes:

    • TestComplete can create a 64-bit hosting process and load 64-bit assemblies in it only in 64-bit operating systems.

    • 32-bit assemblies can only be loaded in the 32-bit hosting process, and 64-bit assemblies can be only loaded in the 64-bit hosting process. Therefore, if the assemblies list contains both 32- and 64-bit assemblies, TestComplete will not load assemblies whose bitness does not match the bitness of the hosting process.

    • When you opened two projects and one of them uses the 32-bit hosting process while another uses the 64-bit hosting process, TestComplete will create two instances of the hosting process: one for 32-bit assemblies and another one for 64-bit assemblies.

    • TestComplete shares hosting processes among projects. It does not create a separate process for each test project. That is, all the projects with the same CRL Bridge bitness will use the same hosting process instance.

  3. Specify the calling thread type.

    To specify the apartment model of the calling thread, use the Preferred apartment model of the calling thread setting that belongs to the CLR Bridge group of project properties. The possible values are:

    • Single-threaded apartment

    • Multi-threaded apartment

    • Does not matter

Calling Routines From .NET Assemblies

Assemblies added to the CLR Bridge options become available in scripts as child objects of the dotNET object. Types defined in a specific assembly are, in their turn, child objects of the assembly node. The object corresponding to a type provides access to subtypes, static members and constructors defined in this type.

The Code Completion window shows all the available assemblies, types and type members:

To call static members of a .NET class:

dotNET.namespace.class.subclass.method(parameters)

dotNET.namespace.class.subclass.property(parameters)

TestComplete automatically converts simple data types (such as strings, integers and Booleans) you pass as parameters to .NET-compatible values.

You can pass parameters of the Object type to the CLR Bridge engine only if they are descendants of the System.Object class. Otherwise, a type mismatch error will occur.

Separate the namespace and class name (and subclass name) using dots. Replace the dots used in the namespace name with an underscore character.

To call non-static members:

First create an instance of the needed class by calling the class constructor or a static member that create a new class instance:

dotNET.namespace.class.zctor()

dotNET.namespace.class.zctor_2(parameters)

Note: Normally, the class constructor name is _ctor(). A class can have several constructors ( _ctor(), _ctor2(), _ctor3(), etc). However, in VBScript an identifier cannot begin with an underscore ( _ ). Therefore, to keep _ctor methods compatible with VBScript, TestComplete replaces the leading underscore character with the letter z in method names. For example, to call the _ctor3 method, use zctor_3.

For more information on creating instances of .NET classes in scripts, see Creating Instances of .NET Classes Defined in Applications.

Calling .NET Routines via Application Domain

All process objects that correspond to .NET applications have the AppDomain method. The method provides access to the application domain. The object returned by the AppDomain method is a wrapper for the .NET AppDomain object.

TestComplete adds an extra dotNET property to the underlying .NET AppDomain object. The property provides access to classes defined in the application and to classes defined in assemblies loaded to the application domain.

Note: The dotNET property also provides access to all classes declared in the mscorlib.dll assembly.
To call a class member via AppDomain:

Sys.Process("MyProcessName").AppDomain("AppDomainName").dotNET.namespace.class.subclass.method(parameters)

Sys.Process("MyProcessName").AppDomain("AppDomainName").dotNET.namespace.class.subclass.property(parameters)

To call a non-static member:

First create a class instance using the class constructor or a special static member (see above):

Sys.Process("MyProcessName").AppDomain("AppDomainName").dotNET.namespace.class.zctor()
Sys.Process("MyProcessName").AppDomain("AppDomainName").dotNET.namespace.class.zctor_2.(parameters)

For more information on creating instances of .NET classes in scripts, see Creating Instances of .NET Classes Defined in Applications.

The advantage of the AppDomain.dotNET property over the dotNET object is that you can create instances of application classes without adding application assemblies to the project’s CLR Bridge list.

If you need to create a .NET object to pass it as a parameter to an application routine, we recommend that you create that object by using the application domain. This way you will be able to avoid compatibility issues if you have several versions of .NET Framework installed on your computer.

You cannot pass non-.NET objects as parameters to .NET application routines. For more information, see Creating Instances of .NET Classes Defined in Applications.

Calling .NET Routines With Optional Parameters

TestComplete does not allow omitting optional parameters when calling .NET functions from your tests. That is, you must specify all the parameters in the call, including the optional ones.

This means that if you want to use the default parameter values, you have to specify them explicitly in the call. A partial workaround to this is passing dotNET.System.Type.Missing as optional parameter values. For example, if you have a method declared in the following way —

C#

namespace TestNamespace
{
  static public class MyTestClass
  {

    // This method takes two parameters.
    // The Param1 parameter is required, the Param2 parameter is optional.
    static public void Method1(int param1, string param2 = "")
    {
      …
    }

  }
}

— then to call it from TestComplete scripts, you can use the following code:

Script

dotNET.TestNamespace.MyTestClass.Method1(1, dotNET.System.Type.Missing)

Note, however, that this workaround does not work for integer, double and boolean optional parameters. You can use it for string, date, object and other parameter types. As for the integer, boolean or decimal optional parameters, you have to specify appropriate integer, double and boolean values in the call.

Working With Values Returned by .NET Routines

In .NET, all data types are objects. Therefore, there are some specifics of using values returned from .NET routines in your tests:

  • .NET integer, double and boolean values are compatible with TestComplete data types. You can use them in tests directly.

  • Access the values of .NET Decimal and DateTime objects and of enumerable members via the OleValue property.

    For example, to access the System.DataTime.UtcNow property holding the current UTC time:

    JavaScript, JScript

    dotNET.System.DateTime.UtcNow.OleValue

    Python

    dotNET.System.DateTime.UtcNow.OleValue

    VBScript

    dotNET.System.DateTime.UtcNow.OleValue

    DelphiScript

    dotNET.System.DateTime.UtcNow.OleValue

    C++Script, C#Script

    dotNET["System"]["DateTime"]["UtcNow"]["OleValue"]

    In some cases, you can use OleValue with .NET strings (System.String objects). For example, you may need to use OleValue to pass a string value as a parameter to a user-defined script function or to compare the value with another string.

  • Single-dimensional .NET arrays have the extra OleValue property that returns a Variant-compatible array. This way you can use native array operations and functions of scripting languages with the returned array.

    For example, in VBScript, you can use the LBound and UBound functions to determine an array’s lower and upper bounds and access array elements using the parenthesis syntax: arr(index).

    In JScript, С#Script and C++Script arrays obtained via the OleValue property are not compatible with native array formats. They should be first converted to native arrays by using the toArray() method.

    JavaScript

    function newTest()
    {
      var dotnet_str = dotNET.System.String.Copy("Hello, world!");
      var dotnet_array = dotnet_str.ToCharArray();
      
      // Converting to a native array
      var array2 = dotnet_array.OleValue;
      for(let i = 0; i < array2.length; i++)
      Log.Message(array2[i]);
    }

    JScript

    function newTest()
    {
      var dotnet_str = dotNET.System.String.Copy("Hello, world!");
      var dotnet_array = dotnet_str.ToCharArray();
      
      // Converting to a native array
      var array2 = dotnet_array.OleValue.toArray();
      
      for(var i = 0; i < array2.length; i++)
      Log.Message(array2[i]);
      
    }

    Python

    def newTest():
      dotnet_str = dotNET.System.String.Copy("Hello, world!")
      dotnet_array = dotnet_str.ToCharArray()
     
      # Converting to a native array
      array2 = dotnet_array.OleValue
      
      for i in range(len(array2)):
        Log.Message(array2[i])

    VBScript

    Sub newTest
      Dim dotnet_str, dotnet_array, array2, i
      
      Set dotnet_str = dotNET.System.String.Copy("Hello, world!")
      Set dotnet_array = dotnet_str.ToCharArray()
      
      ' Converting to a native array
      array2 = dotnet_array.OleValue
      
      i = 0
      For i = LBound(array2) To UBound(array2)
        Log.Message(array2(i))
      Next
    End Sub

    DelphiScript

    procedure newTest;
    var dotnet_str, dotnet_array, array2, i;
    begin
      dotnet_str := dotNET.System.String.Copy('Hello, world!');
      dotnet_array := dotnet_str.ToCharArray();
      
      // Converting to a native array
      array2 := dotnet_array.OleValue;
      
      for i := VarArrayLowBound(array2,1) to VarArrayHighBound(array2,1) do
        Log.Message(array2[i]);
    end;

    C++Script, C#Script

    function newTest()
    {
      dotnet_str = dotNET["System"]["String"]["Copy"]("Hello, world!");
      dotnet_array = dotnet_str["ToCharArray"]();
       
      // Converting to a native array
      array2 = dotnet_array["OleValue"]["toArray"]();
      
      for(var i = 0; i < array2.length; i++)
      Log["Message"](array2[i]);
    }

    Alternatively, you can use the GetValue method to access .NET array items.

    JavaScript

    function arrayTest()
    {
      var dotnet_str = dotNET.System.String.Copy("Hello, world!");
      var dotnet_array = dotnet_str.ToCharArray();
      

      for(let i = 0; i < dotnet_array.Length; i++)
      {
        var elem = dotnet_array.GetValue(i);
        Log.Message(elem);
      }
    }

    JScript

    function arrayTest()
    {
      var dotnet_str = dotNET.System.String.Copy("Hello, world!");
      var dotnet_array = dotnet_str.ToCharArray();
      

      for(var i = 0; i < dotnet_array.Length; i++)
      {
        var elem = dotnet_array.GetValue(i);
        Log.Message(elem);
      }
    }

    Python

    def arrayTest():
      dotnet_str = dotNET.System.String.Copy("Hello, world!")
      dotnet_array = dotnet_str.ToCharArray()
     
      for i in range(dotnet_array.Length):
       Log.Message(dotnet_array.GetValue(i))

    VBScript

    Sub arrayTest
      Dim dotnet_str, dotnet_array, array2, i, elem
      
      Set dotnet_str = dotNET.System.String.Copy("Hello, world!")
      Set dotnet_array = dotnet_str.ToCharArray()
      
      For i = dotnet_array.GetLowerBound(0) To dotnet_array.GetUpperBound(0)
        Log.Message(dotnet_array.GetValue(i))
      Next
    End Sub

    DelphiScript

    procedure arrayTest;
    var dotnet_str, dotnet_array, array2, i, elem;
    begin
      
      dotnet_str := dotNET.System.String.Copy('Hello, world!');
      dotnet_array := dotnet_str.ToCharArray();
      
      for i := dotnet_array.GetLowerBound(0) to dotnet_array.GetUpperBound(0) do
       Log.Message(dotnet_array.GetValue(i));
    end;

    C++Script, C#Script

    function arrayTest()
    {
      dotnet_str = dotNET["System"]["String"]["Copy"]("Hello, world!");
      dotnet_array = dotnet_str["ToCharArray"]();
       
      for(var i = 0; i < dotnet_array.Length; i++)
      {
        var elem = dotnet_array["GetValue"](i);
        Log["Message"](elem);
      }
    }

  • Two- and multidimensional .NET arrays do not have the OleValue property. To get the array elements, use its native Get(index1, index2, ..., indexN) method.

  • To work with other objects, use their internal properties and methods.

Samples

The following example shows how you can create a System.String object (defined in the mscorlib assembly) and call methods of this object in scripts:

JavaScript, JScript

function Test()
{
  var str, i;

  // Calling a class instance constructor
  str = dotNET.System.String.zctor_8(0x41, 3);
  Log.Message(str.OleValue); // Using the OleValue property

  // Calling a static method
  str = dotNET.System.String.Copy("Hello, world!");

  // Calling a class instance (non-static) method
  i = str.IndexOf("w");
  Log.Message(i);
}

Python

def Test():

  # Calling a class instance constructor
  str = dotNET.System.String.zctor_8(0x41, 3)
  Log.Message(str.OleValue) # Using the OleValue property

  # Calling a static method
  str = dotNET.System.String.Copy("Hello, world!")

  # Calling a class instance (non-static) method
  i = str.IndexOf("w")
  Log.Message(i)

VBScript

Sub Test
  Dim str, i

  ' Calling a class instance constructor
  Set str = dotNET.System.String.zctor_8(&H41, 3)
  Log.Message str.OleValue ' Using the OleValue property

  ' Calling a static method
  Set str = dotNET.System.String.Copy("Hello, world!")
  ' Calling a class instance (non-static) method
  i = str.IndexOf("w")
  Log.Message i
End Sub

DelphiScript

procedure Test;
var str, i;
begin
  // Calling a class instance constructor
  str := dotNET.System.String.zctor_8($41, 3);
  Log.Message(str.OleValue); // Using the OleValue property

  // Calling a static method
  str := dotNET.System.String.Copy('Hello, world!');
  // Calling a class instance (non-static) method
  i := str.IndexOf('w');
  Log.Message(i);
end;

C++Script, C#Script

function Test()
{
  var str, i;

  // Calling a class instance constructor
  str = dotNET["System"]["String"]["zctor_8"](0x41, 3);
  Log["Message"](str["OleValue"]); // Using the OleValue property

  // Calling a static method
  str = dotNET["System"]["String"]["Copy"]("Hello, world!");

  // Calling a class instance (non-static) method
  i = str["IndexOf"]("w");
  Log["Message"](i);
}

TestComplete also includes a sample project that shows how to call functions from .NET assemblies via the dotNET object:

Applications:

<TestComplete Samples>\Desktop\Using .NET Classes\Application\UserApp

<TestComplete Samples>\Desktop\Using .NET Classes\Application\UserClassLib

TestComplete project suites:

<TestComplete Samples>\Desktop\Using .NET Classes

Note: If you do not have the sample, download the TestComplete Samples installation package from the support.smartbear.com/downloads/testcomplete/samples/ page of our website and run it.

See Also

Using External Functions
Testing .NET Applications
Accessing Native Properties and Methods of .NET Objects
Calling DLL Functions From Tests
Project Properties - CLR Bridge Options
TestComplete Helper Objects

Highlight search results