Implementing MSAA Support Manually

Applies to TestComplete 15.62, last modified on March 19, 2024
Information in this topic applies to desktop applications only.

When TestComplete works with objects in Open Applications, it exposes all standard controls as well as their properties and methods automatically, and makes them easily accessible. However, the application you test may implement custom interfaces that TestComplete does not handle, or you may need to test an application that cannot be compiled as Open. This topic explains how you can prepare your application to work with the Microsoft Active Accessibility Support plugin manually, and how you can work directly with the object that the application returns.

Implementing IAccessible Interface Support in Tested Applications

To obtain Accessibility information about a window, the Microsoft Active Accessibility Support plugin retrieves the IAccessible object associated with this window. To get a reference to the IAccessible object, TestComplete calls the Windows API AccessibleObjectFromWindow function with the OBJID_NATIVEOM parameter. This function sends the WM_GETOBJECT message to the window. The returned object is the one TestComplete uses.

The code that processes the WM_GETOBJECT message can return any object that implements the IDispatch interface or its descendants, and TestComplete will consider these methods and properties exposed by IAccessible. In your tests, you can access these methods and properties through the NativeObject property that TestComplete adds to the window.

The code below demonstrates how you can handle the WM_GETOBJECT message in your application. It returns an IAccessible reference for a window.

Delphi

interface  
  
const
  WM_GETOBJECT = $003D; // Windows MSAA message identifier

TForm1 = class(TForm)
  ...
  procedure WMGetMSAAObject(var Message : TMessage); message WM_GETOBJECT;
  ...
end;

function LresultFromObject(const riid: TGUID; wParam: DWORD; pAcc: IUnknown): Integer; stdcall; external 'oleacc.dll';
...
implementation
...
procedure TForm1.WMGetMSAAObject(var Message: TMessage);
const
  OBJID_NATIVEOM = $FFFFFFF0;
begin
  if DWORD(Message.LParam) = OBJID_NATIVEOM then
    // Returns the object which you want to obtain
    // PIObject is a reference to the IDispatch interface implemented by the desired object

    Message.Result := LresultFromObject(IDispatch, Message.WParam, PIObject);
  else
    Message.Result := DefWindowProc(Handle, Message.Msg, Message.WParam, Message.LParam);
end;

C#

// Include the needed assemblies
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
  
namespace MSAAApplication
{
  
  ...
  
  public class Form1 : System.Windows.Forms.Form
  {
    // Message identifiers
    private const int WM_GETOBJECT = 0x003D;
    private const uint OBJID_NATIVEOM = 0x0FFFFFFF0;
    
  ...
  
    // Modified window procedure. Includes code for processing the WM_GETOBJECT message.
    protected override void WndProc(ref Message m)
    {
      // Checks the message
      if ((m.Msg == WM_GETOBJECT) & ((uint)m.LParam == OBJID_NATIVEOM))
      {
        // Obtains the GUID of the IDispatch interface
        Guid g = new Guid(Win32.IID_IDispatch);
    
        // Returns the object, which you want to obtain via MSAA
        // IObjectDispatch is a reference to the IDispatch interface implemented by the desired object
        m.Result=Win32.LresultFromObject(ref g, m.WParam, IObjectDispatch);
        
        return;
      }
      else
      {
        // Calls the default window procedure
        base.WndProc(ref m);
      }
    }
  }
  
  // A helper class needed to call Win32 API functions
  public class Win32
  {
    // Imports the LresultFromObject function from oleacc.dll
    [DllImport("oleacc.dll", CharSet=CharSet.Auto)]
    public static extern IntPtr LresultFromObject(ref Guid riid, IntPtr wParam, [MarshalAs(UnmanagedType.IDispatch)] object pAcc);
    // GUID of the IDispatch interface
    public const string IID_IDispatch="00020400-0000-0000-C000-000000000046";
  }
}

Visual Basic

' A data type for working with GUID values
Private Type GUID
  Data1 As Long
  Data2 As Integer
  Data3 As Integer
  Data4(7) As Byte
End Type

' Declarations of external functions
Private Declare Function CLSIDFromString Lib "OLE32" (ByVal lpszCLSID As Long, pclsid As GUID) As Long
Private Declare Function LresultFromObject Lib "oleacc.dll" (ByRef riid As GUID, ByVal wParam As Long, ByVal pAcc As Object) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

Global glPrevWndProc As Long' Saves the address of the original window procedure


' Registers the modified window procedure
Public Function fSubClass() As Long
  fSubClass = SetWindowLong(Form1.hwnd, GWL_WNDPROC, AddressOf pMyWindowProc)
End Function
' The following routine restores the original window procedure
Public Sub pUnSubClass()
  Call SetWindowLong(Form1.hwnd, GWL_WNDPROC, glPrevWndProc)
End Sub

' A modified window procedure. Includes special processing of the WM_GETOBJECT message
Public Function pMyWindowProc(ByVal hw As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  
  ' Message identifiers
  Const OBJID_NATIVEOM = &HFFFFFFF0
  Const WM_GETOBJECT = &H3D
  
  Dim hr As Long
  Dim riid As GUID
  
  ' Checks the message
  If (uMsg = WM_GETOBJECT) And (lParam = OBJID_NATIVEOM) Then
    ' Obtains the identifier of the IDispatch interface
    hr = CLSIDFromString(StrPtr(IID_IDispatch), riid)
    If hr <> 0 Then Err.Raise hr
  
    ' Returns the object, the reference to which you want to obtain via MSAA.
    ' Obj is a reference to the IDispatch interface implemented by the desired object.
    ' If Obj is empty, the application is terminated without displaying any error message
    pMyWindowProc = LresultFromObject(riid, wParam, Obj)
    Exit Function
  End If
  ' Passes the message to the default window procedure
  pMyWindowProc = CallWindowProc(glPrevWndProc, hw, uMsg, wParam, lParam)
End Function

Working With the Object Returned by the Application

You get access to methods and properties of the object, which the application returns, through the NativeObject property that TestComplete appends to windows and controls. The methods and properties of the object become the methods and properties of the NativeObject object. You can call them in tests using the syntax like MyWindowTestObj.NativeObject.Property_Name.

You can view the available methods and properties in the Object Browser. To do this, simply click the ellipsis button of the NativeObject property.

Note: Some methods and properties can be hidden. To view them, enable the Show hidden members option in the TestComplete Engines - General Options dialog.

The following example demonstrates how you can use NativeObject. The code snippet gets the accDescription property of the Microsoft Word ribbon toolbar. The "Document1" file should be open in the Word application:

JavaScript, JScript

function Test()
{
  // Get the object
  var NativeObj = Sys.Process("WINWORD").Form("Document1 - Microsoft Word").Panel("MsoDockTop").ToolBar("Ribbon").NativeObject;
  // Post its accDescription property to the log
  Log.Message(NativeObj).accDescription(0);
}

Python

def Test():
  # Get the object
  NativeObj = Sys.Process("WINWORD").Form("Document1 - Microsoft Word").Panel("MsoDockTop").ToolBar("Ribbon").NativeObject
  # Post its accDescription property to the log
  Log.Message(NativeObj).accDescription(0)

VBScript

Sub Test
  ' Get the object
  NativeObj = Sys.Process("WINWORD").Form("Document1 - Microsoft Word").Panel("MsoDockTop").ToolBar("Ribbon").NativeObject
  ' Post its accDescription property to the log
  Log.Message(NativeObj.accDescription(0))
End Sub

DelphiScript

procedure Test();
var
  NativeObj;
begin
  // Get the object
  NativeObj := Sys.Process('WINWORD').Form('Document1 - Microsoft Word').Panel('MsoDockTop').ToolBar('Ribbon').NativeObject.accDescription(0);
  // Post its accDescription property to the log
  Log.Message(NativeObj);
end;

C++Script, C#Script

function Test()
{
  // Get the object
  var NetiveObj = Sys["Process"]("WINWORD")["Form"]("Document1 - Microsoft Word")["Panel"]("MsoDockTop")["ToolBar"]("Ribbon")["NativeObject"]["accDescription"](0);
  // Post its accDescription property to the log
  Log["Message"](NetiveObj);
}

Working With Custom Objects Returned by the Application

When TestComplete requests an IAccessible reference from a window or control, it actually sends a request for an IDispatch object (IDispatch is an ancestor of IAccessible).

Your application can also return a different object as long as it is inherited from IDispatch. This object can provide accessibility information or contain any other methods and properties. In your tests, you will access the methods and properties of this object through the NativeObject property.

See Also

Using Microsoft Active Accessibility
About Using Microsoft Active Accessibility

Highlight search results