Enumerate COM object (IDispatch) methods using ATL?
Asked Answered
O

3

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?

Octofoil answered 21/1, 2010 at 19:43 Comment(3)
see this tool (source code) : sourceforge.net/projects/axfuzz/filesNertie
and this : codeproject.com/KB/atl/ienum.aspxNertie
I went looking for other examples and also found spec.winprog.org/typeinf2Smash
P
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.

Pasadis answered 21/1, 2010 at 19:52 Comment(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.Pasadis
J
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.

Jolenejolenta answered 21/1, 2010 at 20:27 Comment(5)
@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.Reddick
@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.Jolenejolenta
@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.Jolenejolenta
@Uri - of course, all this is based on knowledge I haven't dusted in about two years, so I might be forgetting details... :-)Jolenejolenta
@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 - blogs.msdn.com/b/ericlippert/archive/2003/09/22/53061.aspx.Jolenejolenta
A
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;

}
Alchemize answered 12/7, 2013 at 13:6 Comment(0)
P
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.

Pasadis answered 21/1, 2010 at 19:52 Comment(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.Pasadis

© 2022 - 2024 — McMap. All rights reserved.