扩展 MIDL 接口和 COM 对象设计

发布于 2024-10-20 10:34:18 字数 4218 浏览 3 评论 0原文

我已经阅读了 COM Programmer's Cookbook 中详细介绍的各种 COM 设计模式以及一些相关的 SO 线程,特别是 讨论组合与多重继承的线程< /a>.可能因为我对 C++ 和 COM 都太陌生,所以我可能会错过各种来源中提出的观点,所以这是我用一句话表达的问题:

我可以扩展由 MIDL 生成的接口以供 DLL 内部使用吗?如果可以的话 我如何正确处理钻石问题/并行层次结构

,在给定 MIDL/COM 限制的情况

下,

?虚拟继承,并且仅允许通过接口进行多重继承。

2)即使COM看不到它,只要我不希望COM直接公开它,那么使用不受支持的C++继承就不应该是非法的。

3)因为MIDL只允许接口的单一继承,如果我有一个并行的层次结构,我需要将它们聚合到组件类中。

4) MIDL 似乎没有声明 coclass 本身,因此我需要编写一个 .h 文件来声明实际的类,在那里,我可以根据需要进行扩展,并理解 COM 使用者无法使用它(这没关系)。

我想要做的是拥有一个处理大部分实现细节并将某些特定功能委托给子类的基础对象(我尚未决定它是否是抽象的,但我认为现在会是抽象的)。客户端通常会使用子类。因此,

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.
};

从概念上讲,创建一个新的 child* 对象总是意味着创建基础对象,因为需要基础对象来处理实现细节,所以我认为让基础对象处理 QueryInterface 和 QueryInterface 也是合适的。引用计数,但我对以下几点感到困惑:

1)编译器抱怨由于并行层次结构而导致成员不明确; IUnknown 从我的自定义接口和其他 IOle* 接口中重新实现了多次。文档表明,预计每个对象实际上只需要一个实现,但我不清楚如何解决编译器的问题,并且我觉得进行强制转换在某种程度上是错误的?我还想知道我是否应该让所有接口都被虚拟继承,这似乎对于C++来说是有效的,尽管COM不会没有这样的理解,但它也不应该关心(?)。

2) 但是,如果我确实在 .h 文件中将所有继承接口声明为虚拟接口,则当我尝试在 Base class.cpp 中实现 QueryInterface 时,编译器会抱怨不允许继承成员。我已经用谷歌搜索了该错误,但不清楚它在这里试图告诉我什么。

编辑:我回答了我自己的问题。 英特尔有关于此错误的文档,我最初没有点击该链接,假设它可能不适用于 Visual Studio。无论如何我希望我这样做,但现在我明白为什么我会收到此错误,因为我试图在 Base:: 中执行所有实现,而不是 IUnknown:: 或 IDispatch:: 。现在这就引出了一个新问题,可以澄清我的原始问题和答案。主要问题——如果可能的话,如何将实现从 IUnknown (和其他)推迟到 Base 并仅从 Base 工作?看来,如果我只使用 IUnknown::xxx,它就不再可以访问 Base 的私有成员,这似乎是合理的预期,所以这可能不是我想要的。我尝试将除基础接口之外的所有其他接口声明为虚拟接口,但这并没有真正实现。 (同样,这可能是我缺乏经验,没有看到明显的解决方案。)

3)Base的QueryInterface无法将base转换为子项,这是一个合理的抱怨,所以我认为无论如何我都必须为所有子项重新实现QI,但我可以委托回来一旦我确定请求的接口不是孩子的接口,就发送给基地的 QI。奇怪的是,编译器坚持认为 child* 类是抽象的,因为缺少 IUnknown & 成员。 IDispatch,但是基础不是已经实现了,因此孩子也应该拥有这些成员吗?

各种编译器错误让我担心我对语言和语言中的任何一个或两者都缺乏经验。框架导致我对如何设计 COM 对象和 COM 对象做出了有缺陷的假设。继承层次结构和实现细节,我在这里肯定遗漏了一些东西。任何指点,甚至是打在头上的一记耳光,都将不胜感激。

谢谢!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

-柠檬树下少年和吉他 2024-10-27 10:34:18

您在这里的想法是正确的,您所缺少的只是在最派生的类中解决一些松散的问题。作为一名 COM 开发人员,您希望类对象上的所有 AddRef/Release/QI 实现都是相同的;但面向 C++ 的编译器不知道这一点,因此将它们视为可能是独立的。这里的两个 impl 是 Base 中的一个和您添加的任何接口中的一个。

在这里直接设置编译器非常简单:在最派生的类中,重新定义所有 IUnknown 方法,并将它们定向到适当的基类 - 例如。

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.
}

这基本上是说,所有名为 AddRef 的方法,无论它们如何在 ChildX 中结束,都将获得特定的实现。

最简单的方法是直接在这里实现 QI,并且仅将 AddRef/Release 委托给 Base。 (从技术上讲,Base 可以使用 static_cast 转换为 Child,但是您需要在 Child 完全定义后将代码放入函数中;但是,不建议这样做,因为基类很少有充分的理由了解从它派生的类。)

其他需要注意的事项:确保 Base 声明了一个虚拟 dtor - 即使只是空的,这样当 ref 变为 0 时 Base 执行“删除此”操作时,它将调用派生类中的 dtor 以及它们分配的任何资源都会得到适当的清理。另外,请确保引用计数正确,并在需要时确保线程安全;查看任何关于 COM 的优秀介绍书籍(例如“Inside Distributed COM”,尽管有这个名称,但都是以普通 COM 开头),看看其他人是如何做到这一点的。

这是 COM 中非常常见的习惯用法,许多框架使用 #define 宏或更多派生的模板类来添加到最派生类的 AddRef/Release/QI(如 MFC 所做的),然后将它们委托给处理大部分内务工作的著名基类。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文