1

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.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • does it work if you use another dispid for bar_method ? now you have same on both functions (c9) – AndersK May 06 '14 at 07:45
  • And where is you class/object/interface implementation? Depends on what your interface uses internally in GetIDsOfNames.Seams that you implementation uses the same call internally for the IDispatch interface for both interfaces. Internally it is no problem that the interface uses different implementations for GetIDsOfNames... – xMRi May 06 '14 at 08:06
  • @Claptrap I tried changing bar_method's DispID but made no difference, `GetIDsOfNames` still fails to find `bar_method` on `ibar`, and does find `foo_method` – M.M May 06 '14 at 08:55
  • @xMRi The implementation is entirely compiler-generated default information , with a MessageBox in `foo_method` and `bar_method`. The implementation of `IDispatch` is done in the compiler's runtime library, as described in the last para of my post; it passes the same `ITypeInfo` to `DispGetIDsOfNames` (a Winapi function) for all interfaces. – M.M May 06 '14 at 08:58
  • Than you need you own implementation. You need to pass the correct information from the specific interface. In fact I don't know what standard implementation you mean (my compiler creates nothing). I use ATL and there I have to specify the list of interfaces I want to support, and ATL does the corresponding stuff. COM_INTERFACE_ENTRY2(IDispatch, IMyDualInterface1) COM_INTERFACE_ENTRY2(IDispatch, IMyDualInterface2) – xMRi May 06 '14 at 09:14
  • @xMRi my question is, does the COM specification say that this must work, or is it a "bonus extra" that some compilers allow? – M.M May 06 '14 at 09:19
  • This has nothing to do with compilersd. It has to do with implementation. – xMRi May 06 '14 at 09:21
  • @xMRi what is the difference between "compilers" and "implementation"? – M.M May 06 '14 at 09:22
  • I can use the same implementation on different compilers ;) I don't have to use the same language (i.e. C++) to create a COM client... – xMRi May 06 '14 at 09:27
  • Well, I'm using the implementation that comes with my compiler, I guess (the code that is called in order to satisfy requests to IUnknown, IDispatch etc.). Are there alternatives, e.g. can I ditch my compiler's version of ATL and try to build a standard-C++-compliant implementation of COM? – M.M May 06 '14 at 09:44
  • 1
    https://learn.microsoft.com/en-us/cpp/atl/multiple-dual-interfaces?view=vs-2017 – andrew.rockwell Mar 20 '19 at 19:38

1 Answers1

2

The specs don't have to say something to that.

  1. You have to different interfaces on one class. This is allowed.
  2. Both derive from IDispatch. This is allowed too.
  3. 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.

xMRi
  • 14,982
  • 3
  • 26
  • 59
  • OK, I will investigate that project and see if I can compile it. My compiler used to use ATL (which worked with the multiple dualinterfaces), but switched from ATL to something called DAX in about 2009, which I think is something they invented themself. I guess I should put in a bug report / feature request for DAX that it does not support this then. – M.M May 06 '14 at 09:47
  • If you are interested, full project can be seen [here](https://forums.embarcadero.com/servlet/JiveServlet/download/2-102793-644252-6652/DaxFail.zip) – M.M May 06 '14 at 10:24
  • Sorry, I am a VC-guy. I don't know enough about this Embacadero T-classes from C++Builder. All this stuff seams to be hidden in the TCppAutoObject template. – xMRi May 06 '14 at 12:18
  • FYI, DAX was introduced because Microsoft stopped licensing older versions of ATL, and newer versions of ATL depend on VC++-specific compiler extensions that C++Builder does not implement. That being said, if you have the ATL files from an older C++Builder version (you can [download them from my website](http://makefiles.lebeausoftware.org)), you can use ATL in modern C++Builder versions, and this is [documented by Embarcadero](http://docwiki.embarcadero.com/RADStudio/XE4/en/C++Builder_Uses_DAX_for_ActiveX_and_COM). – Remy Lebeau May 07 '14 at 23:40
  • @xMRi next question: how is it supposed to work that `IDispatch *ibar; QueryInterface(IID_IBar, (void **)&ibar);` works? I requested an `IBar` and this is the equivalent of doing a `reinterpret_cast` on an `IBar` to treat it as an `IDispatch`. Is that guaranteed to work? I'm also lost on how function calls work on `ibar->foo();` since interfaces are all structs defined with `__declspec(novtable)`. Is there compiler magic going on? – M.M May 08 '14 at 09:04
  • reinterpret cast is never needed. Each dual interface is always derived from IDispatch. So you can directly use the dual interface as a pointer to IDispatch. There is no magic! For all other cases you should directly call QueryInterface with the correct IID. Never cast COM pointer. – xMRi May 08 '14 at 09:13
  • Yes! 64bit build are available with ATL too! – xMRi May 08 '14 at 09:15
  • @xMRi, I mean, with Remy's old version of ATL. – M.M May 10 '14 at 22:04
  • I can't answer this. Look into the ATL code if 64bit pargmas and ifdefs are present. – xMRi May 11 '14 at 16:18