I have made a COM object with multiple dual interfaces. It worked in an earlier version of compiler, but not in the current version.
My question: Does the COM spec say this should work (and therefore I should report a compiler bug), or is not meant to work? This page suggests that perhaps it is not meant to work.
The RIDL file:
[
uuid(0A6CC6CE-623E-4455-8B9B-65178FB7585A),
version(1.0),
helpstring("Library to illustrate failure of Dispatch interface")
]
library DaxFail
{
importlib("stdole2.tlb");
interface IFoo;
coclass DaxFailClass;
interface IBar;
[
uuid(2CD15FFC-0C09-4A29-BD57-99BBC53AE01F),
helpstring("Dispatch interface for DaxFailClass Object"),
dual,
oleautomation
]
interface IFoo: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall foo_method(void);
};
[
uuid(AECB5DF3-EDE3-441A-93E6-220CB271AD43),
dual,
oleautomation
]
interface IBar: IDispatch
{
[id(0x000000C9)]
HRESULT _stdcall bar_method(void);
};
[
uuid(9DCD1024-6E1A-435E-82F9-FD4FE863D710),
helpstring("DaxFailClass Object")
]
coclass DaxFailClass
{
[default] interface IFoo;
interface IBar;
};
};
The code to access it (this is pseudocode, I have extra statements in "real" code to display the HRESULTs):
const GUID CLSID_DaxFailClass = {0x9DCD1024, 0x6E1A, 0x435E,{ 0x82, 0xF9, 0xFD,0x4F, 0xE8, 0x63,0xD7, 0x10} };
const GUID IID_IFoo = {0x2CD15FFC, 0x0C09, 0x4A29,{ 0xBD, 0x57, 0x99,0xBB, 0xC5, 0x3A,0xE0, 0x1F} };
const GUID IID_IBar = {0xAECB5DF3, 0xEDE3, 0x441A,{ 0x93, 0xE6, 0x22,0x0C, 0xB2, 0x71,0xAD, 0x43} };
int _tmain()
{
IDispatch *intf, *ibar;
HRESULT hr;
DISPID disp_id;
wchar_t *name;
CoInitialize(NULL);
hr = CoCreateInstance(CLSID_DaxFailClass, 0, CLSCTX_ALL, IID_IDispatch, (void **)&intf);
// This returns 0 , and doing Invoke with disp_id executes foo_method
name = L"foo_method";
intf->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &disp_id );
// returns 0
hr = intf->QueryInterface(IID_IBar, (void **)&ibar);
// this returns 0x80020006
name = L"bar_method";
hr = ibar->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &disp_id );
// This returns 0 , and doing Invoke with disp_id executes foo_method
name = L"foo_method";
hr = ibar->GetIDsOfNames(IID_NULL, &name, 1, LOCALE_SYSTEM_DEFAULT, &disp_id );
CoUninitialize();
getchar();
}
So, the trouble is that ibar behaves exactly like intf. ibar can have foo_method called on it, but does not seem to know what bar_method is.
I was expecting the second GetIDsOfNames call to give 0 and then Invoke to be able to call bar_method, and the third GetIDsOfNames should give 0x80020006.
Extra info about compilers (although, to be clear, my question is whether the COM spec says it should work or not): Works in BDS 2006 and does not work in C++Builder XE5. I trawled through the code in XE5 which implements COM, and the ojbect factory fills an ITypeInfo * using GetTypeInfoOfGUID(CLSID_....) when the object is first created, but then the implementation of QueryInterface just uses the same ITypeInfo for all results, it does not call GetTypeInfoOfGUID again with the new IID. That ITypeInfo is passed to DispGetIDsOfNames in the implementation of IDispatch::GetIDsOfNames.
The specs don't have to say something to that.
You have to different interfaces on one class. This is allowed.
Both derive from IDispatch. This is allowed too.
Both have to do their own implementation and this implementation should do its job.
If something is wrong with your dual interface "usally" the IDL compiler will tell it to you.
Here with your code: I can say nothing without seeing the class implementation for the interfaces.
And yes: It works and you find a sample for ATL here at CodeProject
It is exactly what you are doing and far more.
Related
I am experimenting with creating a COM interface for my application in order to allow eg. VBA to drive certain parts of my application.
I have my COM library up and running and installed, even to the part where a routine called in Excel can be debugged in the Delphi IDE.
Here's the VBA code that I activate from Excel using a button:
Sub TestAMQMOLE_OpenProject()
Dim vConnection As String
Dim vAMQM As Object
Dim vAMProject As Object
vConnection = "$(MYSRC)\LCCAMQM38\UnitTestData\AnalyseSortingAndGrouping"
Set vAMQM = CreateObject("LCCAMQM_AX.LCCAMQM_Application")
vAMQM.Connect
Set vAMQMProject = vAMQM.OpenProject(vConnection) 'This parameter does not get through
Set vAMQMProject.Active = True
Set vAMQMProject = Nothing
vAMQM.Disconnect
Set vAMQM = Nothing
End Sub
And the part in Delphi handling it looks like this:
function TLCCAMQM_Application.OpenProject(const aFolderOrAlias: WideString): ILCCAMQM_Project;
begin
try
Result:=TLCCAMQM_Project.Create(aFolderOrAlias); // wrapper om TdmAMEditBase
except
SHowMessage(ExceptionToString(ExceptObject,ExceptAddr));
end;
end;
Where the code fails because the aFolderOrAlias parameter string is empty. I added the exception handler in order to debug outside the Delphi IDE. when debugging inside the IDE, the parameter string indeed appears as empty.
I have also tried to pass the parameter as a Variant, or const Variant (and adjusting the type library accordingly), but in that case I get a VT_RECORD variant type (0x0024) which does not make any sense to me.
Here is what the interface definition of the type library looks like.
....
[
uuid(EDD8E7FC-5D96-49F1-ADB7-F04EE9FED7B5),
helpstring("Dispatch interface for LCCAMQM_Application Object"),
dual,
oleautomation
]
interface ILCCAMQM_Application: IDispatch
{
[id(0x000000C9)]
int _stdcall Connect(void);
[id(0x000000CA)]
int _stdcall Disconnect(void);
[id(0x000000CB)]
ILCCAMQM_Project* _stdcall OpenProject([in] BSTR aFolderOrAlias);
[propget, id(0x000000CC)]
HRESULT _stdcall Connected([out, retval] VARIANT_BOOL* Value);
};
[
uuid(590DBF46-76C9-4877-8F47-5A926AFF389F),
helpstring("LCCAMQM_Application Object")
]
coclass LCCAMQM_Application
{
[default] interface ILCCAMQM_Application;
};
....
I am fairly sure there must be a way to pass strings from VBA to COM objects. But after fiddling around for several hours I am lost :s.
As it turned out, ComIntern and Remy were right. I had completely misunderstood the whole stdcall and safecall interfaces.
The .ridl file now looks like this:
....
interface ILCCAMQM_Application: IDispatch
{
[id(0x000000C9)]
int _stdcall Connect(void);
[id(0x000000CA)]
int _stdcall Disconnect(void);
[propget, id(0x000000CC)]
HRESULT _stdcall Connected([out, retval] VARIANT_BOOL* Value);
[id(0x000000CB)]
HRESULT _stdcall OpenProject([in] BSTR aFolderOrAlias, [out, retval] ILCCAMQM_Project** Value);
};
....
And the generated ...TLB.pas file looks like this:
....
// *********************************************************************//
// Interface: ILCCAMQM_Application
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {EDD8E7FC-5D96-49F1-ADB7-F04EE9FED7B5}
// *********************************************************************//
ILCCAMQM_Application = interface(IDispatch)
['{EDD8E7FC-5D96-49F1-ADB7-F04EE9FED7B5}']
function Connect: SYSINT; stdcall;
function Disconnect: SYSINT; stdcall;
function Get_Connected: WordBool; safecall;
function OpenProject(const aFolderOrAlias: WideString): ILCCAMQM_Project; safecall;
property Connected: WordBool read Get_Connected;
end;
// *********************************************************************//
// DispIntf: ILCCAMQM_ApplicationDisp
// Flags: (4416) Dual OleAutomation Dispatchable
// GUID: {EDD8E7FC-5D96-49F1-ADB7-F04EE9FED7B5}
// *********************************************************************//
ILCCAMQM_ApplicationDisp = dispinterface
['{EDD8E7FC-5D96-49F1-ADB7-F04EE9FED7B5}']
function Connect: SYSINT; dispid 201;
function Disconnect: SYSINT; dispid 202;
property Connected: WordBool readonly dispid 204;
function OpenProject(const aFolderOrAlias: WideString): ILCCAMQM_Project; dispid 203;
end;
....
And the OpenProject now works from my internal unit test (written in delphi) as well as from Excel-VBA.
Now I am struggling with properties to set and get through Excel VBA as well as through an OleVariant in delphi. But I will have to put that in another Q.
I got confused with __vfptr which I see through IDispatch/IUnknown pointers.
I'm creating in-proc free threaded COM obj (IMyContainer). Inside this object I need to keep pointers to 2 different instances of com objects which implements same IMyInterface. So I call CreateAndSaveDispToMap() twice.
My idea is to keep their IDispatch pointers in some std::map. At this moment I suspect that each instance will have refCount of 1. And it is so. But suprisingly I see that I am getting same __vftbl through pUnk for 2 different dispatch pointers.
Why? How it's possible that AddRef() and Release() works fine?
HRESULT CMyContainer::CreateAndSaveDispToMap(...)
{
...
IMyInterface* pMyInterface = NULL;
hr = ::CoCreateInstance(CLSID_MyInterface, NULL, CLSCTX_INPROC_SERVER, IID_IMyInterface, (void**)&pMyInterface);
pMyInterface->QueryInterface(IID_IDispatch, (void**)&pDisp);
pMyInterface->Release(); // Call Release since QI already called AddRef()
...
IUnknown* pUnk = NULL;
pDisp->QueryInterface(IID_IUnknown, (void**)&pUnk);
int refCount = pUnk->Release();
...
AddToMap(pDisp);
}
Each polymorphic object will have a __vfptr that is a pointer to the vtable of the actual class of the object. One vtable is generated per each distinct class. That's why for any two objects of the same class their __vfptrs will have identical values.
To distinguish between different COM objects retrieve and compare their IUnknown interface pointers. That's called object identity.
Thanks, I figured out that function's addresses on IUnknown are the same and must be so.
But still don't undersatand the behaviour of AddRef/Release. When I step in debug mode in ExposePointer() I see that the second consequitive call will not bring refCount to 3. It will bring it back to 2.
But if I call ForgetExposePointer() twice instead it will bring it to 3.
Why returning dispatch pointer through Variant* Result or forgetting to return such value gives me different result? My undersanding that between Call 1 and Call 2 some hidden call to Release() occur...
STDMETHODIMP CMyContainer::ExposePointer([in]int index, [out, retval] VARIANT* Result)
{
VariantInit(Result);
IDispatch* pDisp = m_map[index].second;
V_VT(Result) = VT_DISPATCH;
V_DISPATCH(Result) = pDisp;
refCount_x = pDisp->AddRef(); // Increment, because we expose
}
STDMETHODIMP CMyContainer::ForgetExposePointer([in]int index, [out, retval] VARIANT* Result)
{
VariantInit(Result);
IDispatch* pDisp = m_map[index].second;
refCount_y = pDisp->AddRef();
}
MyApp::Function1(...)
{
CreateAndSaveDispToMap(...); // refCount is 1 now
VARIANT var1;
VARIANT var2;
pMyContainer->ExposePointer(index, &var1); // Call 1
pMyContainer->ExposePointer(index, &var2); // Call 2
}
MyApp::Function2(...)
{
CreateAndSaveDispToMap(...); // refCount is 1 now
VARIANT var1;
VARIANT var2;
pMyContainer->ForgetExposePointer(index, &var1);
pMyContainer->ForgetExposePointer(index, &var2);
}
I have a COM component with two CoClasses.
IMyApp is my application CoClass interface.
IFunction is my second ColClass interface.
For the first time when i Create IMyApp object using CoCreateInstance and followed with IFunction object using CoCreateInstance, everything works fine.
Problem:
Now again when i try to Create object of IFunction using CoCreateInstance it calls initinstance of my main application IMyApp.
Following is my IDL file library content:
library DemoPrjLib
{
Importlib("stdole2.tlb");
[
uuid(661CAC63-8F13-473B-8857-48233A668029),
helpstring("MyApp Class")
]
coclass MyApp
{
[default] interface IMyApp;
};
[
uuid(104A759B-1088-435C-A2F3-7F5FD13C233A),
helpstring("Function Class")
]
coclass Function
{
[default] interface IFunction;
};
};
// Creating Application object first time with success
::CoCreateInstanceEx(_uuidof(MyApp), 0, CLSCTX_ALL, &oServerInfo, 1, multi_qi);
// Now creating object of IFunction
CComPtr<IFunction> pFunction;
HRESULT hr = pFunction.CoCreateInstance (CLSID_Function); // this goes sucessfully
if (pFunction)
{
AfxMessageBox ("Works fine!!!");
}
if (pFunction)
pFunction.Release ();
// Problem Here: below call creates MyApp instance again i.e. it calls initinstance of my COM Application
hr = pFunction.CoCreateInstance (CLSID_Function);
Any suggestion is really appreciated.
i have got a ScreenCameraSDK and it comes with a 11kb dll file, it has a documentation too which lists the functions which can be used. It says
ScreenCamera SDK ActiveX Reference Documentation
ActiveX Reference
The ActiveX ID on the system is: ScreenCameraSDK.RemoteControl
Every method on the interface returns FAIL or SUCCESS. (0 or 1).
Create an instance of the ActiveX on your application, and then call InitializeScreenCameraRemoteControl. If the return value is SUCCESS then ScreenCamera is properly installed and you can then call any other method on the ActiveX's interface. If not ScreenCamera could not be found and you should contact support.**
Now my question is, i have the dll and no other files. How can i use the functions inside it in a VC++ Project with Visual Studio 2008.
Thanks
I TRIED THE FOLLOWING CODE BUT GOT COMPILATION ERROR OF UNDEFINED IDENTIFIER
#include <stdio.h>
// This is the path for your DLL.
// Make sure that you specify the exact path.
#import "e:\ScreenCameraSDK.dll" no_namespace
void main()
{
BSTR bstrDesc;
try
{
CoInitialize(NULL);
short st = 2;
short st1;
// Declare the Interface Pointer for your Visual Basic object. Here,
// _Class1Ptr is the Smart pointer wrapper class representing the
// default interface of the Visual Basic object.
_Class1Ptr ptr;
// Create an instance of your Visual Basic object, here
// __uuidof(Class1) gets the CLSID of your Visual Basic object.
ptr.CreateInstance(__uuidof(Class1));
st1 = ptr->MyVBFunction(&st);
}
catch(_com_error &e)
{
bstrDesc = e.Description();
}
CoUninitialize();
}
it says _Class1Ptr is unknown!
BSTR bstrDesc;
try
{
HRESULT hr= CoInitialize(NULL);
CLSID clsid;
hr = CLSIDFromProgID(OLESTR("<complete class name as see in registry>"),&clsid);
short st = 2;
short st1;
//nameOfClassInOCX is placeholder for explanation. If you OCX com class name is blabla
//use _blabla and so on.
_nameOfClassInOCX * ptr;
hr = CoCreateInstance(clsid,NULL,CLSCTX_INPROC_SERVER,__uuidof(_nameOfClassInOCX ),(LPVOID*)&ptr);
cout << ptr->GetFees("hi") <<endl;
ptr->Release();
}
catch(_com_error &e)
{
bstrDesc = e.Description();
}
CoUninitialize();
First of all you have to do this is #import the dll, and the compiler will automatically generate all required definitions from it. Then create objects from the library by using either smart pointers, or CreateInstance().
#import "C:\files\test.dll" no_namespace rename("EOF", "EOFile")
...
int main() {
if (FAILED(::CoInitialize(NULL)))
return 0;
........
::CoUninitialize();
return 0;
}
I'm trying to use WTL within an in-process COM server DLL (an IE BHO), but am struggling with _Module.
My DLL needs CMyModule derived from CAtlDllModuleT<>:
class CMyModule : public CAtlDllModuleT< CMyModule >
{
public:
DECLARE_LIBID(LIBID_MyLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYPROJ, "{...}")
};
CMyModule _Module;
extern "C" BOOL WINAPI DllMain(...)
{
hInstance;
return _Module.DllMain(dwReason, lpReserved);
}
...
STDAPI DllUnregisterServer(void)
{
return _Module.DllUnregisterServer();
}
But this conflicts with most WTL examples, which require something like this within stdafx.h:
extern CAppModule _Module; // WTL version of CComModule
No matter which way I do it, I (unsurprisingly) get compile errors. CMyModule derived from CAppModule borks on _Module.DllUnregisterServer(), etc. CMyModule derived from CAtlDllModuleT<> borks on code like _Module.GetMessageLoop().
Any good references on how WTL is supposed to work within a DLL? Google finds lots of questions, with few answers.
I have a project that uses WTL in a DLL. I looked at how my headers are set up and it looks like I hacked around this same problem...
I have my module set up like your sample code inheriting from CAtlDllModuleT<> except the name of the global module variable is _AtlModule rather than _Module. For example:
class CMyModule : public CAtlDllModuleT< CMyModule >
{
public:
DECLARE_LIBID(LIBID_MyLib)
DECLARE_REGISTRY_APPID_RESOURCEID(IDR_MYPROJ, "{...}")
};
CMyModule _AtlModule;
So, all of the DllMain.cpp entry points use _AtlModule. Then in the stdafx.h file it looks like this:
// WTL includes
#define _Module (*_pModule)
#include <atlapp.h>
#include <atlctrls.h>
#include <atldlgs.h>
#undef _Module
That _pModule thing is defined in atlbase.h like:
__declspec(selectany) CComModule* _pModule = NULL;
There must be a better way, but this does work.
Have you considered the option of multiple inheritance? Try inheriting from both CAtlDllModule and CAppModule since you need both.
I use WTL in an Office add-in; the following works for me. (At the bottom of stdafx.h)
class DECLSPEC_UUID("XXXX-...") MyLib;
using namespace ATL;
/*
* Application module
*/
class CAddInModule : public CAtlDllModuleT< CAddInModule >
{
public:
CAddInModule() : m_hInstance(NULL)
{
}
DECLARE_LIBID(__uuidof(MyLib))
HINSTANCE GetResourceInstance()
{
return m_hInstance;
}
void SetResourceInstance(HINSTANCE hInstance)
{
m_hInstance = hInstance;
}
private:
HINSTANCE m_hInstance;
};
extern CAddInModule _AtlModule;
And then the DLL main use _AtlModule:
// DLL Entry Point
extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
_AtlModule.SetResourceInstance(hInstance);
return _AtlModule.DllMain(dwReason, lpReserved);
}
// Used to determine whether the DLL can be unloaded by OLE
STDAPI DllCanUnloadNow(void)
{
return _AtlModule.DllCanUnloadNow();
}
// Returns a class factory to create an object of the requested type
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _AtlModule.DllGetClassObject(rclsid, riid, ppv);
}
// DllRegisterServer - Adds entries to the system registry
STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
HRESULT hr = _AtlModule.DllRegisterServer();
return hr;
}
// DllUnregisterServer - Removes entries from the system registry
STDAPI DllUnregisterServer(void)
{
HRESULT hr = _AtlModule.DllUnregisterServer();
return hr;
}