【问题标题】:Exposing managed events to COM using C++使用 C++ 向 COM 公开托管事件
【发布时间】:2012-09-20 19:44:49
【问题描述】:

可以公开用 C# 编写的托管事件,以便在使用 c++ 编写的 COM 对象中公开和使用。对com和atl不太熟悉。对于 MSDN 文章中显示的示例,您能否展示一下 C++ 方面的情况

http://msdn.microsoft.com/en-us/library/dd8bf0x3.aspx

显示的VB6代码证明它是可行的。

【问题讨论】:

  • 您在非托管端使用什么技术,ATL、MFC 还是其他?
  • 我想只要工作完成,任何人都会这样做。我更喜欢简单明了的解决方案。

标签: c# c++ events com atl


【解决方案1】:

如果您可以使用 C++/CLI,您可以这样做 (source):

// class that defines methods that will called when event occurs
ref class EventReceiver {
public:
   void OnMyClick(int i, double d) {
      Console::WriteLine("OnClick: {0}, {1}", i, d);
   }

   void OnMyDblClick(String^ str) {
      Console::WriteLine("OnDblClick: {0}", str);
   }
};

int main() {
   EventSource ^ MyEventSource = gcnew EventSource();
   EventReceiver^ MyEventReceiver = gcnew EventReceiver();

   // hook handler to event
   MyEventSource->OnClick += gcnew ClickEventHandler(MyEventReceiver, &EventReceiver::OnMyClick);
}

【讨论】:

    【解决方案2】:

    IMO 中最简单的 C++ 方法是借助 ATL 的 IDispEventImplIDispEventSimpleImpl 模板实现事件接收器。示例项目can be found here的解释。

    有很多关于如何做到这一点的在线资源,例如thisthis,但这里是所需步骤的列表:

    首先让我们看一下托管端。

    为了提供事件,我们必须做到以下几点:

    • 声明一个事件接口(@98​​7654326@-based)
    • ComSourceInterfaces属性标记coclass,将事件接口绑定到coclass
    • 在 coclass 中实现匹配事件

    这里是托管代码:

    [ComVisible(true), 
     Guid("D6D3565F-..."), 
     InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] //! must be IDispatch
    public interface IMyEvents
    {
        [DispId(1)] // the dispid is used to correctly map the events
        void SomethingHappened(DateTime timestamp, string message);
    }
    
    [ComVisible(true)]
    [Guid("E22E64F7-...")]
    [ProgId("...")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(IMyEvents))] // binding the event interface
    public class MyComServer : IMyComServer  
    {
        // here we declare the delegate for the event
        [ComVisible(false)]
        public delegate void MyEventHandler(DateTime timestamp, string message);
    
        // and a public event which matches the method in IMyEvents
        // your code will raise this event when needed
        public event MyEventHandler SomethingHappened;
        ... 
    }
    

    现在,回到非托管方面。我将使用 ATL,因为我发现它是编写 COM 客户端的最有效方式,但您可以尝试 MFC 或“手动”进行。

    需要以下步骤:

    • 接收器将继承IDispEventSimpleImpl(或IDispEventImpl
    • 声明了包含所有需要的方法的接收器映射
    • 为每个事件编写处理程序方法
    • 接收器已向事件源注册
    • 最终,当不再需要时,接收器断开连接

    这是 ATL C++ 客户端中的代码:

    // import the typelib of your COM server
    // 'named_guids' ensures friendly ID of event interface
    #import "myserver.tlb" named_guids 
    const UINT SINK_ID = 234231341; // we need some sink id
    
    class MyClient : public IDispEventSimpleImpl<SINK_ID, MyClient, &MyServer::DIID_IMyEvents >
    {
        public:
        // now you need to declare a sink map - a map of methods handling the events
        BEGIN_SINK_MAP(MyClient)
          SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &someEvent)
                                                       ^      ^      ^                   ^
          // event interface id (can be more than 1)---+      |      |                   |
          // must match dispid of your event -----------------+      |                   |
          // method which handles the event  ------------------------+                   |
          // type information for event, see below --------------------------------------+
        END_SINK_MAP()
    
    // declare the type info object. You will need one for each method with different signature.
    // it will be defined in the .cpp file, as it is a static member
    static _ATL_FUNC_INFO someEvent;  // 'placeholder' object to carry event information (see below)
    
    // method which handles the event
    STDMETHOD (OnSomethingHappened)(DATE timestamp, BSTR message)
    { 
       // usually it is defined it in the .cpp file
    }
    
    ... 
    

    }

    现在,我们需要在 cpp 文件中定义类型信息成员(即上面示例中的 someEvent 实例):

    _ATL_FUNC_INFO MyClient::traceEvent = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} };  // dispid = 1
                                                   ^        ^     ^              ^
    // calling convention (always stdcall) --------+        |     |              |
    // type of return value (only VT_EMPTY makes sense) ----+     |              |
    // number of parameters to the event -------------------------+              |
    // Variant types of event arguments -----------------------------------------+
    

    这可能会很棘手,因为类型映射并不总是很明显(例如,很明显托管 int 映射到 VT_I4,但不太明显 DateTime 映射到 VT_DECIMAL)。 您需要在接收器映射中声明您计划使用的每个事件 - 如果您不需要所有事件,请不要映射它们。

    现在您需要将接收器连接到事件源:

    // IUnknown* pUnk = interface to you COM server instance
    pMyClient->DispEventAdvise(pUnk);
    // .. from this point, events will be caught by the client
    // when you are done, disconnect:
    pMyClient->DispEventUnadvise(pUnk);
    

    差不多就是这样。使用IDispEventImpl 而不是IDispEventSimpleImpl 会产生更少的代码,因为您不需要提供可能是最难看的部分的类型信息对象。但是,它有两个缺点:

    • 需要访问类型库(因为它需要读取接口元数据以提供类型信息本身)
    • 有点慢(但我猜不会很明显)

    【讨论】:

      【解决方案3】:

      Zdeslav Vojkovic 提出的解决方案对我来说几乎是完整的答案,但是在 Outlook 加载项中实施接收器时遇到了稳定性问题。在第一次不建议之后,系统变得不稳定,并且可能在下一次建议时崩溃。我的解决方案是将接收器作为 COM 对象。

      为简洁起见,我只添加了解决方案的 ATL 方面,因为托管代码可以完全按照 Zdeslav Vojkovic 的建议保留。

      我在 Visual Studio 2017 中按照以下步骤操作:

      创建 ATL 简单对象

      1. 右键单击 Project 并选择 Add > New Item...
      2. 选择 ATL 简单对象并单击添加按钮
      3. 在“其他”选项卡中,为线程模型选择“单一”
      4. 点击完成

      填写sink接口详情

      在生成的 idl 中添加用于初始化和关闭的处理程序:

      [
          object,
          uuid(a5211fba-...),
          dual,
          nonextensible,
          pointer_default(unique)
      ]
      interface IMyClient : IDispatch
      {
          [id(1), helpstring("method InitHandler"), local] HRESULT InitHandler(IUnknown* myserver);
          [id(2), helpstring("method ShutdownHandler"), local] HRESULT ShutdownHandler(void); 
      };
      

      填写头文件(MyClient.h)

      const UINT SINK_ID = 234231341;
      extern _ATL_FUNC_INFO SomethingHappenedInfo;
      
      // LIBID_MyATLComLib should point to the LIBID of the type library 
      class ATL_NO_VTABLE CMyClient :
          public CComObjectRootEx<CComSingleThreadModel>,
          public CComCoClass<CMyClient, &CLSID_MyClient>,
          public IDispatchImpl<IMyClient, &IID_IMyClient, &LIBID_MyATLComLib, /*wMajor =*/ 1, /*wMinor =*/ 0>,
          public IDispEventSimpleImpl<SINK_ID, CMyClient, &MyServer::DIID_IMyEvents>
      {
      public:
      
          typedef IDispEventSimpleImpl</*nID =*/ SINK_ID, CMyClient, &MyServer::DIID_IMyEvents> SomethingHappenedEvent;
      
          ...
      
          BEGIN_SINK_MAP(CMyClient)
              SINK_ENTRY_INFO(SINK_ID, MyServer::DIID_IMyEvents, 0x1, OnSomethingHappened, &SomethingHappenedInfo)
          END_SINK_MAP()
      
          ...
      
      private:
          CComQIPtr<MyServer::IMyComServer> mMyComServer; 
      
      public:    
          STDMETHOD(InitHandler)(IUnknown* myserver);
          STDMETHOD(ShutdownHandler)(void);
      
          void __stdcall OnSomethingHappened(DateTime timestamp, string message);     
      };
      

      一些生成的代码被保留为“...”

      填写C++代码(MyClient.cpp)

      _ATL_FUNC_INFO SomethingHappenedInfo = { CC_STDCALL, VT_EMPTY, 2 , {VT_DECIMAL, VT_BSTR} };
      
      
      STDMETHODIMP CMyClient::InitHandler(IUnknown* myserver)
      {
          this->mMyComServer = myserver;
          SomethingHappenedEvent::DispEventAdvise((IDispatch*)this->mMyComServer);
      
          return S_OK;
      }
      
      
      STDMETHODIMP CMyClient::ShutdownHandler(void)
      {    
          SomethingHappenedEvent::DispEventUnadvise(this->mMyComServer);          
      
          return S_OK;
      }
      
      void __stdcall CMyClient::OnSomethingHappened(DateTime timestamp, string message)
      {   
          ...
      }
      

      请注意这里的 Advise/Unadvise 调用是不同的。

      【讨论】:

        猜你喜欢
        • 2017-01-23
        • 2012-07-27
        • 2010-12-31
        • 1970-01-01
        • 2021-07-26
        • 2015-10-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多