/******************************************************************************\
*       This is a part of the Microsoft Source Code Samples. 
*       Copyright (C) 1993 Microsoft Corporation.
*       All rights reserved. 
*       This source code is only intended as a supplement to 
*       Microsoft Development Tools and/or WinHelp documentation.
*       See these sources for detailed information regarding the 
*       Microsoft samples programs.
\******************************************************************************/

/*----------------------------------------------------------------------------
|                                                                              |
*|   oac.cpp    - Windows message spy application                               |
|                                                                              |
\*----------------------------------------------------------------------------*/

#include <windows.h>
#include <initguid.h>
#include <objbase.h>
#include <objerror.h>
#include <ole2.h>
#include <oleauto.h>
#include <tchar.h>
#include <oleacc.h>
#include "resource.h"
#include "oacspy.h"

// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
//
//    Supporting functions.
//
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------

VOID PrintDebug(
    LPTSTR fmt,
    ...
    )
{
#ifdef DEBUG
    HFILE hf;
    va_list marker;
    TCHAR szBuf[256];
    TCHAR buf[2];
    OFSTRUCT of;
    buf[0]=13; buf[1]=10;

    va_start(marker, fmt);
    wvsprintf(szBuf, fmt, marker);
    va_end(marker);

OutputDebugString(szBuf);
OutputDebugString("\r\n");
//return;

    hf = _lopen(".\\debug.txt", OF_WRITE);
    if (hf == HFILE_ERROR) hf = OpenFile(".\\debug.txt", &of, OF_CREATE);
    _llseek(hf, 0, FILE_END);
    _lwrite(hf, szBuf, lstrlen(szBuf));
    _lwrite(hf, buf, sizeof(TCHAR)*2);
    _lclose(hf);
#endif

}


void BstrToStr(LPSTR str, BSTR bstr)
{
    int n = SysStringLen(bstr);
    int i;
    if (!str || !bstr) return;
    for (i=0; i<n && i<63; i++)
    {
		str[i] = (char)bstr[i];
    }
    str[i]=0;
}

void HResultText(LPSTR lpOut, HRESULT hr)
{
        char szTemp[64];

	switch (hr)
	{
		
                case S_FALSE: lstrcpy(szTemp, "S_FALSE"); break;
                case E_NOTIMPL: lstrcpy(szTemp, "E_NOTIMPL"); break;
                case E_FAIL: lstrcpy(szTemp, "E_FAIL"); break;
                case STG_E_INSUFFICIENTMEMORY: lstrcpy(szTemp, "STG_E_INSUFFICIENTMEMORY"); break;
                case E_OUTOFMEMORY: lstrcpy(szTemp, "E_OUTOFMEMORY"); break;
                case E_INVALIDARG: lstrcpy(szTemp, "E_INVALIDARG"); break;
                case DISP_E_UNKNOWNNAME: lstrcpy(szTemp, "DISP_E_UNKNOWNNAME"); break;
                case DISP_E_UNKNOWNLCID:lstrcpy(szTemp, "DISP_E_UNKNOWNLCID"); break;
                case TYPE_E_IOERROR: lstrcpy(szTemp, "TYPE_E_IOERROR"); break;
                case TYPE_E_INVDATAREAD: lstrcpy(szTemp, "TYPE_E_INVDATAREAD"); break;
                case TYPE_E_UNSUPFORMAT: lstrcpy(szTemp, "TYPE_E_UNSUPFORMAT"); break;
                case TYPE_E_INVALIDSTATE: lstrcpy(szTemp, "TYPE_E_INVALIDSTATE"); break;
                case TYPE_E_WRONGTYPEKIND: lstrcpy(szTemp, "TYPE_E_WRONGTYPEKIND"); break;
                case DISP_E_BADPARAMCOUNT: lstrcpy(szTemp, "DISP_E_BADPARAMCOUNT");break;
                case DISP_E_BADVARTYPE: lstrcpy(szTemp, "DISP_E_BADVARTYPE");break;
                case DISP_E_EXCEPTION: lstrcpy(szTemp, "DISP_E_EXCEPTION");break;
                case DISP_E_MEMBERNOTFOUND: lstrcpy(szTemp, "DISP_E_MEMBERNOTFOUND"); break;
                case DISP_E_NONAMEDARGS: lstrcpy(szTemp, "DISP_E_NONAMEDARGS");break;
                case DISP_E_OVERFLOW: lstrcpy(szTemp, "DISP_E_OVERFLOW");break;
                case DISP_E_PARAMNOTFOUND: lstrcpy(szTemp, "DISP_E_PARAMNOTFOUND");break;
                case DISP_E_TYPEMISMATCH: lstrcpy(szTemp, "DISP_E_TYPEMISMATCH");break;
                case DISP_E_UNKNOWNINTERFACE: lstrcpy(szTemp, "DISP_E_UNKNOWNINTERFACE");break;
                case DISP_E_PARAMNOTOPTIONAL: lstrcpy(szTemp, "DISP_E_PARAMNOTOPTIONAL");break;
                case DISP_E_NOTACOLLECTION: lstrcpy(szTemp, "DISP_E_NOTACOLLECTION"); break;
		default: 
		{
			char buf[32];
			wsprintf(buf, "Error %lx", hr);
                        lstrcpy(szTemp, buf);
		}
	}
        lstrcpy(lpOut, szTemp);
}

BOOL IsHResultError(HRESULT hr, LPSTR szText)
{
    char buf[128];
    char szHR[64];
    if (hr == S_OK) return FALSE;
    HResultText(szHR, hr);
    wsprintf(buf, "HRESULT error %s: %s", szHR, szText);
    PrintDebug(buf);
    return TRUE;
}



// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
//
//    IAccessible support.
//
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------


// --------------------------------------------------------------------------
//
//  GetObjectName
//
// --------------------------------------------------------------------------
HRESULT GetObjectName(IAccessible * pOleAcc, LONG index, LPTSTR lpszName,
    UINT cchName)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    BSTR bstrName;

    PrintDebug("GetObjectName: pOleAcc %lx, index %ld, lpszName %lx, cchName %u",
        pOleAcc, index, lpszName, cchName);

    VariantInit(&varChild);
    *lpszName = 0;
    bstrName = NULL;

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hResult = pOleAcc->get_accName(varChild, &bstrName);
    if (IsHResultError(hResult, "get_accName"))
    {
        return(hResult);
    }

    if (bstrName)
    {
        WideCharToMultiByte(CP_ACP, 0, bstrName, -1, lpszName, cchName, NULL, NULL);
        SysFreeString(bstrName);
    }

    PrintDebug("get_accName: %s", lpszName);

    return hResult;
}



// --------------------------------------------------------------------------
//
//  GetObjectValue
//
// --------------------------------------------------------------------------
HRESULT GetObjectValue(IAccessible * pOleAcc, LONG index, LPTSTR lpszValue,
    UINT cchValue)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    BSTR bstrValue;

    PrintDebug("GetObjectValue");

    VariantInit(&varChild);
    *lpszValue = 0;
    bstrValue = NULL;

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hResult = pOleAcc->get_accValue(varChild, &bstrValue);
    if (IsHResultError(hResult, "get_accValue"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (bstrValue)
    {
        WideCharToMultiByte(CP_ACP, 0, bstrValue, -1, lpszValue, cchValue, NULL, NULL);
        SysFreeString(bstrValue);
    }

    PrintDebug("get_accValue: %s", lpszValue);

//AllDone:
    return hResult;
}


// --------------------------------------------------------------------------
//
//  GetObjectRole()
//
// --------------------------------------------------------------------------
HRESULT GetObjectRole(IAccessible * pOleAcc, LONG index, LPTSTR lpszRole,
    UINT cchRole)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    VARIANT varRetVal;

    PrintDebug("GetObjectRole");

    VariantInit(&varRetVal);
    VariantInit(&varChild);
    varChild.vt = VT_I4;
    varChild.lVal = index;

    *lpszRole = 0;

PrintDebug("bf get_accRole");
    hResult = pOleAcc->get_accRole(varChild, &varRetVal);
PrintDebug("af get_accRole");
    if (IsHResultError(hResult, "accRole"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (varRetVal.vt == VT_I4)
    {
#ifdef DEBUG // GetRoleText API tests
// User actions: Bring up Spy Window. Point at objects.
        UINT cchRet, cch;
        PrintDebug("Begin GetRoleText tests");
        // Normal
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, cchRole);
        if (!cchRet) {PrintDebug("GetRoleText failed"); goto EndGRTTest;}
        // NULL return string to get string size
        cch = GetRoleText(varRetVal.lVal, NULL, cchRole);
        if (!cch) PrintDebug("NULL lpsz in GetRoleText returns 0 string size", cchRet);
        // Full string size
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, cch);
        if (cchRet!=cch-1) PrintDebug("Full buffer size for GetRoleText. cchRet %u unexpected", cchRet);
        // Full string size + 1
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, cch+1);
        if (cchRet!=cch) PrintDebug("Full buffer size+1 for GetRoleText. cchRet %u unexpected", cchRet);
        // cch 2
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, 2);
        if (cchRet!=1) PrintDebug("Buffer size 2 for GetRoleText. cchRet %u", cchRet);
        // cch 1
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, 1);
        if (cchRet!=0) PrintDebug("Buffer size 1 for GetRoleText. cchRet %u", cchRet);
        // bad role id
        cchRet = GetRoleText(0xf111f111, lpszRole, cchRole);
        if (cchRet) PrintDebug("Bad role id in GetRoleText wrongly succeeded");
        // bad return string
        cchRet = GetRoleText(varRetVal.lVal, (LPTSTR)0x1fff1fff, cchRole);
        if (cchRet) PrintDebug("Bad lpsz in GetRoleText wrongly succeeded");
        //0 cch
        cchRet = GetRoleText(varRetVal.lVal, lpszRole, 0);
        if (cchRet) PrintDebug("0 cch in GetRoleText wrongly succeeded");
EndGRTTest:
        PrintDebug("End GetRoleText tests");
#endif
        GetRoleText(varRetVal.lVal, lpszRole, cchRole);
    }
    else if (varRetVal.vt == VT_BSTR)
    {
        WideCharToMultiByte(CP_ACP, 0, varRetVal.bstrVal, -1, lpszRole,
            cchRole, NULL, NULL);
    }

    VariantClear(&varRetVal);
    PrintDebug("get_accRole: %s", lpszRole);
    return hResult;
}



// --------------------------------------------------------------------------
//
//  GetObjectState()
//
// --------------------------------------------------------------------------
HRESULT GetObjectState(IAccessible * pOleAcc, LONG index, LPTSTR lpszState,
    UINT cchState)
{
    HRESULT hr = S_OK;
    VARIANT varChild;
    VARIANT varRetVal;

    PrintDebug("GetObjectState");

    VariantInit(&varChild);
    VariantInit(&varRetVal);
    varChild.vt = VT_I4;
    varChild.lVal = index;

    *lpszState = 0;

    hr = pOleAcc->get_accState(varChild, &varRetVal);
    if (IsHResultError(hr, "get_accState"))
    {
//        DebugBreak();
        return(hr);
    }

    if (varRetVal.vt == VT_I4)
    {
        int   iStateBit;
        DWORD lStateBits;
        LPSTR lpszT;

        lpszT = lpszState;

#ifdef DEBUG // GetStateText API tests
// User actions: Bring up Spy Window. Point at objects.
    {
        UINT cchRet;
        PrintDebug("Begin GetStateText tests");
        // Normal
        cchRet = GetStateText(0, lpszState, cchState);
        if (!cchRet) {PrintDebug("GetStateText failed"); goto EndGSTTest;}
        // cch=2
        cchRet = GetStateText(0, lpszState, 2);
        if (cchRet!=1) PrintDebug("Buffer size 2 for GetStateText. cchRet=%u", cchRet);
        // bad State id
        cchRet = GetStateText(0xf111f111, lpszState, cchState);
        if (cchRet) PrintDebug("Bad State id in GetStateText wrongly succeeded");
        // NULL return string
        cchRet = GetStateText(0, NULL, cchState);
        if (!cchRet) PrintDebug("NULL lpsz in GetStateText should not return string size 0");
        // bad return string
        cchRet = GetStateText(varRetVal.lVal, (LPTSTR)0x1fff1fff, cchState);
        if (cchRet) PrintDebug("Bad lpsz in GetStateText wrongly succeeded");
        //0 cch
        cchRet = GetStateText(varRetVal.lVal, lpszState, 0);
        if (cchRet) PrintDebug("0 cch in GetStateText wrongly succeeded");
EndGSTTest:
        PrintDebug("End GetStateText tests");
    }
#endif

        //
        // Convert state flags to comma separated list.
        //
        for (iStateBit = 0, lStateBits = 1; iStateBit < 32; iStateBit++, (lStateBits <<= 1))
        {
            if (varRetVal.lVal & lStateBits)
            {
                lpszT += GetStateText(lStateBits, lpszT, cchState);
                *lpszT++ = ',';
                *lpszT++ = ' ';
            }
        }

        //
        // Clip off final ", "
        //
        if (varRetVal.lVal)
        {
            *(lpszT-2) = 0;
            *(lpszT-1) = 0;
        }
    }
    else if (varRetVal.vt == VT_BSTR)
    {
        WideCharToMultiByte(CP_ACP, 0, varRetVal.bstrVal, -1, lpszState,
            cchState, NULL, NULL);

        // BOGUS!  VariantClear() will free the BSTR, don't do this twice.
        // SysFreeString(varRetVal.bstrVal);
    }
    else
    {
        // Don't recognize it
        hr = E_FAIL;
    }

    PrintDebug("get_accState: %s", lpszState);
    VariantClear(&varRetVal);

//AllDone:
    if (! *lpszState)
        GetStateText(0, lpszState, cchState);

    return hr;
}



// --------------------------------------------------------------------------
//
//  GetObjectLocation()
//
// --------------------------------------------------------------------------
HRESULT GetObjectLocation(IAccessible * pOleAcc, LONG index,
    long *pxLeft, long *pyTop, long *pxWidth, long *pyHight)
{
    HRESULT hr = S_OK;
    VARIANT varChild;
    RECT rcL;

    PrintDebug("GetOBjectLocation");

    VariantInit(&varChild);
    SetRectEmpty(&rcL);

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hr = pOleAcc->accLocation(&rcL.left, &rcL.top, &rcL.right, &rcL.bottom, varChild);
    if (IsHResultError(hr, "accLocation"))
    {
//        DebugBreak();
        return(hr);
    }
        
    if (!rcL.right || !rcL.bottom)
        return(E_FAIL);

    *pxLeft = rcL.left;
    *pyTop  = rcL.top;
    *pxWidth = rcL.right;
    *pyHight = rcL.bottom;

    PrintDebug("get_accLocation: *pxLeft %ld, *pyTop %ld, *pxWidth %ld, *pyHight %ld",
        *pxLeft, *pyTop, *pxWidth, *pyHight);

    return hr;
}



// --------------------------------------------------------------------------
//
//  GetObjectShortcut
//
// --------------------------------------------------------------------------
HRESULT GetObjectShortcut(IAccessible * pOleAcc, LONG index,
    LPTSTR lpszShortcut, UINT cchShortcut)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    BSTR bstrShortcut;

    PrintDebug("GetObjectShortcut");

    VariantInit(&varChild);
    *lpszShortcut = 0;
    bstrShortcut = NULL;

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hResult = pOleAcc->get_accKeyboardShortcut(varChild, &bstrShortcut);
    if (IsHResultError(hResult, "accKeyboardShortcut"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (bstrShortcut)
    {
        WideCharToMultiByte(CP_ACP, 0, bstrShortcut, -1, lpszShortcut, cchShortcut, NULL, NULL);
        SysFreeString(bstrShortcut);
    }

    PrintDebug("get_accKeyboardShortcut: %s", lpszShortcut);

    return hResult;
}


// --------------------------------------------------------------------------
//
//  GetObjectDefAction
//
// --------------------------------------------------------------------------
HRESULT GetObjectDefAction(IAccessible * pOleAcc, LONG index,
    LPTSTR lpszDefAction, UINT cchDefAction)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    BSTR bstrDefAction;

    PrintDebug("GetObjectDefAction");

    VariantInit(&varChild);
    *lpszDefAction = 0;
    bstrDefAction = NULL;

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hResult = pOleAcc->get_accDefaultAction(varChild, &bstrDefAction);
    if (IsHResultError(hResult, "accDefaultAction"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (bstrDefAction)
    {
        WideCharToMultiByte(CP_ACP, 0, bstrDefAction, -1, lpszDefAction,
            cchDefAction, NULL, NULL);
        SysFreeString(bstrDefAction);
    }

    PrintDebug("get_accDefaultAction: %s", lpszDefAction);

    return hResult;
}


// --------------------------------------------------------------------------
//
//  GetObjectHelp
//
// --------------------------------------------------------------------------
HRESULT GetObjectHelp(IAccessible * pOleAcc, LONG index,
    LPTSTR lpszHelp, UINT cchHelp)
{
    HRESULT hResult = S_OK;
    VARIANT varChild;
    BSTR bstrHelp;
    LONG idHelpTopic;

    PrintDebug("GetObjectHelp");

    VariantInit(&varChild);
    *lpszHelp = 0;
    bstrHelp = NULL;

    varChild.vt = VT_I4;
    varChild.lVal = index;

    hResult = pOleAcc->get_accHelp(varChild, &bstrHelp);
    if (IsHResultError(hResult, "accHelp"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (bstrHelp)
    {
        WideCharToMultiByte(CP_ACP, 0, bstrHelp, -1, lpszHelp,
            cchHelp, NULL, NULL);
        SysFreeString(bstrHelp);
    }
    else
    {
        bstrHelp = NULL;
        idHelpTopic = 0;

        hResult = pOleAcc->get_accHelpTopic(&bstrHelp, varChild, &idHelpTopic);
        if (!IsHResultError(hResult, "accHelpTopic") && bstrHelp)
        {
            TCHAR szHelpFile[MAX_PATH];

            WideCharToMultiByte(CP_ACP, 0, bstrHelp, -1, szHelpFile,
                MAX_PATH, NULL, NULL);
            SysFreeString(bstrHelp);

            wsprintf(lpszHelp, "Help File: %s, Help Topic: %d.",
                szHelpFile, idHelpTopic);
        }
    }

    PrintDebug("get_accHelpTopic: %s", lpszHelp);

    return hResult;
}


// --------------------------------------------------------------------------
//
//  GetObjectParent()
//
//  This gets the name of the parent of an object.
//
// --------------------------------------------------------------------------
HRESULT GetObjectParent(IAccessible * pOleAcc, LPTSTR lpszParent,
    UINT cchParent)
{
    HRESULT hResult = S_OK;
    IDispatch * pdispParent;
    IAccessible * pOleAccParent;

    PrintDebug("GetObjectParent");

    //
    // Get parent object
    //
    pdispParent = NULL;

    hResult = pOleAcc->get_accParent(&pdispParent);
    if (IsHResultError(hResult, "accParent"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (!pdispParent)
        return(E_FAIL);

    //
    // See if it supports OLE Accessibility
    //
    pOleAccParent = NULL;
    hResult = pdispParent->QueryInterface(IID_IAccessible,
        (void **)&pOleAccParent);
    pdispParent->Release();

    if (IsHResultError(hResult, "Parent not OAC Obj") || !pOleAccParent)
    {
//        DebugBreak();
        return(hResult);
    }

    //
    // Get its name.
    //
    hResult = GetObjectName(pOleAccParent, 0,
        lpszParent, cchParent);

    pOleAccParent->Release();

    PrintDebug("get_accParent: %s", lpszParent);

    return hResult;
}


// --------------------------------------------------------------------------
//
//  'ildren()
//
//  This gets the name/role pairs of all _visible_ children in an object.
//
// --------------------------------------------------------------------------
HRESULT GetObjectChildren(IAccessible * pOleAcc, LPTSTR lpszChildren,
    UINT cchChildren)
{
    HRESULT hr = S_OK;
    long    lCount, lCountCheck;
    VARIANT varChild;
    VARIANT varResult;
    long    iChild;
    LPTSTR  lptstrT;
    UINT    cchRemain=cchChildren, cch;
    IEnumVARIANT* penum = NULL;
    UINT    cchBuf, cchString, cchT;
    LPTSTR  lpszBuf=NULL;

    PrintDebug("GetObjectChildren");

    VariantInit(&varChild);
    VariantInit(&varResult);
    *lpszChildren = 0;

    // Get child count.
    lCount = 0;
    hr = pOleAcc->get_accChildCount(&lCount);
    if (IsHResultError(hr, "get_accChildCount"))
    {
        return(hr);
    }

    if (!lCount)
    {
        PrintDebug("No children.");
        return hr;
    }

    hr = pOleAcc->QueryInterface(IID_IEnumVARIANT, (void**)&penum);
    if (hr == E_NOTIMPL) goto TrySimpleIndex;
    if (IsHResultError(hr, "QI for IEnumVARIANT")) return hr;

    //
    // Use IEnumVARIANT to enumerate the children.
    //

    PrintDebug("Use IEnumVARIANT to enumerate the children.");
    lpszBuf = lpszChildren;
    lCountCheck = 0;
    cchString = 0;
    hr = penum->Reset();
    IsHResultError(hr, "IEnum Reset");
    while (TRUE)
    {
        ULONG celt = 0;
        VariantInit(&varChild);

        cchBuf = cchChildren - cchString;

        // Get the next element
        hr = penum->Next(1, &varChild, &celt);
        if (IsHResultError(hr, "IEnum Next"))
        {
            cchString = 0;
            goto EnumDone;
        }

        // Are we at end?
        if (!celt)
        {
            PrintDebug("celt = 0");
            break;
        }
        lCountCheck++;

        // Get this child's name.
        if (varChild.vt == VT_DISPATCH)
        {
            IAccessible * poleaccChild = NULL;

PrintDebug("1");
            hr = varChild.pdispVal->QueryInterface(IID_IAccessible,
                (void**)&poleaccChild);
            varChild.pdispVal->Release();

            if (IsHResultError(hr, "varChild QI for IAccessible"))
            {
                cchString = 0;
                break;
            }

            GetObjectName(poleaccChild, 0, lpszBuf, cchBuf);
            poleaccChild->Release();
            cchT = lstrlen(lpszBuf);
        }
        else if (varChild.vt == VT_I4)
        {
PrintDebug("2");
            GetObjectName(pOleAcc, varChild.lVal, lpszBuf, cchBuf);
            cchT = lstrlen(lpszBuf);
        }
        else
        {
PrintDebug("3");
            VariantClear(&varChild);
            cchString = 0;
            break;
        }

        lpszBuf += cchT;
        cchString += cchT;
        if (cchString+3 > cchChildren) break;
        *(lpszBuf++) = ';';
        *(lpszBuf++) = ' ';
        cchString += 2;
    }
    lpszChildren[cchChildren]=0;

EnumDone:
    if (lCount != lCountCheck) PrintDebug("count mismatch getcount=%ld enum=%ld",
        lCount, lCountCheck);
    penum->Release();
    goto Done;

TrySimpleIndex:

    //
    // Append the name of each visible child to our list.
    // (This simple indexing method is used ONLY if the server does not
    //  support IEnumVARIAN%T.)
    //
    PrintDebug("Use Simple index to emum children");
    lptstrT = lpszChildren;
    for (iChild = 1; iChild <= lCount; iChild++)
    {
        varChild.vt = VT_I4;
        varChild.lVal = iChild;

        hr = pOleAcc->get_accState(varChild, &varResult);
        if (IsHResultError(hr, "get_accState"))
        {
            return(hr);
        }

        if (varResult.vt != VT_I4)
            continue;
        if (varResult.lVal & STATE_SYSTEM_INVISIBLE)
            continue;

        //
        // Great, we have a visible child.  Get its name.
        //
        if (cchRemain < 33) break;
        GetObjectName(pOleAcc, iChild, lptstrT, 32);
        cch = lstrlen(lptstrT);
        lptstrT += cch;
        cchRemain -= cch;
        if (cchRemain < 35) break;
        *lptstrT++ = ' ';
        GetObjectRole(pOleAcc, iChild, lptstrT, 32);
        cch = lstrlen(lptstrT);
        lptstrT += cch;
        *lptstrT++ = ';';
        *lptstrT++ = ' ';
        cchRemain -= (cch + 2);
    }

    if (lptstrT != lpszChildren)
    {
        //
        // We must have an extra "; " at the end, null it out.
        //
        *(lptstrT - 2) = 0;
    }

Done:

    PrintDebug("get_accChildren: %s", lpszChildren);

    return hr;
}



// --------------------------------------------------------------------------
//
//  GetObjectFocus()
//
// --------------------------------------------------------------------------
HRESULT GetObjectFocus(IAccessible * pOleAcc, LPTSTR lpszFocus,
    UINT cchFocus)
{
    HRESULT hResult = S_OK;
    VARIANT varResult;

    PrintDebug("GetOBjectFocus");

    VariantInit(&varResult);
    *lpszFocus = 0;

    // Get item with the focus
    hResult = pOleAcc->get_accFocus(&varResult);
    IsHResultError(hResult, "accFocus");

    if (varResult.vt == VT_I4)
    {
        GetObjectName(pOleAcc, varResult.lVal, lpszFocus, cchFocus);
        PrintDebug("get_accFocus: %s", lpszFocus);
    }
    else if (varResult.vt == VT_EMPTY)
    {
        PrintDebug("No focus");
    }
    else
    {
        PrintDebug("!!! Invalid variant type returned. varResult.vt %x", varResult.vt);
//        DebugBreak();
        hResult = E_FAIL;
    }

    VariantClear(&varResult);
    return hResult;
}


// --------------------------------------------------------------------------
//
//  GetObjectSelection()
//
// --------------------------------------------------------------------------
HRESULT GetObjectSelection(IAccessible * pOleAcc, LPTSTR lpszSel,
    UINT cchSel)
{
    HRESULT hResult = S_OK;
    VARIANT varResult;

    PrintDebug("GetObjectSelection");

    VariantInit(&varResult);
    *lpszSel = 0;

    // Get item(s) with the selection
    hResult = pOleAcc->get_accSelection(&varResult);
    if (IsHResultError(hResult, "accSelection"))
    {
//        DebugBreak();
        return(hResult);
    }

    if (varResult.vt == VT_I4)
    {
        // Get name of child/self that is selected
        return(hResult);
    }
    else if ((varResult.vt == VT_DISPATCH) && varResult.pdispVal)
    {
        IAccessible * pOleAccSelChildren;

        // Get names of children that are selected
        hResult = varResult.pdispVal->QueryInterface(IID_IAccessible,
            (void **)&pOleAccSelChildren);

        varResult.pdispVal->Release();

        if (IsHResultError(hResult, "Child not OAC.") || !pOleAccSelChildren)
        {
//            DebugBreak();
            return(hResult);
        }

        hResult = GetObjectChildren(pOleAccSelChildren, lpszSel, cchSel);
        pOleAccSelChildren->Release();

        PrintDebug("get_accSelection: %s", lpszSel);

        return(hResult);
    }
    else if (varResult.vt == VT_EMPTY)
    {
        PrintDebug("No selection");
        return hResult;
    }
    else
    {
        VariantClear(&varResult);
        PrintDebug("!!! Invalid variant type returned.");
//        DebugBreak();
        return(E_FAIL);
    }
}

HRESULT oacGetObjectInfo(IAccessible FAR* poac, LONG index, OACOBJINFO *poi)
{
    HRESULT hr=E_FAIL;

    PrintDebug("Entered oacGetObjectInfo poac %lx index %lx poi %lx", poac, index, poi);

    if (!poac || !poi) {PrintDebug("Invalid params"); return E_INVALIDARG;}

    memset(poi, 0, sizeof(OACOBJINFO));

    hr = GetObjectLocation(poac, index, &poi->xLeft, &poi->yTop, &poi->xWidth, &poi->yHight);
    if (!SUCCEEDED(hr)) goto EXIT1;

    hr = GetObjectName(poac, index, poi->szObjName, sizeof(poi->szObjName)-1);
    if (!SUCCEEDED(hr)) goto EXIT1;

    hr = GetObjectRole(poac, index, poi->szRole, sizeof(poi->szRole)-1);
    if (!SUCCEEDED(hr)) goto EXIT1;

    hr = GetObjectValue(poac, index, poi->szValue, sizeof(poi->szValue)-1);

    hr = GetObjectState(poac, index, poi->szState, sizeof(poi->szState)-1);

    hr = GetObjectShortcut(poac, index, poi->szShortcut, sizeof(poi->szShortcut)-1);

    hr = GetObjectDefAction(poac, index, poi->szDefAction, sizeof(poi->szDefAction)-1);

    if (index) lstrcpy(poi->szParent, "Already a child element");
    else hr = GetObjectParent(poac, poi->szParent, sizeof(poi->szParent)-1);

    if (index) lstrcpy(poi->szChildren, "Already a child element");
    else hr = GetObjectChildren(poac, poi->szChildren, sizeof(poi->szChildren)-1);

    hr = GetObjectHelp(poac, index, poi->szHelp, sizeof(poi->szHelp)-1);

    if (index) lstrcpy(poi->szSel, "Already a child element");
    else hr = GetObjectSelection(poac, poi->szSel, sizeof(poi->szSel)-1);

    if (index) lstrcpy(poi->szFocus, "Already a child element");
    else hr = GetObjectFocus(poac, poi->szFocus, sizeof(poi->szFocus)-1);

    PrintDebug("oacGetObjectInfo succeeded.");
    poi->hr = S_OK;
    return S_OK;

EXIT1:
    poi->hr = hr;
    return hr;
}


// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
//
//    multi-interface support.
//
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------


// This function tries out the different interfaces.
HRESULT GetObjectInfo(LPUNKNOWN punk, LONG index, OACOBJINFO *poi)
{
    HRESULT hr=S_OK;
    IAccessible FAR* poac=NULL;
    IDispatch FAR* pdisp=NULL;

    PrintDebug("Entered GetObjectInfo punk %lx index %lx poi %lx", punk, index, poi);

    if (!punk || !poi) {PrintDebug("Invalid params"); return E_INVALIDARG;}

    // Try to call via IAccessible
    hr = punk->QueryInterface(IID_IAccessible, (LPVOID FAR *) &poac);
    if (hr == S_OK)
    {
        hr = oacGetObjectInfo(poac, index, poi);
        poac->Release();
    }
    else // Try to call via iDispatch
    {
        hr = punk->QueryInterface(IID_IDispatch, (LPVOID FAR *) &pdisp);
        if (hr == S_OK && pdisp)
        {
// Through oa, we should be able to get a lot of info, too.
// This code is not tested !!!
//            hr = dispGetObjectInfo(pdisp, poi);
hr = E_FAIL;
            pdisp->Release();
        }
    }

    IsHResultError(hr, "GetObjectInfo");
    return hr;
}

