Creating Scripting Objects From C++Builder Application Objects

Applies to TestComplete 14.50, last modified on April 22, 2021
Information in this topic applies to desktop applications only.
Connected and Self-Testing applications are deprecated. These technologies will be removed in one of the future releases of TestComplete.
To create test code that runs from within your tested apps, use TestLeft, a SmartBear functional testing tool for developers.

This topics explains how you can expose objects of C++Builder applications as scripting objects in TestComplete. The explanation applies to applications created in Borland C++Builder 6.0, CodeGear C++Builder 2009 and Embarcadero C++Builder 2010 and XE. The explanation is divided into the following sections:

Preparing Your C++Builder Project

In order for you to expose objects of C++Builder applications as TestComplete scripting objects, you need to get access to the TestComplete COM server. The easiest way to manage TestComplete via COM is to transform your application into a Connected Application. Connected Applications are created by linking special modules to your project. These modules provide objects and routines that free you from having to manually write the code needed to connect to TestComplete via COM.

To learn how you can transform your C++Builder application into a Connected one, see Creating Connected Applications in C++.

Creating the Object Type Library

TestComplete can create scripting objects from those objects of C++Builder applications that provide type information. So, before creating a custom scripting object and writing its code, you need to create a type library that describes the object’s interface, its methods and properties, their parameters and so on.

Note: If you need to expose an arbitrary object of an application (for instance, a specific global or non-visual object), you need to create a COM wrapper that provides access to this object. To learn how you can do this, please refer to the C++Builder documentation.

To create a new type library in C++Builder, follow these steps:

  • Open your project in C++Builder.

  • Choose File | New | Other from C++Builder’s main menu. This will call the New Items dialog.

  • In C++Builder 6, switch to the ActiveX page of the dialog, select Type Library and press OK.

    In C++Builder 2006, select the C++Builder Projects | ActiveX category on the left of the dialog, then select Type Library and press OK.

    C++Builder will create a new type library and show the editor for it.

  • Using the editor, create an interface that defines your runtime object. In our example, we will create an interface containing one read-write property, Text, and one method, ShowText:

    C++Builder TypeLibrary Editor
    Tip: Help strings that you specify for methods and properties will be shown as a method (or a property) description in the Code Completion window.
  • Select File | Save from C++Builder’s main menu.

    C++Builder will generate the binary type library file (.tlb) and the LibraryName_TLB.h and LibraryName_TLB.cpp units holding the type library implementation. It will also add these units to your project.

  • Exit the Type Library editor.

Writing the Object Code

After you have defined the object interface, you can create a class that implements this interface and write the implementation of the interface methods and properties. Since the TestComplete architecture, including the object model, is COM-based, the custom scripting object’s class should also be COM class.

To create a new COM class in C++Builder, follow these steps:

  • Open your project in C++Builder.

  • Select File | New | Other in C++Builder’s main menu. This will call the New Items dialog.

  • In C++Builder 6, switch to the ActiveX page of the dialog, select COM Object and press OK.

    In C++Builder 2006, select the C++Builder Projects | ActiveX category in the tree on the left, then select the COM Object item and press OK.

  • In the ensuing New COM Object dialog, specify the class name of your COM object in the CoClass Name field. In the Implemented Interface field, enter the name of the interface you have previously created (for instance, in our example it is ISampleRuntimeObjectIntf). Optionally, specify a short description for the class in the Description field.

    Press OK.

C++Builder will add the class definition to the project’s type library. It will also generate the class code, including stub implementations of the methods and properties of its interface. You only need to replace the stub code with the code that provides the desired functionality. In our example, the Text property will obtain and return a text string, and the ShowText method will display this string in a message box:

C++Builder

/* TSampleRuntimeObjectImpl.h */

class ATL_NO_VTABLE TSampleRuntimeObjectImpl :
...
{
...
private:
  ATL::CComBSTR fText;
...
};

 
 

/* TSampleRuntimeObjectImpl.cpp */

#include "TSampleRuntimeObjectImpl.h"

STDMETHODIMP TSampleRuntimeObjectImpl::get_Text(BSTR* Value)
{
  return fText.CopyTo(Value);
};


STDMETHODIMP TSampleRuntimeObjectImpl::set_Text(BSTR Value)
{
  fText = Value;
  return S_OK;
};


STDMETHODIMP TSampleRuntimeObjectImpl::ShowText()
{
    MessageBoxW(NULL, fText, L"", MB_OK);
    return S_OK;
}

Registering and Unregistering Custom Scripting Objects

In order to add our custom object to the TestComplete object model, you need to register it in the TestComplete extensibility manager (see Creating Scripting Objects From Application Objects). In C++ Connected Applications, you can address this manager via the Manager object.

To register a custom scripting object, use the manager’s AddName method. The method call can be inserted in the application’s initialization routine, or in a routine that is called upon a specific event. In our example, we will add a button control to the application form and place the registration code in the button’s OnClick event handler. This way, we will be able to easily re-register our object in subsequent TestComplete sessions while the application is running.

The sample code is provided below. It requires a form that has the class name TForm1 and contains a button named Button1.

C++Builder

/* MainForm.h */

#include "SampleRuntimeObjectImpl.h"
const wchar_t* FRuntimeObjectName = L"SampleRuntimeObject";

...

class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TButton *Button1;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall FormCreate(TObject *Sender);
    ...
private:       // User declarations
    CComPtr<IMyIntf> FRuntimeObject;
    bool FRegistered;
    ...
public:        // User declarations
    ...
};



/* MainForm.cpp */

#include "MainForm.h"
#include "C:\Program Files\SmartBear\TestComplete 14\Connected Apps\C++\script.h"

using namespace TestComplete;
IMPLEMENT_TESTCOMPLETE_GLOBAL_OBJECTS

...

void __fastcall TForm1::FormCreate(TObject *Sender)
{
    FRuntimeObject = CoSampleRuntimeObject::Create();
    FRegistered = false;
}

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    const wchar_t* ProjectPath = L"TCProject\\CustomRuntimeObject.pjs";
    try {
        wchar_t buffer[MAX_PATH];
        wchar_t* ch = 0;

        // Open a project suite in TestComplete
        TC["Visible"] = true;
        GetFullPathNameW (ProjectPath, MAX_PATH, buffer, &ch);
        TC["Integration"]["OpenProjectSuite"](buffer);

        // Register our runtime object
        Manager["AddName"](FRuntimeObjectName, false, (IDispatch*) FRuntimeObject);
        FRegistered = true;
    }
    catch (Exception &e) {
        wchar_t buffer [MAX_PATH];
        swprintf (buffer, L"Could not register %s:\r\n%s", WideString(FRuntimeObjectName), WideString(e.Message));
        MessageBoxW(this, buffer, L"", MB_OK);
    }
}

To unregister an object, use the RemoveName method of the TestComplete extensibility manager. In our example, we will place the unregistration code in the form’s Close event handler, so that the object is unregistered when the user exits the application by closing the form. Since the unregistration makes sense only if the object has been previously registered and if TestComplete is running, we check both these conditions before calling the RemoveName method:

C++Builder

/* MainForm.h */

class TForm1 : public TForm
{
__published:    // IDE-managed Components
    void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
    ...
};


/* MainForm.cpp */

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
    IUnknown* Unknown = NULL;
    CLSID TCClassID;

    CLSIDFromProgID(TCApplication, &TCClassID);
    HRESULT res = GetActiveObject(TCClassID, NULL, &Unknown);
    if ((FRegistered) & (res == S_OK))
    {
        Manager["RemoveName"](FRuntimeObjectName);
    }

    FRuntimeObject = NULL;
}

Finally, re-compile your application.

Working With Custom Scripting Objects

After a custom scripting object is registered in TestComplete, it is displayed in the Code Completion window and can be used in scripts:

The SampleRuntimeObject object

You can get and set the object’s property values, call its methods and check their return values the same way you would with standard scripting objects such as TestedApps, Log and others. The only difference is that the processing is performed by your application rather than by TestComplete.

The code snippet below demonstrates how you can work with the sample scripting object we have created earlier in this topic. This object is available in TestComplete from the time you launch the application and click the button on its main form and until the application is closed. Note that, despite the object is a part of a C++Builder application, it can be used in scripts written in any scripting language, not only C++Script:

JavaScript, JScript

function Main()
{
  SampleRuntimeObject.Text = "Hello, world!";
  SampleRuntimeObject.ShowText();
}

Python

def Main():
  SampleRuntimeObject.Text = "Hello, world!"
  SampleRuntimeObject.ShowText()

VBScript

Sub Main
  SampleRuntimeObject.Text = "Hello, world!"
  SampleRuntimeObject.ShowText
End Sub

DelphiScript

procedure Main;
begin
  SampleRuntimeObject.Text := 'Hello, world!';
  SampleRuntimeObject.ShowText;
end;

C++Script, C#Script

function Main()
{
  SampleRuntimeObject["Text"] = "Hello, world!";
  SampleRuntimeObject["ShowText"]();
}

See Also

Creating Scripting Objects From Application Objects
Connected Applications - Overview
Creating Connected Applications in C++

Highlight search results