扩展MIDL接口和COM对象设计

时间:2011-03-03 16:46:43

标签: winapi com 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看不到它,但是我不应该使用不受支持的C ++继承,因为我不希望它直接暴露给COM。

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

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

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

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

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

编辑:我回答了我自己的问题。 Intel had documentation这个错误,我最初没有点击链接,假设它可能不适用于Visual Studio。我希望我做了但是现在我理解为什么我得到这个错误,因为我试图在Base ::而不是IUnknown ::或IDispatch ::中进行所有实现。现在这引出了一个新的问题,可能会澄清我原来的&amp;主要问题 - 我如何将实现从IUnknown(和其他)推迟到Base并且只能从Base工作,如果可能的话?似乎如果我只使用IUnknown :: xxx,它就不能再访问Base的私有成员了,这似乎是理所当然的,所以这可能不是我想要的。我尝试将除base之外的所有其他接口声明为虚拟,但这并没有真正占用。 (再次,可能是我没有看到明显解决方案的经验。)

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

各种编译错误让我担心我对语言和语言中的一种或两种都缺乏经验框架正在引导我对如何设计COM对象进行有缺陷的假设。继承层次结构和实现细节,我肯定在这里遗漏了一些东西。任何指针,甚至是头上的一记耳光都会非常感激。

谢谢!

1 个答案:

答案 0 :(得分:6)

你在这里得到了正确的想法,你所缺少的只是在最派生的课程中占用一些松散的目标。作为COM开发人员,您希望类对象上的所有AddRef / Release / QI impl都是相同的;但面向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 - 即使只是空的,所以当Base在ref为0时执行'删除'时,它将调用派生类中的dtors并且他们分配的任何资源都得到适当的清理。此外,请务必确保引用计数正确,并在需要时进行线程安全;查看COM书的任何好的介绍(例如“内部分布式COM”,尽管名称,以普通COM开始),以了解其他人如何做到这一点。

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