Page 1 of 1

wxAutomationObject Helpers

Posted: Sun Jul 12, 2020 8:45 pm
by PB
Three utility functions (adapted from wxAutoExcel) aiming to help with the unpleasant task of debugging OLE Automation with wxAutomationObject.

wxautomationobject_helpers.h

Code: Select all

///////////////////////////////////////////////////////////////////////////////
// Name:        wxautomationobject_helpers.h
// Purpose:     Helper functions for wxAutomationObject
// Author:      PB
// Created:     2020-07-12
// Copyright:   (c) 2020 PB
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#ifndef WXAUTOMATIONOBJECT_HELPERS
#define WXAUTOMATIONOBJECT_HELPERS

#include <wx/wx.h>

#if defined(__WXMSW__)

#include <wx/msw/ole/automtn.h>

// LCID for US English
extern const WXLCID wxLCIDEnglishUS;

// Returns name of the actual object based on object's IDispatch->GetTypeInfo
// or empty string when the name could not be retrieved
wxString wxGetAutomationObjectName(const wxAutomationObject& object,
                                   WXLCID lcid = wxLCIDEnglishUS,
                                   bool stripUnderscores = false);

// Returns list of properties and methods for given wxAutomationObject
bool wxGetAutomationObjectPropertyAndMethodNames(const wxAutomationObject& object,
                                                 wxArrayString& propertyNames,
                                                 wxArrayString& methodNames,
                                                 bool includeHidden = false);

// Uses wxLogDebug to dump information about wxVariant
void wxLogDebugAutomationVariant(const wxVariant& v, size_t maxItemsInList = 30);

#endif // #if  defined(__WXMSW__)

#endif // WXAUTOMATIONOBJECT_HELPERS
wxautomationobject_helpers.cpp

Code: Select all

///////////////////////////////////////////////////////////////////////////////
// Name:        wxautomationobject_helpers.cpp
// Purpose:     Helper functions for wxAutomationObject
// Author:      PB
// Created:     2020-07-12
// Copyright:   (c) 2020 PB
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////


#include <wx/wx.h>

#if defined(__WXMSW__)

#include "wxautomationobject_helpers.h"

#include <wx/msw/private/comptr.h>

// LCID for US English
const WXLCID wxLCIDEnglishUS = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);


// Returns name of the actual object based on object's IDispatch->GetTypeInfo
// or empty string when the name could not be retrieved
wxString wxGetAutomationObjectName(const wxAutomationObject& object,
                                   WXLCID lcid, bool stripUnderscores)
{
    wxCHECK_MSG(object.GetDispatchPtr(), wxEmptyString, "object does not contain a valid IDispatch pointer");

    wxCOMPtr<ITypeInfo> typeInfo;
    IDispatch* dispatch = (IDispatch*)object.GetDispatchPtr();
    HRESULT hr = dispatch->GetTypeInfo(0, lcid, &typeInfo);

    if ( FAILED(hr) )
    {
        wxLogApiError("IDispatch::GetTypeInfo()", hr);
        return wxEmptyString;
    }

    BSTR bName = nullptr;
    wxString name;
    hr = typeInfo->GetDocumentation(MEMBERID_NIL, &bName, nullptr, nullptr, nullptr);

    if ( FAILED(hr) )
    {
        wxLogApiError("ITypeInfo::GetDocumentation()", hr);
        return wxEmptyString;
    }

    name = bName;
    ::SysFreeString(bName);

    if ( stripUnderscores )
        name.Replace("_", wxEmptyString);

    return name;
}

// Returns list of properties and methods for the given wxAutomationObject
bool wxGetAutomationObjectPropertyAndMethodNames(const wxAutomationObject& object,
                                                 wxArrayString& propertyNames,
                                                 wxArrayString& methodNames,
                                                 bool includeHidden)
{
    wxCHECK_MSG(object.GetDispatchPtr(), false, "object does not contain a valid IDispatch pointer");

    HRESULT hr = S_OK;
    wxCOMPtr<ITypeInfo> typeInfo;
    IDispatch* dispatch = (IDispatch*)object.GetDispatchPtr();
    TYPEATTR* typeAttr = NULL;

    hr = dispatch->GetTypeInfo(0, 0, &typeInfo);
    if ( FAILED(hr) )
    {
        wxLogApiError("IDispatch::GetTypeInfo()", hr);
        return false;
    }

    hr = typeInfo->GetTypeAttr(&typeAttr);
    if ( FAILED(hr) )
    {
        wxLogApiError("ITypeInfo::GetTypeAttr()", hr);
        return false;
    }

    for ( WORD i = 0; i < typeAttr->cFuncs; i++ )
    {
        FUNCDESC* funcDesc = NULL;
        BSTR bName = NULL;

        hr = typeInfo->GetFuncDesc(i, &funcDesc);
        if ( FAILED(hr) )
        {
            wxLogApiError("ITypeInfo::GetFuncDesc()", hr);
            typeInfo->ReleaseTypeAttr(typeAttr);
            return false;
        }

        hr = typeInfo->GetDocumentation(funcDesc->memid, &bName, NULL, NULL, NULL);
        if ( FAILED(hr) )
        {
            wxLogApiError("ITypeInfo::GetDocumentation()", hr);
            typeInfo->ReleaseFuncDesc(funcDesc);
            typeInfo->ReleaseTypeAttr(typeAttr);
            return false;
        }

        if ( ((funcDesc->wFuncFlags & FUNCFLAG_FHIDDEN) != FUNCFLAG_FHIDDEN)
             || includeHidden )
        {
            wxString name(bName);

            switch  ( funcDesc->invkind )
            {
                case INVOKE_FUNC:
                    methodNames.push_back(name);
                    break;
                case INVOKE_PROPERTYGET:
                case INVOKE_PROPERTYPUT:
                case INVOKE_PROPERTYPUTREF:
                    // avoid adding the same property name for different INVOKEKINDs
                    if ( !propertyNames.empty() && propertyNames.Last().IsSameAs(name, false) )
                        break;

                    propertyNames.push_back(name);
                    break;
                default:
                    wxFAIL_MSG("Unknown INVOKEKIND value");
            }
        }

        ::SysFreeString(bName);
        typeInfo->ReleaseFuncDesc(funcDesc);
    }

    typeInfo->ReleaseTypeAttr(typeAttr);
    return true;
}

// Uses wxLogDebug to dump information about wxVariant
void wxLogDebugAutomationVariant(const wxVariant& v, size_t maxItemsInList)
{
    const wxString type = v.GetType();

    wxString info;
    const wxString& name = v.GetName();

    if ( type == wxS("arrstring") )
    {
        wxArrayString as = v.GetArrayString();
        info.Printf(wxS("variant type: \"%s\", element count: %zu, name: \"%s\"."),
            type, as.size(), name);
        wxLogDebug(wxS("%s"), info);
        for (size_t i = 0; i < as.size(); i++)
        {
            info.Printf(wxS("   string #%zu value: \"%s\""), i, as[i]);
            if ( i == maxItemsInList )
            {
                wxLogDebug(wxS("And %zu more strings"), as.size() - i);
                break;
            }
            else
                wxLogDebug(wxS("%s"), info);
        }
        return;
    }
    if ( type == wxS("list") )
    {
        info.Printf(wxS("Variant type: \"%s\", element count: %zu, name: \"%s\"."),
            type, v.GetCount(), name);
        wxLogDebug(wxS("%s"), info);

        for ( size_t i = 0; i < v.GetCount(); i++ )
        {
            if ( i == maxItemsInList )
            {
                wxLogDebug(wxS("And %zu more variants"), v.GetCount() - i);
                break;
            }
            else
            {
                const wxVariant& vTmp = v[i];
                info.Printf(wxS("   variant #%zu type: \"%s\", value: \"%s\", name: \"%s\"."),
                    i, vTmp.GetType(), vTmp.MakeString(), vTmp.GetName());
                wxLogDebug(wxS("%s"), info);
            }
        }
        return;
    }

    if ( type == wxS("void*") && v.GetVoidPtr() != NULL )
    {
        wxString automationName;
        wxAutomationObject object;
        IDispatch* dispatch = (IDispatch*)v.GetVoidPtr();

        dispatch->AddRef();
        object.SetDispatchPtr(dispatch);
        info.Printf(wxS("variant type: \"IDispatch - %s\", value: \"%s\", name: \"%s\"."),
            wxGetAutomationObjectName(false), v.MakeString(), name);
    }
    else
    {
        info.Printf(wxS("variant type: \"%s\", value: \"%s\", name: \"%s\"."),
            type, v.MakeString(), name);
    }

    wxLogDebug(wxS("%s"), info);
}

#endif // #if  defined(__WXMSW__)
EDIT (2020-07-22): Updated wxGetAutomationObjectName() which now on error returns an empty string and uses wxLogApiError() to print what went wrong.