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?
You can enumerate the methods an IDispatch exposes through the type info. There are two ways to get the type info:
through the type library (if any) for the dispinterface.
through calling IDispatch::GetTypeInfo.
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.
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;
}
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.
Related
i want to pass a com object instance as a variant parameter to another active x object function, for that i need to convert the idispatch pointer to a variant? i am not sure.
hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
return;
}
hr = CLSIDFromProgID(objectName.c_str(), &clsid);
if (FAILED(hr))
{
return;
}
hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pApp));
if (FAILED(hr) || pApp == nullptr) {
return;
}
this is the instance creating code, after that i am using this :
VARIANT v;
VariantInit(&v);
v.pdispVal = pApp;
v.ppdispVal = &pApp;
v.vt = VT_DISPATCH;
return v;
and passing it to an active x method, but it is giving access violation after invoke.
what i am doing wrong?
If you want to use the VARIANT raw structure, you can code it like this:
VARIANT v;
VariantInit(&v);
pApp->AddRef();
v.pdispVal = pApp;
v.vt = VT_DISPATCH;
...
// later on, some code (this code or another code) will/should call this
VariantClear(&v); // implicitely calls pdispVal->Release();
Or, if you're using the Visual Studio development environment, then you can just use the _variant_t or CComVariant (ATL) smart wrappers which I recommend. In this case, you can just call it like this:
IDispatch *pApp = ...
// both wrappers will call appropriate methods
// and will release what must be, when destroyed
CComVariant cv = pApp;
// or
_variant_t vt = pApp;
PS: don't use both wrapper classes, make your choice. If a project uses ATL, I uses CComVariant, otherwise _variant_t, for example.
I have a transform filter which exposes a custom interface says IMyInit. This interface used to be configured some basic setup setting before streaming.
DECLARE_INTERFACE_(IMyInit, IUnknown) {
STDMETHOD HRESULT SetPath(const wchar_t* wcsPath) PURE;
STDMETHOD HRESULT SetMode(UINT uMode) PURE;
};
Client code like:
CComPtr<IBaseFilter> pMyFilter;
HRESULT hr = CoCreateInstance(CLSID_MYFILTER, IID_MYFILTER, ..., (void**)&pMyFilter);
// hr is S_OK
CComPtr<IMyInit> pMyInit;
hr = pMyFilter->QueryInterface(IID_IMyInit, (void**)&pMyInit);
// hr is S_OK
hr = pMyInit->SetMode(1);
// hr is 0x80040213/VFW_E_NO_CLOCK
In my CMyFilter::SetMode(UINT uMode), there are only E_POINTER, E_INVALIDARG for parameters checking, and S_OK if uMode is set. It is not possible to return such error code, VFW_E_NO_CLOCK, related to transform filter.
Why?
I have implemented the IMyInit interface dispatch in NonDelegatingQueryInterface, and it seems like
if(riid == IID_IMyInit) {
return GetInterface((IMyInit*)this, ppv);
}
But! I forgot to let my CMyFilter concrete class inherit from the IMyInit interface. So there is no connection between IMyInit and CMyFilter.
The C style casting, (IMyInit*)this, then casts the ppv to CMyFilter's some base class, may be the direct show's CTransformFilter. The unknown method pointered by IMyInit::SetMode(UINT) may require clock to exist. This is why the VFW_E_NO_CLOCK will return.
I'd like to use a COM function : CreateInstance
http://msdn.microsoft.com/en-us/library/k2cy7zfz%28v=vs.80%29.aspx
like this
IPointer p=NULL;
HRESULT hr=p.CreateInstance(xxx);
However I don't have CLSID of xxx I only know its interface name ISubPointer
I can see its interface description inside the tlb file when I view the file with oleview. What should I do to use that CreateInstance ?
There are two ways to do this:
1st: a ClassFactory ,and
2nd: a helper function to create a pointer.
I found this:
int main()
{
IMath* pIMath;
HRESULT hr;
// 1. Initialize COM Library
CoInitialize(NULL);
// 2. Call CoCreateInstance to get the IMath interface pointer
hr = CoCreateInstance ( __uuidof(CMathComp), NULL, CLSCTX_INPROC_SERVER,
__uuidof(IMath), (void**) &pIMath );
if ( FAILED(hr) )
{
return 0;
}
// 3. Call the interface functions
int sum = pIMath->Add(1, 3);
printf("Sum = %d \n", sum);
int sub = pIMath->Sub(4, 3);
printf("Sub = %d \n", sub);
// 4. Release the interface pointer if you are done
pIMath->Release();
// 5. Un-Initialize COM Library
CoUninitialize();
return 0;
}
Also see MSDN:
HRESULT CoCreateInstance(
_In_ REFCLSID rclsid,
_In_ LPUNKNOWN pUnkOuter,
_In_ DWORD dwClsContext,
_In_ REFIID riid,
_Out_ LPVOID *ppv
);
If you can gather the CLSID from OLEVIEW use it, otherwise there must be documentation about this. You can't deliver a component without exposing ist CLSID.
You have a couple of options for obtaining the class ID of the object you want to create. You can use the OLE Viewer to generate the header files or you can directly import the type library into your source file using the #import directive. The CreateInstance function that you refer to is a non-static member of _com_ptr_t and requires you to use an instance of it.
The following example should get you on your way.
#include <comip.h> // _com_ptr_t
#import "tlbname.tlb" // Change to the name of your type library
int main()
{
CoInitialize(NULL);
::_com_ptr_t<ISubPointer> ptr;
// CoISubPointer is the class ID specified in the type library
// you will need to change the name accordingly.
ptr.CreateInstance(__uuid(CoISubPointer), NULL, CLSCTX_INPROC_SERVER);
CoUninitialize();
return 0;
}
When main() has finished ptr will automatically release it's reference to the ISubPointer object it holds.
You can't create COM object without knowing class ID. I suggest to read the basic of COM in this article http://www.codeproject.com/Articles/633/Introduction-to-COM-What-It-Is-and-How-to-Use-It
I am trying to use the IFileSystemImage2 interface to create an ISO with multiple boot records using Imapi2.
To do this, I should be able to use put_BootImageOptionsArray passing in SAFEARRAY* of VT_DISPATCH type, i.e. COM pointers of type IBootOptions for each boot options configuration. As a short demo, I have the following code (I only created one IBootOptions in this case):
SAFEARRAYBOUND bounds[1];
bounds[0].cElements = 1;
bounds[1].lLbound = 0;
IBootOptions* BootOptionsArrayData = NULL;
SAFEARRAY* Array = SafeArrayCreateEx(VT_DISPATCH,
1,
bounds,
(void*) &IID_IBootOptions);
hr = SafeArrayAccessData(Array,
reinterpret_cast<void**>(&BootOptionsArrayData));
BootOptionsArrayData = BootOptions; // BootOptions = IBootOptions*
hr = SafeArrayUnaccessData(Array);
hr = IsoImage->put_BootImageOptionsArray(Array);
However, every time I call put_BootImageOptionsArray I get E_NOINTERFACE returned.
IsoImage is being created as you'd expect:
hr = CoCreateInstance(CLSID_MsftFileSystemImage,
NULL,
CLSCTX_ALL,
__uuidof(IFileSystemImage2),
(void**) &IsoImage);
Using IFileSystemImage2 any inherited functionality from IFileSystemImage works fine. Likewise, I can CoCreateInstance a IFileSystemImage instead, and this interface can be used just fine.
I have attached to my process in WinDbg and set a breakpoint in CMsftFileSystemImage::put_BootOptionsArray, however, this function (the underlying implementation) simply isn't being called.
My question, therefore is simple: the implementation appears to be there, but I don't seem to be able to call it. Does anyone have any experience of using this particular bit of functionality and if so how did you get it to work?
The documentation stipulates the SAFEARRAY must be an array of VARIANT that contain IDispatch interface pointers, so you could do something like this (I'm using smart pointers which is easier...):
CComPtr<IFileSystemImage2> image;
CComPtr<IBootOptions> options;
image.CoCreateInstance(CLSID_MsftFileSystemImage);
options.CoCreateInstance(CLSID_BootOptions);
// set various options here...
options->put_Manufacturer(CComBSTR(L"joe"));
// create a SAFEARRAY of VARIANT
CComSafeArray<VARIANT> a(1);
// create a VARIANT of type VT_UNKNONW (or VT_DISPATCH)
CComVariant v(options);
// put it in the array
a.SetAt(0, v);
HRESULT hr = pImage->put_BootImageOptionsArray(a.m_psa);
I'm having trouble calling a function that has a floating point argument and a floating point result, using IDispatch.Invoke.
Here's a minimal reproduction:
#include <atlbase.h>
#include <comutil.h>
int main(int argc, char* argv[])
{
CoInitialize(NULL);
CComPtr<IDispatch> wordapp;
if (SUCCEEDED(wordapp.CoCreateInstance(L"Word.Application", NULL, CLSCTX_LOCAL_SERVER)))
{
CComVariant result;
CComVariant centimeters((float)2.0);
CComVariant retval = wordapp.Invoke1(L"CentimetersToPoints", ¢imeters, &result);
}
return 0;
}
I'm using the ATL CComPtr to make things cleaner. But it's a very loose wrapper around IDispatch.Invoke.
When I run this, the call to Invoke1 fails and returns E_FAIL.
I suspect that the problem is related to the use of floating point arguments and/or return value. If I call a function that does not use such values, the invoke succeeds:
CComVariant retval = wordapp.Invoke0(L"ProductCode", &result);
I note that if I call the function from VBS, or from PowerShell, it succeeds. I'm presuming that they both use late bound IDispatch and so that would indicate that what I am attempting is at least possible.
So, how can I call this function using IDispatch?
Well, that was a head-scratcher. The documentation for this "method" is very misleading, it is not a method. It behaves more like an indexed property and requires the DISPATCH_METHOD | DISPATCH_PROPERTYGET flags. CComPtr doesn't support that, you'll need to spin this by hand. This code worked:
CComVariant result;
CComVariant centimeters((float)2.0);
DISPID dispid;
LPOLESTR name = L"CentimetersToPoints";
HRESULT hr = wordapp->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_USER_DEFAULT, &dispid);
assert(SUCCEEDED(hr));
DISPPARAMS dispparams = { ¢imeters, NULL, 1, 0};
hr = wordapp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT,
DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dispparams, &result, nullptr, nullptr);
assert(SUCCEEDED(hr));