开发者

Extending MIDL interfaces and COM object design

开发者 https://www.devze.com 2023-02-15 02:57 出处:网络
I\'ve read about various COM design patterns detailed in COM Programmer\'s Cookbook as well some of related SO threads, notably the thread discussing composition vs. multiple inheritance. Possibly bec

I've read about various COM design patterns detailed in COM Programmer's Cookbook as well some of related SO threads, notably the thread discussing composition vs. multiple inheritance. Possibly because I'm too new to both C++ and COM, I may be missing the points made in various sources so here's my question expressed in single sentence:

Can I extend an interface generated by MIDL for DLL's internal use and if so, how do I handle the diamond problem/parallel hierarchy correctly given MIDL/COM limitation?

The dirty details...

Hopefully to help other pinpoint where my confusion may be, here are my assumptions:

1) COM does not support virtual inheritance, and only allows for multiple inheritance via interfaces only.

2) Even though COM cannot see it, it should not be illegal for me to use unsupported C++ inheritance as long I do not expect it to be directly exposed by COM.

3) Because MIDL only allows single inheritance of interfaces, if I have a parallel hierarchy, I need to aggregate them for the coclass.

4) MIDL does not appear to declare the coclass itself, so I would need to write a .h file declaring the actual class and there, I may extend as needed with understanding COM consumers cannot use it (and that's OK).

What I want to do is have a base object (I've not decided yet whether it'll be abstract or not though I think it will be for now) that handles most of implementation details and delegate some specific functionality to subclasses. The client would typically use the subclasses. So,

project.idl

import "oaidl.idl"
import "ocidl.idl"

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IBase : IDispatch {
   //stuff I want to show to COM
};

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IChild1 : IBase {
   //stuff (in addition to base) I want to show to COM
};

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IChild2 : IBase {
   //stuff (in addition to base) I want to show to COM
};

[
   uuid(...),
   version(...),
]
library myproject {
   importlib("stdole32.tlb");
   interface IBase;
   interface IChild1;
   interface IChild2;
   [
      uuid(...),
   ]
   coclass Base {
      [default]interface IBase;
      interface IOle*; //include other IOle* interfaces required for the functionality
   };
   [
      uuid(...),
   ]
   coclass Child1 {
      [default]interface IChild1;
      interface IOle*; //those are delegated to the base members
   };
   [
      uuid(...),
   ]
   coclass Child2 {
      [default]interface IChild2;
      interface IOle*; //those are delegated to the base members
   };
};

base.h

#include base_h.h //interfaces generated by MIDL

// I assume I need to re-include IOle* because IBase has no relationship
// and C++ wouldn't know that I want the object base to also have those
// interfaces...
class base : public IBase,
             public IOle* {
    //handle all IUnknown, IDispatch and other IOle* stuff here
    //as well as the common implementations as appropriate
};

child1.h

#include base.h

//I'm not sure if I need to re-include the IOle* interfaces...
//I also assume that by inheriting base, child1 also inherits its interface
class Child1 : public Base,
               public IChild1 {
  //specific details only, let base handle everything else.
};

child2.h

#include base.h

//I'm not sure if I need to re-include the IOle* interfaces...
class Child2 : public Base,
               public IChild2 {
  //specific details only, let base handle everything else.
};

Conceptually, creating a new child* object would always imply a creation 开发者_开发百科of base object because base would be required to handle the implementation details, so I thought it's also appropriate to have the base take care of the QueryInterface & Reference counting, but I get confused on the following points:

1) compiler complains about members being ambiguous due to parallel hierarchy; IUnknown is re-implemented several times from my custom interface and from the additional IOle* interfaces. Documentations suggests that it is expected that only one implementation per object is actually needed but I'm not clear how I'd address the compiler's issues and I feel that doing a casting is somehow wrong? I also wonder if I am supposed to have all interfaces be inherited virtually which seems to be valid for C++, though COM wouldn't have no such understanding but it shouldn't care either(?).

2) However, if I do declare all of inherited interfaces as virtual in the .h files, compiler then complains that inherited members are not allowed when I try to implement QueryInterface in the Base class.cpp. I've googled on that error but am not clear what it is trying to tell me here.

EDIT: I answered my own question. Intel had documentation for this error which I initially did not click on the link, assuming that it may not apply to Visual Studio. I wish I did anyway but now I understand why I was getting this error since I was trying to do all implementation in Base::, rather than IUnknown:: or IDispatch::. This now begs a new question which may clarify my original & main question -- how do I defer the implementation from IUnknown (and others) to Base and work only from Base, if that is even possible? It seems that if I just use IUnknown::xxx, it no longer can access the Base's private members, which seems sane thing to expect so that may not be what I want. I tried declaring all other interfaces except base's own as virtual, but that didn't really take. (Again, it may be my inexperience not seeing the obvious solution.)

3) Base's QueryInterface cannot cast base to a child, which is a reasonable complaint, so I assume I have to reimplement the QI for all children anyway but I can delegate back to base's QI once I determine the requested interfaces isn't the child's. Bizarrely, the compiler insists that the child* class are abstract due to missing members for IUnknown & IDispatch, but didn't the base already implement and thus the child should have those members as well?

The various compiler errors makes me worry that my inexperience with either or both of language & framework is leading me to make a flawed assumptions about how I can design the COM objects & inheritance hierarchy and implementation details and I'm decidedly missing something here. Any pointers, even a slap on the head would be much appreciated.

Thanks!


You've got the right idea here, all you're missing is tying up some loose ends in the most-derived class. As a COM developer, you expect all the AddRef/Release/QI impls on a class object to be the same; but the C++-oriented compiler doesn't know that, so is treating them as all being potentially separate. The two impls you have here are the one in Base and the ones in any interfaces you've added.

Setting the compiler straight here is pretty easy: in the most derived class, redefine all the IUnknown methods, and direct them to the appropriate base class - eg.

class ChildX: public Base,
              public IChildA
              ... more COM interfaces if needed ...
{
    ...

    // Direct IUnknown methods to Base which does the refcounting for us...
    STDMETHODIMP_(ULONG) AddRef() { return Base::AddRef(); } 
    STDMETHODIMP_(ULONG) Release() { return Base::Release(); } 
    ... suggest implementing QI explicitly here.
}

This basically says that all methods called AddRef, regardless of how they ended up in ChildX, will get that specific implementation.

Its simplest to actually implement QI outright here, and only delegate AddRef/Release to Base. (Technically, Base can cast to Child using static_cast, but you need to put the code in an function after Child has been fully defined; it's not recommended to do this, however, since there's rarely a good reason for a base class to know about the classes that derive from it.)

Other things to watch for: make sure that Base has a virtual dtor declared - even if just empty, so that when Base does a 'delete this' when the ref goes to 0, it will call the dtors in the derived classes and any resources that they have allocated get cleaned up appropriately. Also, be sure to get the ref counting correct, and thread-safe if needed; check with any good intro to COM book (eg "Inside Distributed COM", which, despite the name, starts off with plain COM) to see how other folk do this.

This is a very common idiom in COM, and many frameworks use either #define macros or a more-derived template class to add in the AddRef/Release/QI (as MFC does) at the most-derived class and then delegate those to a well-known base class that handles much of the housekeeping.

0

精彩评论

暂无评论...
验证码 换一张
取 消