winapi - 扩展MIDL接口(interface)和COM对象设计

标签 winapi com activex ole

我已经阅读了COM Programmer's Cookbook中详细介绍的各种COM设计模式以及一些相关的SO线程,尤其是the thread discussing composition vs. multiple inheritance。可能是因为我对C++和COM都不太熟悉,所以我可能会遗漏各种来源中提出的观点,因此这是我用单句话表达的问题:

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

肮脏的细节...

希望可以帮助其他人弄清楚我的困惑所在,这是我的假设:

1)COM不支持虚拟继承,仅允许通过接口(interface)进行多次继承。

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

3)因为MIDL仅允许接口(interface)的单个​​继承,所以如果我具有并行层次结构,则需要将它们聚合为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
   };
};

基数
#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 *对象总是意味着要创建基础对象,因为需要base来处理实现细节,因此我认为让base负责QueryInterface和Reference计数也很合适,但是我很困惑在以下几点上:

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

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

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

3)Base的QueryInterface无法将base强制转换为孩子,这是合理的抱怨,因此我认为无论如何我都必须重新实现所有孩子的QI,但是一旦我确定所请求的接口(interface)不是孩子的QI,我就可以将其委托(delegate)给base的QI。奇怪的是,由于缺少IUnknown&IDispatch的成员,编译器坚持认为child *类是抽象的,但是基类是否尚未实现,因此child也应该具有这些成员?

各种编译器错误使我担心,由于我对语言和框架中的任何一种或两种都缺乏经验,导致我对如何设计COM对象和继承层次结构以及实现详细信息做出了错误的假设,而我肯定在这里遗漏了一些东西。任何指针,甚至是打耳光都将不胜感激。

谢谢!

最佳答案

您在这里有了一个正确的想法,您所缺少的只是在最派生的 class 中 bundle 了一些松散的一端。作为COM开发人员,您希望类对象上的所有AddRef / Release / QI都相同。但是面向C++的编译器不知道这一点,因此将它们视为可能是独立的。您在此处获得的两个提示是Base中的一个,在您添加的任何接口(interface)中的一个。

在这里直接设置编译器非常容易:在最派生的类中,重新定义所有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一样),然后将其委派给众所周知的基类,负责处理许多家务。

关于winapi - 扩展MIDL接口(interface)和COM对象设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5183763/

相关文章:

c# - 为什么 Task.Delay 会破坏线程的 STA 状态?

javascript - 如何使用javascript获取txt文件的长度

java - 将 ActiveX 控件添加到 JPanel

c++ - BSTR 中的内存泄漏到 wstring

delphi - 将 C stdcall 接口(interface)方法中的变量参数转换为 Delphi

c++ - 如何将 BSTR 与 c/c++ 中的字符串进行比较?

windows - 为什么 WmiPrvSE.exe 持有我进程的作业对象的句柄?

macos - 如何验证系统中安装的客户端证书

.net - 使用 SendMessage API 检索 ComboBox 计数和项目

python - 如何使用 pywin32 知道窗口是否最大化?