扩展MIDL接口和COM对象设计

人气:691 发布:2022-10-16 标签: com winapi activex ole

问题描述

我读过COM Programmer's Cookbook中详细介绍的各种COM设计模式,以及一些相关的SO线程,特别是the thread discussing composition vs. multiple inheritance。可能是因为我对C++和COM都太陌生了,我可能错过了各种来源中的观点,所以我的问题是用一句话来表达的:

我是否可以扩展由MIDL生成的接口以供DLL内部使用,如果可以,我如何在MIDL/COM限制的情况下正确处理菱形问题/并行层次结构?

肮脏的细节...

希望能帮助其他人找出我的困惑所在,以下是我的假设:

1)COM不支持虚拟继承,只允许通过接口进行多重继承。

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

3)因为MIDL只允许接口的单一继承,所以如果我有一个并行层次结构,我需要为CoClass聚合它们。

4)MIDL似乎不声明CoClass本身,所以我需要编写一个.h文件来声明实际的类,在那里,我可以根据需要进行扩展,因为我知道COM使用者不能使用它(这是可以的)。

我想要做的是有一个基本对象(我还没有决定它是否将是抽象的,尽管我认为它现在会是抽象的),它处理大部分实现细节并将一些特定的功能委托给子类。客户端通常会使用子类。所以,

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

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

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

从概念上讲,创建新的子*对象总是意味着创建base对象,因为base将需要处理实现细节,所以我认为让base负责QueryInterface&;引用计数也是合适的,但我对以下几点感到困惑:

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

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

编辑:我回答了自己的问题。Intel had documentation对于这个错误,我最初没有点击该链接,假设它可能不适用于Visual Studio。我希望我无论如何都要这样做,但现在我明白了为什么我会收到这个错误,因为我试图在Base::而不是IUnnow::或IDispatch::中完成所有的实现。这就引出了一个新的问题,这个问题可能会澄清我最初的主要问题--如果可能的话,我如何将实现从I未知(和其他)推迟到Base并仅从Base工作?似乎如果我只使用IUnnow::xxx,它就不能再访问Base的私有成员,这似乎是正常的事情,所以这可能不是我想要的。我尝试将除base自己的接口之外的所有其他接口声明为虚拟接口,但这并没有真正起作用。(再说一次,可能是我缺乏经验,没有看到明显的解决方案。)

3)Base的QueryInterface不能将base强制转换为子对象,这是一个合理的抱怨,所以我假设我无论如何都必须为所有的子对象重新实现QI,但是一旦我确定所请求的接口不是该子对象的接口,我就可以委托回base的QI。奇怪的是,由于缺少IUnnow&;IDispatch的成员,编译器坚持认为Child*类是抽象的,但基不是已经实现了吗?因此该子对象也应该具有这些成员?

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

谢谢!

推荐答案

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

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

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--即使只是空的,这样,当Base在ref变为0的情况下执行"DELETE This"时,它将调用派生类中的dtor,并且它们分配的任何资源都会得到适当的清理。此外,确保引用计数正确,如果需要,确保线程安全;查看任何好的COM书籍介绍(例如《Inside Distributed COM》,尽管名为Inside Distributed COM,但以普通COM开头),以了解其他人是如何做到这一点的。

这是COM中非常常见的习惯用法,许多框架使用#Define宏或更派生的模板类在派生最多的类中添加AddRef/Release/QI(如MFC所做的那样),然后将其委托给处理大部分内务的众所周知的基类。

143