8

Using ATL (VS2008) how can I enumerate the available methods available on a given IDispatch interface (IDispatch*)? I need to search for a method with a specific name and, once I have the DISPID, invoke the method (I know the parameters the method takes.) Ideally I would like to do this using smart COM pointers (CComPtr<>).

Is this possible?

Rob
  • 76,700
  • 56
  • 158
  • 197

3 Answers3

20

You can enumerate the methods an IDispatch exposes through the type info. There are two ways to get the type info:

Unfortunately, an IDispatch implementation is not obligated to provide type info about the methods and properties it implements.

If it does, however, the basic enumerating involves calling ITypeInfo::GetTypeAttr to get the TYPEATTR for the interface and looking at the number of implemented methods (cFuncs) and variables (cVars) and looping over these and calling ITypeInfo::GetFuncDesc() or ITypeInfo::GetVarDesc(). Of course, there are lot more details you will have to deal with as I can list here, but this should be a good starting point for your exploration.

Here's a nice article explaining the process in more details with code in VB.Net.

Franci Penov
  • 74,861
  • 18
  • 132
  • 169
  • Good stuff. Thanks for adding this. – i_am_jorf Jan 21 '10 at 20:38
  • @Franci, when a property is an array, the returned VARDESC has a varkind=IDispatch. How can you tell whether a property is an array, and if an array - how can I access its members? When calling Invoke to get an array the result is IDispatch. This IDispatch doesn't support 'Item' or 'Length' or any similar property. – Uri London Feb 16 '12 at 17:15
  • 1
    @Uri - note that properties are not fields and should be inspected through `GetFuncDesc()` which gives you a `FUNCDESC`, from where you have to go to `elemdescFunc` (for the return) or `lprgelemdescParam` (for the parameters). Arrays are usually returned as out parameter, so you should inspect the latter. In any case, both of these give `ELEMDESC`, where you should inspect the `tdesk`, which returns to you a `TYPEDESC`, which based on the `VARTYPE vt` might turn out to be actually an `ARRAYDESC`. If that is the case, you have a `SAFEARRAY`. – Franci Penov Feb 16 '12 at 20:35
  • @Uri - If it turns out it is _not_ a `SAFEARRAY`, the property is most likely returning an `IDispatch` pointer to an object that implements array-like functionality as a collection. You should ask that `IDispatch` for its type info and inspect that one to figure out the right methods to access the collection members. – Franci Penov Feb 16 '12 at 20:36
  • @Uri - of course, all this is based on knowledge I haven't dusted in about two years, so I might be forgetting details... :-) – Franci Penov Feb 16 '12 at 20:37
  • 1
    @Uri - if your array comes from JScript, it's not a SAFEARRAY, but an IDIspatch to a JScript Array object. Here's a description of the difference between VBScript arrays (which are SAFEARRAYS) and JScript arrays - http://blogs.msdn.com/b/ericlippert/archive/2003/09/22/53061.aspx. – Franci Penov Feb 21 '12 at 18:43
16

Here's some code that does the enumeration (it inserts the [Dispatch ID]-[Method Name] pairs in a map, but that's easy to change).

///
/// \brief Returns a map of [DispId, Method Name] for the passed-in IDispatch object
///
HRESULT COMTools::GetIDispatchMethods(_In_ IDispatch * pDisp,
                                      _Out_ std::map<long, std::wstring> & methodsMap)
{
    HRESULT hr = S_OK;

    CComPtr<IDispatch> spDisp(pDisp);
    if(!spDisp)
        return E_INVALIDARG;

    CComPtr<ITypeInfo> spTypeInfo;
    hr = spDisp->GetTypeInfo(0, 0, &spTypeInfo);
    if(SUCCEEDED(hr) && spTypeInfo)
    {
        TYPEATTR *pTatt = nullptr;
        hr = spTypeInfo->GetTypeAttr(&pTatt);
        if(SUCCEEDED(hr) && pTatt)
        {
            FUNCDESC * fd = nullptr;
            for(int i = 0; i < pTatt->cFuncs; ++i)
            {
                hr = spTypeInfo->GetFuncDesc(i, &fd);
                if(SUCCEEDED(hr) && fd)
                {
                    CComBSTR funcName;
                    spTypeInfo->GetDocumentation(fd->memid, &funcName, nullptr, nullptr, nullptr);
                    if(funcName.Length()>0)
                    {
                        methodsMap[fd->memid] = funcName;
                    }

                    spTypeInfo->ReleaseFuncDesc(fd);
                }
            }

            spTypeInfo->ReleaseTypeAttr(pTatt);
        }
    }

    return hr;

}
9

You can't enumerate all the available methods unless the object implements IDispatchEx.

However, if you know the name of the method you want to call, you can use GetIDsOfNames to map the name to the proper DISPID.

HRESULT hr;
CComPtr<IDispatch> dispatch;
DISPID dispid;
WCHAR *member = "YOUR-FUNCTION-NAME-HERE";
DISPPARAMS* dispparams;

// Get your pointer to the IDispatch interface on the object here.  Also setup your params in dispparams.

hr = dispatch->GetIDsOfNames(IID_NULL, &member, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (SUCCEEDED(hr)) {
  hr = dispatch->Invoke(1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, dispparams, &varResult, NULL, NULL);
}

Edit: For completeness, I suspect there is a way to interrogate the ITypeInfo2 interface (assuming there is a type library for the object) that you get from IDispatch::GetTypeInfo for a list of methods, but I've not done it. See the other answer.

i_am_jorf
  • 53,608
  • 15
  • 131
  • 222
  • 1
    I believe I made every point you just made in your comment in my answer. Please read it again closely. Furthermore, the poster just wanted to be able to invoke a method that he already knew the name of. So my answer provided the solution to what he really wanted to do, not necessarily what he asked. That is why, I suspect, it was marked as the correct answer. And lastly, please calm down. It's only 1s and 0s. – i_am_jorf May 05 '14 at 16:29