【问题标题】:How do you specify "event type" in COM event source and handler?如何在 COM 事件源和处理程序中指定“事件类型”?
【发布时间】:2021-06-17 04:15:55
【问题描述】:

我有一个用 C# 编写的 COM 对象,我在 C++ 中使用它,在我不得不向它添加事件之前,它可以正常工作。我尝试在这里查看无数教程、文档和问题,但奇怪的是,它们都不适合我的确切情况。

从我的代码中将事件源挂钩/取消挂钩到接收器的部分,我收到此错误:

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 类属性中的“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 服务器的基本实现也很困难,但试图让事件工作完全是一场噩梦。如果有人能以任何方式对此提供帮助,我将不胜感激,即使是指向显示在 C++ 客户端中的 C# COM 服务器上工作的事件的教程的链接也绰绰有余。以下是到目前为止我能够拼凑的相关部分。我使用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);
    }
};

【问题讨论】:

    标签: c# c++ events com atl


    【解决方案1】:

    Events in .NET are seen as Connection Point 在本机端。您可以将它们与 ATL 一起使用,如下所述ATL Connection PointsEvent Handling Principles

    所以这里有一个小回顾。

    这是你的 C# 类

    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# 类(将创建一个 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 中并使用它... em>

    这是您的 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# 代码中实际发送一串数据,我没有将其包含在问题中,因为我认为之后添加它是微不足道的:)。你知道这个解决方案是否支持在事件中包含参数?
    • @taiteilijaumbra - 当然,我已经更新了我的代码。
    • 您先生,绝对是个传奇。再一次,我不能感谢你。
    猜你喜欢
    • 1970-01-01
    • 2011-08-14
    • 1970-01-01
    • 2013-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多