用c++ (ATL, MFC或纯c++)处理COM事件,实现Java互操作

visual studio - Handling COM events in C++ (ATL, MFC or pure C++) for Java interop

本文关键字:c++ 事件 COM 实现 Java 互操作 处理 或纯 ATL MFC      更新时间:2023-10-16

我目前正在尝试构建一个c++库(DLL文件),与COM组件接口,使其在Java中可用。我的想法是,我将构建一个非常小的c++ DLL,其中包含一个"包装"在COM组件周围的类,然后使用SWIG导出它。我使用了#import语句:

#import "ComponentName.dll"

调用CoInitialize()并创建组件的实例(通过由Visual Studio生成的IComponentNamePtr类)。这对所有普通的COM方法调用都有效,这很好。

然而,我不知道如何让事件工作。我看到有一个IComponentNameEventsPtr补充了主要的"智能指针"类,但我不知道该怎么做才能让它工作

我已经尝试了以下所有事件的工作:

  • 纯c++ —我不知道该怎么做。我尝试创建一个从IComponentNameEvents类继承的新类,为所有抽象函数制作存根,并覆盖函数,除了函数没有标记为虚拟,所以没有工作。
  • MFC
  • 本;我无法得到调用AfxOleInit正确工作。谷歌告诉我,当从DLL调用时,调用失败,因为它假设OLE已经初始化。我不太确定如何解决这个问题。每次我试图创建COM组件的实例时,我的库都会崩溃(我认为是因为COM没有正确初始化)
  • ATL 本;我不知道如何在ATL中进行项目。我可以创建类(通过"简单ATL"向导,然后是"实现接口"向导),但不知道如何使用它。我在MSDN上阅读了Using IDispEventImpl,但不知道如何使用该方法生成的类。我是否需要通过ATL来使用COM对象(或者我可以使用#import自动生成的类)?如何"附加"事件侦听器类?
  • 我读事件处理在COM使用event_receiver属性(新的Visual c++统一事件模型的一部分)。最初,我不知道如何将其与通过#import语句创建的COM组件的使用相结合。我终于弄清楚了(并且在页面上提到了!),我需要在#import语句上使用"embedded_idl"标志,但这破坏了其他事情(我在.tlh文件中得到了一堆"期望类型规范接近"的错误)

有人知道怎么做吗?最简单的方法是什么?我的背景是c#和PHP,所以我没有太多在c++中使用COM的经验。

tldr:在c++ DLL中使用COM事件最简单的方法是什么?

在代码中实现源接口(使用任何机制,包括可能使用midl编译器生成纯C代码)。在您的外部类型库(您正在使用的类型库)中查找如下接口:

 [source] interface IOutGoing;

一旦你实现了它,使用Advise在事件源对象上注册它(用Unadvise取消注册)

下面的代码片段显示了典型的用法,假设您选择了MIDL方式(使用ATL/MFC,您必须编写更少的代码,但了解更多的宏/模板)

class CSink : public IOutGoing
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);
    // IOutGoing
    HRESULT __stdcall GotMessage(int Message);
    CSink() : m_cRef(0) { }
    ~CSink() { }
private:
    long m_cRef;
};

IUnknown* pUnknown;
CoCreateInstance(CLSID_XXXXXXXXX, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void**)&pUnknown);
IConnectionPointContainer* pConnectionPointContainer;
hr = pUnknown->QueryInterface(IID_IConnectionPointContainer, (void**)&pConnectionPointContainer);
hr = pConnectionPointContainer->FindConnectionPoint(IID_IOutGoing, &pConnectionPoint);
// Instantiate the sink object.
CSink* mySink = new CSink;
// Give the connectable object a pointer to the sink.
DWORD dwCookie;
pConnectionPoint->Advise((IUnknown*)mySink, &dwCookie);

实际上我自己使用统一事件模型得到了这个工作。我的一些心得之谈:

  • 您需要初始化ATL,否则在连接事件时发生空指针错误。如果你不想在你的项目中使用ATL(就像我没有-我的项目是纯c++),像这样的东西在dllmain.cpp工作得很好(用一个虚拟模块初始化ATL):

    class CDummyModule : public CAtlDllModuleT<CDummyModule> {};
    CDummyModule _Module;
    
  • 你需要在#import行后面加上"embedded_idl"

    #import "ComponentName.dll" embedded_idl
    
  • 如果组件中嵌入了任何结构,您可能会由于VisualStudio中的错误而得到一些MIDL2025错误("期望接近类型规范")(参见https://connect.microsoft.com/VisualStudio/feedback/details/333473/midl2025-migrating-an-attributed-com-project-to-vs-2008-pro-with-exported-structures和http://social.msdn.microsoft.com/forums/en-US/vcgeneral/thread/03b78133-5eac-4754-b9af-fc864a9389a3)。解决方法是添加:

    [importidl("vsbugfix.idl")];
    

然后加上

typedef struct StructName StructName;

对于每个抛出错误的结构体

  • 一旦完成,您应该能够初始化COM并创建对象的实例。丑陋的示例代码:

    IComponentName blah;
    HRESULT hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        MessageBox(NULL, "Failed to initialize COM", "Hi!", 0);
        throw "Failed to initialize COM";
    }
    try
    {
        hr = blah.CreateInstance("Something.Something");
        if (FAILED(hr))
        {
            CoUninitialize();
            MessageBox(NULL, "Failed to initialize the COM library!!", "Hi!", 0);
            throw "Failed to initialize the COM library";
        }
    }
    catch (...)
    {
        MessageBox(NULL, "Exception occured when initialising library!", "Error", 0);
        CoUninitialize();
        throw "Exception occured when initialising library!";
    }
    

一旦你有了你的COM对象,你就可以按照MSDN"COM中的事件处理"文章来连接事件:

__hook(&IComponentNameEvents::OnWhatever, blah, &EventHandlerClass::OnWhatever);

确保在调用CoUninitialize()之前解除所有事件的挂钩,否则会得到错误。