Developing Applications that Use Active Accessibility


This section provides information useful to accessibility aid developers. Information is divided into the following topics.

Active Accessibility Clients

Any application that uses Active Accessibility to gain information about objects is considered a client. Typically, clients are accessibility aids, such as screen readers, screen enlargement utilities for low-vision users, and speech recognition utilities. However, Active Accessibility provides technology useful to many types of applications. For example, testing tools can use Active Accessibility to query the state of applications and objects. Development tools, such as object browsers, might need the run-time information that Active Accessibility provides.

To implement an Active Accessibility client, you must know how to call an accessible object's IDispatch or IAccessible interface methods. For more information on IDispatch, see IDispatch Interface.

Setting a WinEvent Hook

To set a WinEvent hook, you must register a hook procedure by calling the SetWinEventHook function. This function accepts seven parameters that describe the events you want notifications for, the location of the event hook procedure, and other environment variables. The first two parameters, eventMin and eventMax, are event constant values that inclusively declare the lower and upper events you want notifications about. The third parameter, hmodWinEventProc, accepts a handle to the module that contains an in-context hook procedure—this parameter is ignored if the WINEVENT_INCONTEXT flag isn't specified. The fourth parameter, lpfnWinEventProc, is the address of the function that Active Accessibility will call when any of the requested events occurs. The fifth and sixth parameters, idProcess and idThread, are used to identify the specific process and thread to be monitored; setting these to zero indicates that you want to monitor all processes and threads. The last parameter, dwflags, is a bit field that you can use to specify receiving parameters.

The following code fragment, taken from the inspect.cpp file included with this SDK, removes a preexisting event hook or sets a new one. The new hook procedure is called for a specific event (EVENT_OBJECT_FOCUS), is out-of-context, and applies to all threads and processes.


  // Set up an event hook.
  hEventHook = SetWinEventHook(EVENT_MIN,         // We want all events
                               EVENT_MAX,
                               GetModuleHandle(NULL),// Use this module
                               WinEventProc,
                               0,        // All processes
                               0,        // All threads
                               WINEVENT_OUTOFCONTEXT);
    // Did we install correctly? 

    if (hEventHook)
        return(TRUE);

    // Did not install properly - fail
    return(FALSE);

For more information, see WinEvents and About the WinEventProc Callback Function.

Getting an Accessible Object Interface Pointer

This section contains information about the various ways that Active Accessibility client applications can retrieve interface pointers to accessible objects. The following topics are discussed.

Using AccessibleObjectFromEvent

Many client applications need to look up information about specific accessible objects that have generated events. Since the IAccessible interface is the "gateway" to accessible objects, clients must have an easy way to associate WinEvents with the IAccessible interface of the object generating the events. Active Accessibility provides the AccessibleObjectFromEvent function specifically for this purpose. Other times, a client might want to access to an object based on its location, or window handle. Active Accessibility provides the AccessibleObjectFromPoint and AccessibleObjectFromWindow functions.

AccessibleObjectFromEvent accepts much of the same information that a client application receives with an event notification. In effect, when a client hook procedure receives an event notification, it can "recycle" most of the parameters it receives with the notification to call the AccessibleObjectFromEvent function and retrieve the generating object's IAccessible interface pointer.

The AccessibleObjectFromEvent function accepts five parameters total. The first three parameters, hwnd, dwId, and dwChildId, receive information taken directly from the WinEventProc callback function. The hwnd parameter receives the callback function's hwnd parameter, the dwId parameter gets the idChild parameter. The remaining two parameters that AccessibleObjectFromEvent accepts will contain the address of an object's IAccessible interface and a status value. The status value, received in the pvarChild parameter, indicates whether the provided interface belongs to the object that generated the event or its parent object. If a parent object is specified, then the server assumes the responsibility of supporting IAccessible for the child object.

AccessibleObjectFromEvent increases an object's reference count, and must have a corresponding Release to lower the reference count. Even though an object has a reference count greater than zero, that object can still be destroyed, and clients are not guaranteed that getting properties from or calling methods on an object will succeed. This is what Word does with its objects when it quits: As with usual OLE Automation objects, Word will quit and Active Accessibility will CoDisconnect the object so that if anyone tries to access it cross-process after the application quits, OLE will return an error. A reference count on a Active Accessibility object does not reference count the application.

Using AccessibleObjectFromPoint

Accessibility aids can use the AccessibleObjectFromPoint function to retrieve the address of an object's IAccessible interface. This function accepts three parameters that describe a point on the screen, and two output variables used to state the call's result. The first parameter, ptScreen, is a POINT structure that describes the x- and y-coordinates of the on-screen location to be tested. The second parameter, ppacc, is the address of a variable that will contain an IAccessible interface pointer. The last parameter, pvarChild, is the address of a VARIANT structure that will contain a value that describes the call's result. The vt member is always set to VT_I4, which means that the lVal member contains the value you need. Since it's possible that the item at the point you specified was over a simple element, the value in lVal tells you whether the retrieved interface pointer belongs to an object at the specified point, or to a simple element's parent object. If lVal is nonzero, then the function retrieved an interface pointer to a simple element's parent object; if lVal is zero, then the retrieved interface belongs to the object itself.

AccessibleObjectFromPoint increases the an object's reference count, and must have a corresponding Release(); . Even though an object's reference count is greater than zero, that object can still be destroyed, and clients are not guaranteed that getting properties from or calling methods on an object will succeed. This is what Word does with its objects when it quits: As with usual OLE Automation objects, Word will quit and Active Accessibility will CoDisconnect the object so that if anyone tries to access it cross-process after the application quits, OLE will return an error. A reference count on a Active Accessibility object does not reference count the application.

The following code fragment provides an example of how to use the AccessibleObjectFromPoint function.


// For this example, assume that the g_szName variable 
// is defined as a global LPTSTR variable.

// Get the current cursor location.
POINT ptCursor
GetCursorPos(&ptCursor);

// Setup variables for interface pointers and return value.
IUnknown*    punk    = NULL;
IAccessible* pOleAcc = NULL;
HRESULT      hr;
VARIANT      varChild;

// See if there is an accessible object under the cursor.
hr = AccessibleObjectFromPoint(ptCursor, &pOleAcc, &varChild);
if (SUCCEDED(hr)) 
{
    // Query on the object's name.
    BSTR bstrName = NULL;
    hr = pOleAcc->get_accName(varChild, &bstrName);

    // If a name was returned without error,
    // convert it to an OLE Unicode string.
    if (bstrName && SUCCEDED(hr)) 
    {
        WideCharToMultiByte(CP_ACP, 0, bstrName,-1, g_szName,
            cchName, NULL, NULL);
        SysFreeString(bstrName);
    }
}

Retrieving the Object with Keyboard Focus

By using the IAccessible::get_accFocus method, you can retrieve the child object that has keyboard focus. Keep in mind that only one object in the system at a time can have keyboard focus. This method accepts the address of a single VARIANT structure, which it fills to represent the call's result. After making the call, you can look at the value in the structure's vt member to find the result.

If a child object has keyboard focus, then the vt member is set to VT_I4 or VT_DISPATCH. This value tells you whether to look for information in the form of a child ID or an IDispatch interface pointer, held in the lVal or pdispVal members, respectively. If the object had no children with keyboard focus, the vt member will be VT_EMPTY.

Do not confuse object focus with object selection. For more information, see Accessible Object Selection and Focus and Retrieving Selected Objects and Children. For more information about the VARIANT structure, see VARIANT Structure.

Retrieving Selected Objects and Children

By using the IAccessible::get_accSelection method, you effectively ask an object to retrieve information about any of its currently selected children. This method accepts the address of a single VARIANT structure, which it fills to represent the number of selected objects and indicate the type of information retrieved. After calling get_accSelection, you can use the value in the vt member to determine the call's result.

If one object is selected, then the application sets the vt member to VT_I4 or VT_DISPATCH. This value tells you whether to look for information in the form of a child ID or an IDispatch interface pointer, held in the lVal or pdispVal members, respectively. It is possible that the object you're working with has no children, but is selectable. In this case, vt will be VT_I4 and the lVal member will be CHILDID_SELF.

If multiple objects were selected, getting information about them is a little more complex. The server that handled your get_accSelection call sets the vt member to VT_UNKNOWN and the punkVal member to a valid IUnknown interface pointer. You can call this interface's QueryInterface method to request an IEnumVARIANT interface, which you can use to enumerate the selected objects. The IEnumVARIANT interface provides a method for enumerating a collection of variants, including heterogeneous collections of objects and intrinsic types. It is the server's responsibility to support this interface to allow you to enumerate the selected children. This is the preferred method.

Do not confuse selection with focus. Any or all objects in the system can simultaneously have selected children, but only one can have keyboard focus. For more information, see Accessible Object Selection and Focus and Retrieving the Object with Keyboard Focus.

Selecting Child Objects

Applications call the IAccessible::accSelect method to modify selection or keyboard focus among the child object(s) within the current object. The flags you specify with the call define the operation you want to perform.

The following list describes how you can use IAccessible::accSelect to perform complex selection operations.

To simulate a click:
Specify the SELFLAG_TAKEFOCUS flag.
To simulate CTRL + click:
To select the target item, specify a combination of the SELFLAG_TAKEFOCUS and SELFLAG_ADDSELECTION flags. Or, to cancel selection of the target item, call the method specifying a combination of the SELFLAG_TAKEFOCUS and SELFLAG_REMOVESELECTION flags.
To simulate SHIFT + click:
Call ::accSelect, specifying a combination of the SELFLAG_TAKEFOCUS and SELFLAG_EXTENDSELECTION flags.
To select a range of objects:
This requires two calls: in the first, specify SELFLAG_TAKEFOCUS on the starting object; in the second, specify a combination of SELFLAG_EXTENDSELECTION and SELFLAG_TAKEFOCUS on the last object.
To cancel selection of all objects:
Specify a combination of SELFLAG_REMOVESELECTION and SELFLAG_TAKEFOCUS.

Active Accessibility Client Tutorial

The Active Accessibility SDK includes a simple client application, called Babble. In addition to Babble's executable file, the SDK includes the sample's source code, header files, and Microsoft® Visual C++® project workspace files. This tutorial sequentially discusses the tasks that Babble performs and, in doing so, presents the basic information you need to develop your own Active Accessibility client application.

The tutorial contains the following steps.

Additionally, the following topic contains background information.

About the Babble Sample Application

Babble is a simple application that provides information about accessible objects and announces environment changes regarding focus and activation events. Babble combines Active Accessibility technology and the Microsoft Speech SDK to vocalize events and object information to the user. This sample uses both WinEvent and standard keyboard hooks to capture events related to accessible objects and global keyboard events. As a result, the Babble project workspace includes a "Babhook" project for the DLL code to grab all keyboard events.

Step 1: Initialize the COM Library

Before you can start using Active Accessibility's features, you must initialize the Component Object Model (COM) library by calling the CoInitialize OLE function. Babble calls this function before initializing the text to speech engine in its InitTTS application-defined function. InitTTS does several things specific to the speech engine after initializing COM, but the following code fragment illustrates how it calls CoInitialize.

// Attempt to initialize OLE.
if (FAILED(CoInitialize(NULL))) 
    return FALSE;

CoInitialize accepts only one parameter, pvReserved, that must always be set to NULL. The function returns a value that indicates success or failure—Babble uses the FAILED macro to test for a failure condition. If the call fails, then COM could not be initialized and application cannot successfully run. FAILED is a macro found in the COM interface.

Step 2: Set a WinEvent Hook

After initializing the COM library, you must set a WinEvent hook by calling the SetWinEventHook function. The Babble application calls this function in its InitMSAA application-defined function. The following code illustrates how to call SetWinEventHook.

BOOL InitMSAA(void) {
  // Set up an event hook.
  hEventHook = SetWinEventHook(EVENT_MIN,         // We want all events
                               EVENT_MAX,
                               GetModuleHandle(NULL),// Use this module
                               WinEventProc,
                               0,        // All processes
                               0,        // All threads
                               WINEVENT_OUTOFCONTEXT);
    // Did we install correctly? 
    if (hEventHook)
        return(TRUE);

    // Did not install properly - fail
    return(FALSE);
}

Note that InitMSAA specifies the EVENT_MIN and EVENT_MAX event constants. This causes Active Accessibility to notify the hook procedure for all possible events. Additionally, InitMSAA tests the returned hook identifier to determine if the hook was successfully installed. If the return value is non-NULL, then the call succeeded.

For more information, see WinEvents.

Step 3: Respond to Event Notifications

Once a WinEvent hook is successfully in place, your hook procedure will begin receiving event notifications. The following function, taken from the Babble sample application, shows an example of how a hook procedure can receive event notifications and process them.

void CALLBACK WinEventProc(HWINEVENTHOOK hEvent, DWORD event,
    HWND hwndMsg, LONG idObject, LONG idChild, DWORD idThread, DWORD dwmsEventTime)
{
    // What type of event is coming through?
    switch (event) {
        case EVENT_OBJECT_FOCUS:
            // Focus is changing.
            OnFocusChangedEvent(event, hwndMsg, 
                                idObject, idChild,
                                dwmsEventTime);
            break;
    	}
    return;
}

For simplicity, Babble's WinEventProc function responds only to EVENT_OBJECT_FOCUS events, calling another application-defined function to process them (your client application would probably respond to far more events). When the hook procedure receives an EVENT_OBJECT_FOCUS event, Babble calls the following OnFocusChangedEvent application-defined function to retrieve an interface pointer based on the information Babble received with the notification.

BOOL OnFocusChangedEvent(DWORD event, HWND hwnd,
    LONG idObject, LONG idChild, DWORD dwmsTimeStamp)
{
    VARIANT      varChild;
    IAccessible* pIAcc;
    HRESULT      hr;
    int          cchName = 128;
    TCHAR        tszName[128];
    TCHAR        tszSpeak[1024];
    
    // Important to init variants
    VariantInit(&varChild);

    hr = AccessibleObjectFromEvent(hwnd, (DWORD)idObject, (DWORD)idChild, &pIAcc, &varChild);
    
    // Check to see if we got a valid pointer
    if (SUCCEEDED(hr)) 
    {
        OBJINFO objCurrent;

        objCurrent.hwnd = hwnd;
        objCurrent.plObj = (long*)pIAcc;
        objCurrent.varChild = varChild;

        GetObjectName(&objCurrent, tszName, cchName);

        wsprintf(tszSpeak, "Focus now %s.", tszName);
        
        SpeakString(tszSpeak);

        if (pIAcc)
             pIAcc->Release();

        return(TRUE);
     }
    
     // No object was retrieved, so send a failure value.
    return(FALSE);
}

OnFocusChangedEvent uses the AccessibleObjectFromEvent function to retrieve information about the object that generated the event. Note that AccessibleObjectFromEvent doesn't accept an event value. At first this might seem odd, but Babble already knows about what event happened; now it needs information about who generated it. Knowing this, it makes sense that AccessibleObjectFromEvent only needs information about the window, object, and child element associated with the event. Also note that Babble initializes the VARIANT structure it sends with the function call to comply with OLE Automation standards.

When AccessibleObjectFromEvent returns, Babble tests the return value with the SUCCEEDED macro. SUCCEEDED is a macro found in the COM interface. If the call succeeded, it uses Microsoft Speech SDK API elements to vocalize the event, then releases the retrieved interface by calling its IUnknown::Release function. This step is required to meet COM standards.

Step 4: Close the COM Library

When your application no longer needs COM services, use the CoUninitialize OLE function to close the COM library. Closing the COM library is required by COM standards; failing to do so will result in memory leaks. Babble calls CoUninitialize when it closes the text to speech engine in its UnInitTTS application-defined function.

Converting Unicode and ANSI Strings

Active Accessibility uses Unicode strings, as defined by the BSTR data type. If your application does not use Unicode strings, or if you want to convert strings for certain API calls, use the MultiByteToWideChar and WideCharToMultiByte Win32 functions to perform necessary conversion. You can use WideCharToMultiByte to convert a Unicode string to an ANSI string; the MultiByteToWideChar function does the opposite. The Babble sample application uses the WideCharToMultiByte function in several places to perform Unicode to ANSI string conversion. The following application-defined function, from the Babble sources, illustrates how to properly call the function.

DWORD GetObjectName(LPOBJINFO poiObj,LPSTR lpszBuf, int cchBuf)
{
    DWORD dwRetVal;
    BSTR  bszName;
    IAccessible* pIAcc;
    long* pl;

    bszName = NULL;

    // Get the object out of the structure.
    pl = poiObj->plObj;

    pIAcc =(IAccessible*)pl;

    // Get the object's name.
    pIAcc->get_accName(poiObj->varChild, &bszName);
    
    // Did we get name string?
    if (bszName)
    {
        // Convert the string Unicode to ANSI.
        if (WideCharToMultiByte(CP_ACP, 0, 
                            bszName,
                            WC_SEPCHARS, // -1
                            lpszBuf,
                            cchBuf,
                            NULL, NULL))
        {
            SysFreeString(bszName);

            dwRetVal = NO_ERROR;
        }
        else
        {
            dwRetVal = GetLastError();
        }

        return(dwRetVal);
    }
        
    
    // Need general failure handling routine
    MessageBeep(MB_ICONEXCLAMATION);
    
    return(ERROR_INVALID_FUNCTION);
}

For more information about these string conversion functions, see their references in the Win32 SDK.

Including Active Accessibility with your Accessibility Aids for Client Applications

You can get Active Accessibility in two forms. The first is this Active Accessibility Software Development Kit (SDK). The Active Accessibility SDK enables developers to create accessibility aids and accessible applications using the Active Accessibility technology. The SDK includes the core components, new dynamic-link libraries (DLLs), sample source code, libraries, header files, sample applications, and documentation. Additionally, the Active Accessibility Redistribution Kit is available. This kit includes the core components and new DLLs, which you can ship with an accessibility aid or accessible application.

If an independent software vendor (ISV) wants to incorporate Active Accessibility in a product, he or she must redistribute the Redistribution Kit. This self-installing package presents no user interface and ensures that the core components are installed correctly.

Typically, an accessibility aid's installation program will execute the Redistribution Kit software as a final step, and then exit. The Redistribution Kit unpacks the various components, performs version checking, registers the IAccessible interface, and restarts the machine to complete the installation.

Note: ISVs are not allowed to set up the Active Accessibility components individually. The license agreement provided with the SDK and Redistribution Kit allows Active Accessibility to be distributed only through the Redistribution Kit.

The following table lists the components that comprise the Redistribution Kit.
Component Name Description
Comctl32.dll New functionality to support WinEvents
Gdi.exe New functionality for DDI Redirector
Gdi32.exe 32-bit version of Gdi.exe
Oleacc.dll Provides helper functions, marshaling support, and providers for system components
Oleaut32.dll Newer version than what shipped with Windows 95 to support marshaling of interfaces
Stdole2.tlb Companion to Oleaut32.dll
User.exe New functionality for WinEvents and screen reader behavior changes.
User32.dll 32-bit version of User.exe

Writing Platform Independent Applications

The first release of Microsoft Active Accessibility supports Microsoft Windows 95 only; however, clients and servers might need to run on Windows NT® as well. Active Accessibility introduces new APIs that are not yet available on the Windows NT platform. As a result, instead of relying on import libraries to call new APIs, an MSAA application should use GetProcAddress to dynamically address new USER functions.

For example, an application that wants to use NotifyWinEvent should call GetProcAddress() using the module handle of USER to get the address of the API. If the call is successful, meaning that this version of Windows supports MSAA, then the application can set a flag indicating that it is safe to call NotifyWinEvent. The address received from GetProcAddress() can be stored in a function pointer variable and used throughout the code. See the sample source code for the server application for details.

© 1997 Microsoft Corporation. All rights reserved. Legal Notices.