如何在 COM 事件源和处理程序中指定 "event type"?
How do you specify "event type" in COM event source and handler?
我有一个用 C# 编写的 COM 对象,我在 C++ 中使用它,在我不得不向它添加事件之前,它一直没有问题。我试过在这里查看无数教程、文档和问题,但奇怪的是 none 其中符合我的确切情况。
从我的代码中 hook/unhook 事件源到接收者的部分,我得到了这个错误:
Error C3731 incompatible event 'HRESULT EggplantClient::IEggplantClientEvents::Completed(void)' and handler 'HRESULT CReceiver::Completed(void)'; event source and event handler must have the same event type
我不知道这个“事件类型”是什么。我假设它是 CReceiver class 属性中的“com”部分:
[module(name = "EventReceiver")]
[event_receiver(com, true)]
class CReceiver {
...
至少这是我可以从 Microsoft documentation 中收集到的有关错误代码的信息。如果是这样,我如何将 C# 事件源设置为具有相同的类型?
我遇到的另一个非常奇怪的错误是:
Error C3702 ATL is required for COM events
这指向我定义class CReceiver
的行。对于错误,我包含与 Microsoft documentation 中完全相同的头文件。我还从第 [module(name = "EventReceiver")]
行收到 usage of ATL attributes is deprecated
的警告,我认为它们是相关的?
我已经坚持了好几天了。这是我第一次用 COM 做事,即使是 COM 服务器的基本实现也很困难,但试图让事件正常工作完全是一场噩梦。如果有人能以任何方式帮助解决这个问题,我将非常感激,即使 link 教程显示从 C# COM 服务器在 C++ 客户端中工作的事件也绰绰有余。以下是到目前为止我能够拼凑的相关部分。我使用 this 作为客户端代码,服务器部分我什至找不到了,因为我已经翻阅了这么多页面。
C# COM 服务器,事件源
namespace EggplantClient
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("C61C7C47-BB98-4DF3-BC61-7CA9430EDE7A")]
[ComVisible(true)]
public interface IEggplantClientEvents
{
[DispId(1)]
void Completed();
}
[Guid("0a805b99-756a-493c-96b7-063400f171ed")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IEggplantClientEvents))]
[ProgId("EggplantClient.CEggplantClient")]
public class CEggplantClient : IEggplantClient
{
[ComVisible(false)]public delegate void CompletedDelegate();
public event CompletedDelegate Completed;
...
C++ COM客户端,事件接收者
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include <stdio.h>
int Flag = 0;
[module(name = "EventReceiver")]
[event_receiver(com, true)]
class CReceiver {
public:
HRESULT Completed() {
printf_s("Event received");
Flag = 1;
return S_OK;
}
void HookEvent(EggplantClient::IEggplantClient* pSource) {
__hook(&EggplantClient::IEggplantClientEvents::Completed, pSource, &CReceiver::Completed);
}
void UnhookEvent(EggplantClient::IEggplantClient* pSource) {
__unhook(&EggplantClient::IEggplantClientEvents::Completed, pSource, &CReceiver::Completed);
}
};
Events in .NET are seen as Connection Point on native side. You can use them with ATL as described here ATL Connection Points and Event Handling Principles
所以这里有一个小回顾。
这是你的 C# class
namespace Eggplant
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("C61C7C47-BB98-4DF3-BC61-7CA9430EDE7A")]
[ComVisible(true)]
public interface IEggplantClientEvents
{
[DispId(1)]
void Completed(string text);
}
[Guid("0a805b99-756a-493c-96b7-063400f171ed")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IEggplantClientEvents))]
[ProgId("EggplantClient.CEggplantClient")]
public class CEggplantClient
{
[ComVisible(false)] public delegate void CompletedDelegate(string text);
public event CompletedDelegate Completed;
public CEggplantClient()
{
// wait 2 seconds and then call every second
Task.Delay(2000).ContinueWith(async t =>
{
do
{
Completed?.Invoke("Time is " + DateTime.Now);
await Task.Delay(1000);
}
while (true);
});
}
}
}
您可以像这样使用 .NET Framework 注册您的 C# class(将创建一个 Eggplant.tlb
文件):
%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe Eggplant.dll /codebase /tlb
注意:对于 .NET Core 和 .NET 5,您必须构建自己的 .TLB,或将 C# .NET Core 代码复制到 .NET Framework .dll 中并使用此 .NET .NET Core 代码。 ..
这是您的 C/C++ 代码(省略前向引用):
#include <windows.h>
#include <stdio.h>
#include <atlbase.h>
#include <atlcom.h>
#import "D:\kilroy\was\here\Eggplant\bin\Debug\Eggplant.tlb" // import the tlb
using namespace Eggplant; // #import by default puts generated code in a specific namespace
int main()
{
CoInitialize(nullptr);
{
CComPtr<IUnknown> app;
if (SUCCEEDED(app.CoCreateInstance(__uuidof(CEggplantClient))))
{
// sink events
auto sink = new CEggplantClientEventsSink();
if (SUCCEEDED(sink->Connect(app)))
{
// this message box allows us to wait while events arrive
MessageBox(nullptr, L"Click to stop listening", L"Events", MB_OK);
}
}
}
CoUninitialize();
return 0;
}
// this is the event sink
class CEggplantClientEventsSink : public CDispInterfaceBase<IEggplantClientEvents>
{
public:
CEggplantClientEventsSink() { }
HRESULT Invoke(DISPID dispid, DISPPARAMS* pdispparams, VARIANT* pvarResult)
{
switch (dispid)
{
case 1: // the Completed DISPID value
wprintf(L"Completed called text:%s\n", pdispparams->rgvarg[0].bstrVal);
break;
}
return S_OK;
}
};
// this is a generic support class to hook IDispatch events
// adapted from here: https://devblogs.microsoft.com/oldnewthing/20130612-00/?p=4103
template<typename DispInterface>
class CDispInterfaceBase : public DispInterface
{
LONG m_cRef;
CComPtr<IConnectionPoint> m_spcp;
DWORD m_dwCookie;
public:
CDispInterfaceBase() : m_cRef(1), m_dwCookie(0) { }
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
*ppv = nullptr;
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IUnknown || riid == IID_IDispatch || riid == __uuidof(DispInterface))
{
*ppv = static_cast<DispInterface*>(static_cast<IDispatch*>(this));
AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
IFACEMETHODIMP_(ULONG) Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (!cRef) delete this; return cRef; }
// IDispatch
IFACEMETHODIMP GetTypeInfoCount(UINT* pctinfo) { *pctinfo = 0; return E_NOTIMPL; }
IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { *ppTInfo = nullptr; return E_NOTIMPL; }
IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }
IFACEMETHODIMP Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
if (pvarResult) VariantInit(pvarResult);
return Invoke(dispid, pdispparams, pvarResult);
}
virtual HRESULT Invoke(DISPID dispid, DISPPARAMS* pdispparams, VARIANT* pvarResult) = 0;
public:
HRESULT Connect(IUnknown* punk)
{
CComPtr<IConnectionPointContainer> spcpc;
HRESULT hr = punk->QueryInterface(IID_PPV_ARGS(&spcpc));
if (SUCCEEDED(hr)) hr = spcpc->FindConnectionPoint(__uuidof(DispInterface), &m_spcp);
if (SUCCEEDED(hr)) hr = m_spcp->Advise(this, &m_dwCookie);
return hr;
}
void Disconnect()
{
if (m_dwCookie)
{
m_spcp->Unadvise(m_dwCookie);
m_spcp.Release();
m_dwCookie = 0;
}
}
};
这是结果:
我有一个用 C# 编写的 COM 对象,我在 C++ 中使用它,在我不得不向它添加事件之前,它一直没有问题。我试过在这里查看无数教程、文档和问题,但奇怪的是 none 其中符合我的确切情况。
从我的代码中 hook/unhook 事件源到接收者的部分,我得到了这个错误:
Error C3731 incompatible event 'HRESULT EggplantClient::IEggplantClientEvents::Completed(void)' and handler 'HRESULT CReceiver::Completed(void)'; event source and event handler must have the same event type
我不知道这个“事件类型”是什么。我假设它是 CReceiver class 属性中的“com”部分:
[module(name = "EventReceiver")]
[event_receiver(com, true)]
class CReceiver {
...
至少这是我可以从 Microsoft documentation 中收集到的有关错误代码的信息。如果是这样,我如何将 C# 事件源设置为具有相同的类型?
我遇到的另一个非常奇怪的错误是:
Error C3702 ATL is required for COM events
这指向我定义class CReceiver
的行。对于错误,我包含与 Microsoft documentation 中完全相同的头文件。我还从第 [module(name = "EventReceiver")]
行收到 usage of ATL attributes is deprecated
的警告,我认为它们是相关的?
我已经坚持了好几天了。这是我第一次用 COM 做事,即使是 COM 服务器的基本实现也很困难,但试图让事件正常工作完全是一场噩梦。如果有人能以任何方式帮助解决这个问题,我将非常感激,即使 link 教程显示从 C# COM 服务器在 C++ 客户端中工作的事件也绰绰有余。以下是到目前为止我能够拼凑的相关部分。我使用 this 作为客户端代码,服务器部分我什至找不到了,因为我已经翻阅了这么多页面。
C# COM 服务器,事件源
namespace EggplantClient
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("C61C7C47-BB98-4DF3-BC61-7CA9430EDE7A")]
[ComVisible(true)]
public interface IEggplantClientEvents
{
[DispId(1)]
void Completed();
}
[Guid("0a805b99-756a-493c-96b7-063400f171ed")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IEggplantClientEvents))]
[ProgId("EggplantClient.CEggplantClient")]
public class CEggplantClient : IEggplantClient
{
[ComVisible(false)]public delegate void CompletedDelegate();
public event CompletedDelegate Completed;
...
C++ COM客户端,事件接收者
#define _ATL_ATTRIBUTES 1
#include <atlbase.h>
#include <atlcom.h>
#include <atlctl.h>
#include <stdio.h>
int Flag = 0;
[module(name = "EventReceiver")]
[event_receiver(com, true)]
class CReceiver {
public:
HRESULT Completed() {
printf_s("Event received");
Flag = 1;
return S_OK;
}
void HookEvent(EggplantClient::IEggplantClient* pSource) {
__hook(&EggplantClient::IEggplantClientEvents::Completed, pSource, &CReceiver::Completed);
}
void UnhookEvent(EggplantClient::IEggplantClient* pSource) {
__unhook(&EggplantClient::IEggplantClientEvents::Completed, pSource, &CReceiver::Completed);
}
};
Events in .NET are seen as Connection Point on native side. You can use them with ATL as described here ATL Connection Points and Event Handling Principles
所以这里有一个小回顾。
这是你的 C# class
namespace Eggplant
{
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("C61C7C47-BB98-4DF3-BC61-7CA9430EDE7A")]
[ComVisible(true)]
public interface IEggplantClientEvents
{
[DispId(1)]
void Completed(string text);
}
[Guid("0a805b99-756a-493c-96b7-063400f171ed")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComSourceInterfaces(typeof(IEggplantClientEvents))]
[ProgId("EggplantClient.CEggplantClient")]
public class CEggplantClient
{
[ComVisible(false)] public delegate void CompletedDelegate(string text);
public event CompletedDelegate Completed;
public CEggplantClient()
{
// wait 2 seconds and then call every second
Task.Delay(2000).ContinueWith(async t =>
{
do
{
Completed?.Invoke("Time is " + DateTime.Now);
await Task.Delay(1000);
}
while (true);
});
}
}
}
您可以像这样使用 .NET Framework 注册您的 C# class(将创建一个 Eggplant.tlb
文件):
%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe Eggplant.dll /codebase /tlb
注意:对于 .NET Core 和 .NET 5,您必须构建自己的 .TLB,或将 C# .NET Core 代码复制到 .NET Framework .dll 中并使用此 .NET .NET Core 代码。 ..
这是您的 C/C++ 代码(省略前向引用):
#include <windows.h>
#include <stdio.h>
#include <atlbase.h>
#include <atlcom.h>
#import "D:\kilroy\was\here\Eggplant\bin\Debug\Eggplant.tlb" // import the tlb
using namespace Eggplant; // #import by default puts generated code in a specific namespace
int main()
{
CoInitialize(nullptr);
{
CComPtr<IUnknown> app;
if (SUCCEEDED(app.CoCreateInstance(__uuidof(CEggplantClient))))
{
// sink events
auto sink = new CEggplantClientEventsSink();
if (SUCCEEDED(sink->Connect(app)))
{
// this message box allows us to wait while events arrive
MessageBox(nullptr, L"Click to stop listening", L"Events", MB_OK);
}
}
}
CoUninitialize();
return 0;
}
// this is the event sink
class CEggplantClientEventsSink : public CDispInterfaceBase<IEggplantClientEvents>
{
public:
CEggplantClientEventsSink() { }
HRESULT Invoke(DISPID dispid, DISPPARAMS* pdispparams, VARIANT* pvarResult)
{
switch (dispid)
{
case 1: // the Completed DISPID value
wprintf(L"Completed called text:%s\n", pdispparams->rgvarg[0].bstrVal);
break;
}
return S_OK;
}
};
// this is a generic support class to hook IDispatch events
// adapted from here: https://devblogs.microsoft.com/oldnewthing/20130612-00/?p=4103
template<typename DispInterface>
class CDispInterfaceBase : public DispInterface
{
LONG m_cRef;
CComPtr<IConnectionPoint> m_spcp;
DWORD m_dwCookie;
public:
CDispInterfaceBase() : m_cRef(1), m_dwCookie(0) { }
// IUnknown
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
*ppv = nullptr;
HRESULT hr = E_NOINTERFACE;
if (riid == IID_IUnknown || riid == IID_IDispatch || riid == __uuidof(DispInterface))
{
*ppv = static_cast<DispInterface*>(static_cast<IDispatch*>(this));
AddRef();
hr = S_OK;
}
return hr;
}
IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_cRef); }
IFACEMETHODIMP_(ULONG) Release() { LONG cRef = InterlockedDecrement(&m_cRef); if (!cRef) delete this; return cRef; }
// IDispatch
IFACEMETHODIMP GetTypeInfoCount(UINT* pctinfo) { *pctinfo = 0; return E_NOTIMPL; }
IFACEMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) { *ppTInfo = nullptr; return E_NOTIMPL; }
IFACEMETHODIMP GetIDsOfNames(REFIID, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { return E_NOTIMPL; }
IFACEMETHODIMP Invoke(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
{
if (pvarResult) VariantInit(pvarResult);
return Invoke(dispid, pdispparams, pvarResult);
}
virtual HRESULT Invoke(DISPID dispid, DISPPARAMS* pdispparams, VARIANT* pvarResult) = 0;
public:
HRESULT Connect(IUnknown* punk)
{
CComPtr<IConnectionPointContainer> spcpc;
HRESULT hr = punk->QueryInterface(IID_PPV_ARGS(&spcpc));
if (SUCCEEDED(hr)) hr = spcpc->FindConnectionPoint(__uuidof(DispInterface), &m_spcp);
if (SUCCEEDED(hr)) hr = m_spcp->Advise(this, &m_dwCookie);
return hr;
}
void Disconnect()
{
if (m_dwCookie)
{
m_spcp->Unadvise(m_dwCookie);
m_spcp.Release();
m_dwCookie = 0;
}
}
};
这是结果: