1

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

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

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

4

3 回答 3

7

IDispEventImplC++ 中最简单的方法是,IMO,在 ATL和IDispEventSimpleImpl模板的帮助下实现事件接收器。可以在此处找到示例项目的说明。

有很多关于如何执行此操作的在线资源,例如thisthis,但这里是所需步骤的列表:

首先让我们看一下托管方面。

为了提供事件,我们必须执行以下操作:

  • 声明一个事件接口(IDispatch基于 -)
  • 用属性标记 coclassComSourceInterfaces以将事件接口绑定到 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,但映射到 不太明显DateTimeVT_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导致代码更少,因为您不需要提供可能是最丑陋的部分的类型信息对象。但是,它有两个缺点:

  • 需要访问类型库(因为它需要读取接口元数据以提供类型信息本身)
  • 有点慢(但我猜不是很明显)
于 2012-09-29T14:03:56.060 回答
0

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

// 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);
}
于 2012-09-28T22:38:41.580 回答
0

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 调用以不同的方式进行。

于 2018-11-11T13:13:16.103 回答