【问题标题】:How to call a C++ delegated function from C#, when C++ call C# functions?当 C++ 调用 C# 函数时,如何从 C# 调用 C++ 委托函数?
【发布时间】:2016-05-06 11:02:05
【问题描述】:

我想从 C++ 调用一个 C# 库。

C# 库,请求一个委托函数, 为了报告结果。

也许我的目的令人困惑:概念是,C++ 调用 C# 函数,而 C# 函数将从 C++ 调用回调函数。

我在 C# 调用 C++ 回调函数中被阻止了,COM Interop 对我来说是神秘的。

我的示例代码是:

C#代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace CSharpLibraryNameSpace
{
    // Interface declaration.
    public delegate int NativeDelegateType(int x);
    //public delegate int NativeDelegateType([In, MarshalAs(UnmanagedType.LPStr)] string arg1);

    public interface ManagedInterface
    {
        int Add(int Number1, int Number2);

        int CalltheCallbackFun(NativeDelegateType callbackFun);
    };


    // Interface implementation.
    public class ManagedCSharpClass : ManagedInterface
    {
        public int Add(int Number1, int Number2)
        {
            Console.Write("Add\n");
            return Number1 + Number2;
        }

        public int 
            CalltheCallbackFun(
            /*[MarshalAs(UnmanagedType.FunctionPtr)]*/ NativeDelegateType callbackFun)
        {
            Console.Write("BB\n");
            string str;
            str = "AAA";
           unsafe
           {
               fixed (char* p = str)
               {
                   Console.Write("before call callbackFun\n");

                   callbackFun(0x01);
               }
           }
            return 0;
        }
    }
}

C++ 代码:

#include <windows.h>
// Import the type library.

#import "CSharpLibrary.tlb" raw_interfaces_only
using namespace CSharpLibrary;

 typedef void (__stdcall * C_Callback)(int);

 __declspec(dllexport) int __stdcall theCallback(void)
{
    return 0;
}/*theCallback*/

 class CPPcallback :public _NativeDelegateType
 {
 public:
     CPPcallback(){};
            virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
            /* [out] */ UINT *pctinfo)
        {
            return E_NOTIMPL;
        }

        virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo **ppTInfo)
        {

             if (ppTInfo == NULL)
                return E_INVALIDARG;
            *ppTInfo = NULL;

            if(iTInfo != 0)
                return DISP_E_BADINDEX;

            AddRef();      // AddRef and return pointer to cached
                               // typeinfo for this object.
            *ppTInfo = NULL;

            return NOERROR;
        }

        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
        /* [in] */ REFIID riid,
        /* [size_is][in] */ LPOLESTR *rgszNames,
        /* [in] */ UINT cNames,
        /* [in] */ LCID lcid,
        /* [size_is][out] */ DISPID *rgDispId)
        {
            return E_NOTIMPL;
        }

        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
        /* [in] */ DISPID dispIdMember,
        /* [in] */ REFIID riid,
        /* [in] */ LCID lcid,
        /* [in] */ WORD wFlags,
        /* [out][in] */ DISPPARAMS *pDispParams,
        /* [out] */ VARIANT *pVarResult,
        /* [out] */ EXCEPINFO *pExcepInfo,
        /* [out] */ UINT *puArgErr)
        {
            return 0;
        }

        virtual HRESULT STDMETHODCALLTYPE QueryInterface(/* [in] */ REFIID riid,
                /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
        {
            if (riid == IID_IUnknown) 
            {
                *ppvObject = static_cast<IUnknown*>(this); 
                AddRef();
                return S_OK;
            }

            if (riid == IID_IDispatch) {
                *ppvObject = static_cast<IDispatch*>(this); 
                AddRef();
                return S_OK;
            }

            //if (riid == IID_CPPcallback ) 
            {
                *ppvObject = static_cast<CPPcallback*>(this);
                AddRef();
                return S_OK;
            }

            *ppvObject = NULL;
            return E_NOINTERFACE;
        }

        virtual ULONG STDMETHODCALLTYPE AddRef( void)
        {
            return InterlockedIncrement(&_refCount);
        }

        virtual ULONG STDMETHODCALLTYPE Release( void)
        {
            return  InterlockedDecrement(&_refCount);
        }

 private:
        long _refCount;
 };

int main(int argc, char *argv[])
{
    // Initialize COM.
    HRESULT hr = CoInitialize(NULL);

    // Create the interface pointer.
    ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass));

    long lResult = 0;

    // Call the Add method.
    CSharpDLLPtr->Add(5, 10, &lResult);
    long aa;
    aa = 3;
    CPPcallback cppcallback;


    CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &aa);
    wprintf(L"The result is %d\n", lResult);


    // Uninitialize COM.
    CoUninitialize();
    return 0;
}

C++ 调用 C# 函数有效。但是线 CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &aa);

会进入函数QueryInterface,GetTypeInfo然后直接回到C++主函数。那是 C# 中的那一行

  Console.Write("before call callbackFun\n");

无法到达。

如何在 C# 中注册 C++ 回调函数?

【问题讨论】:

    标签: c# c++ com-interop


    【解决方案1】:

    我不喜欢COM 方法(代码和注册过多),我建议您改用PInvokeManaged CLI

    但在您的情况下(假设您已经构建了大型 COM 基础架构)我可以建议使用很少的 PInvoke 特定解决方法。 想法是将pointer 作为IntPtrvoid* C++ 等效项)传递给您的CallBack 函数。然后在 C# 端将其转换回Delegate with

    Marshal.GetDelegateForFunctionPointer

    COM 不会破坏IntPtr。我检查了IntPtr 是如何转换为COM 接口的。事实证明,在x64 上,它变成了LongLong,而对于x86,它是Long。因此,该类型非常适合保存指针(顺便说一句,您可以将指针传递给 Callback 中的类)。这意味着AnyCPU 配置将毫无用处。

    这是代码示例 C# 重写部分:

    using System;
    using System.Runtime.InteropServices;
    
    namespace CSharpLibraryNameSpace
    {    
        // Any delegate decorated as for PInoke
        public delegate int NativeDelegateType([MarshalAs(UnmanagedType.LPWStr)] string strMsg);
    
        // Interface declaration.
        [ComVisible(true)]
        public interface ManagedInterface
        {
            int Add(int Number1, int Number2);
    
            int CalltheCallbackFun(IntPtr callbackFnPtr);
        };
    
    
        // Interface implementation.
        [ComVisible(true)]
        public class ManagedCSharpClass : ManagedInterface
        {
            public int Add(int Number1, int Number2)
            {
                Console.Write("Inside MANAGED Add Num1={0} Num2={1}\n", Number1, Number2);
                return Number1 + Number2;
            }
    
            public int CalltheCallbackFun(IntPtr callbackFnPtr)
            {
                Console.Write("Inside MANAGED CalltheCallbackFun Before Call ptr={0}\n", callbackFnPtr);
    
                //Convert IntPtr to Delegate
                NativeDelegateType callback =
                    Marshal.GetDelegateForFunctionPointer(callbackFnPtr,
                        typeof(NativeDelegateType)) as NativeDelegateType;
    
                int nRet = callback("Message from C# :)");
    
                Console.Write("Inside MANAGED CalltheCallbackFun After Call Result={0}\n", nRet);
    
                return nRet;
            }
        }
    }
    

    还有C++客户端部分:

    #import "CSharpLibrary.tlb" raw_interfaces_only
    using namespace CSharpLibrary;
    
    int SimpleCallbackFunction(const wchar_t* pszMsg)
    {
        wprintf(L"Inside C++ UNMANAGED Callback Param=\"%s\"\n", pszMsg);
        return 77;
    }
    
    int main()
    {
        wprintf(L"Inside C++ UNMANAGED Start\n");
    
        // Initialize COM.
        HRESULT hr = CoInitialize(NULL);
    
        // Create the interface pointer.
        ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass));
    
        long lResult = 0;
    
        // Call the Add method.
        CSharpDLLPtr->Add(5, 10, &lResult); //lResult == 15
    
        //! For x64 you need to convert to LongLong
        CSharpDLLPtr->CalltheCallbackFun((long)SimpleCallbackFunction, &lResult);
        wprintf(L"Inside C++ UNMANAGED Main result is %d\n", lResult);
    
        // Uninitialize COM.
        CoUninitialize();
        return 0;
    }
    

    输出是:

    Inside C++ UNMANAGED Start
    Inside MANAGED Add Num1=5 Num2=10
    Inside MANAGED CalltheCallbackFun Before Call ptr=2625906
    Inside C++ UNMANAGED Callback Param="Message from C# :)"
    Inside MANAGED CalltheCallbackFun After Call Result=77
    Inside C++ UNMANAGED Main result is 77
    

    【讨论】:

    • 谢谢,不过我还是要评论一下:把type:var替换成NativeDelegateType比较好,兼容性更好
    • @GaigerChen 我不明白替换var 类型?关于主题,我想补充一点,使用PInvoke 有很多优点:您不必编写包装器,您可以通过C++ ArraysLPCTSTRStructs 无需显式转换,它更快等...
    • 在我的环境:VS2005中,编译器无法传递var的使用,但NativeDelegateType可以。
    【解决方案2】:

    虽然我仍然建议使用PInvokeManaged CLI 或至少使用我之前的answer 方法,但我想添加COM 方法答案。

    COM wrappers 用于delegates 不是一个好主意。以下代码将 Callback 声明为来自 C#Interface,并获取实现此 interface 的对象。

    using System;
    using System.Runtime.InteropServices;
    
    namespace CSharpLibraryNameSpace
    {   
        //Callback Interface declaration
        [ComVisible(true)]
        public interface CallbackInterface1
        {
            int InvokeUnmanaged(string strMsg);
        }
    
        [ComVisible(true)]
        public interface ManagedInterface
        {
            int Add(int Number1, int Number2);
            int CalltheCallbackFun(CallbackInterface1 callback);
        };
    
    
        // Interface implementation.
        [ComVisible(true)]
        public class ManagedCSharpClass : ManagedInterface
        {
            public int Add(int Number1, int Number2)
            {
                Console.Write("Inside MANAGED Add Num1={0} Num2={1}\n", Number1, Number2);
                return Number1 + Number2;
            }
    
            public int CalltheCallbackFun(CallbackInterface1 callback)
            {
                Console.Write("Inside MANAGED CalltheCallbackFun Before Call\n");
    
                int nRet = callback.InvokeUnmanaged("Message from C# :)");
    
                Console.Write("Inside MANAGED CalltheCallbackFun After Call Result={0}\n", nRet);
    
                return nRet;
            }
        }
    }
    

    为了使用来自C++callback,您需要制作特殊对象。以下是整个程序代码:

    #import "CSharpLibrary.tlb" raw_interfaces_only
    using namespace CSharpLibrary;
    
    class CPPcallback :public CallbackInterface1
    {
    public:
        CPPcallback(){};
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
            /* [out] */ UINT *pctinfo)
        {
            *pctinfo = 1;
            return S_OK;
        }
    
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo **ppTInfo)
        {
            return E_NOTIMPL;
        }
    
        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID *rgDispId)
        {
            return E_NOTIMPL;
        }
    
        virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke( 
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS *pDispParams,
            /* [out] */ VARIANT *pVarResult,
            /* [out] */ EXCEPINFO *pExcepInfo,
            /* [out] */ UINT *puArgErr)
        {
            return E_NOTIMPL;
        }
    
        virtual HRESULT STDMETHODCALLTYPE QueryInterface(/* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
        {
            if (riid == IID_IUnknown) 
            {
                *ppvObject = static_cast<IUnknown*>(this); 
                AddRef();
                return S_OK;
            }
    
            if (riid == IID_IDispatch) {
                *ppvObject = static_cast<IDispatch*>(this); 
                AddRef();
                return S_OK;
            }
    
            if (riid == __uuidof(CallbackInterface1)) 
            {
                *ppvObject = static_cast<CallbackInterface1*>(this);
                AddRef();
                return S_OK;
            }
    
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
    
        virtual ULONG STDMETHODCALLTYPE AddRef( void)
        {
            return InterlockedIncrement(&_refCount);
        }
    
        virtual ULONG STDMETHODCALLTYPE Release( void)
        {
            return  InterlockedDecrement(&_refCount);
        }
    
        virtual HRESULT __stdcall InvokeUnmanaged (
            /*[in]*/ BSTR strMsg,
            /*[out,retval]*/ long * pRetVal ) override
        {
            wprintf(L"Inside C++ UNMANAGED Callback Param=\"%s\"\n", strMsg);
            *pRetVal = 77;
            return S_OK;
        }
    
    private:
        long _refCount;
    };
    
    int main()
    {
        wprintf(L"Inside C++ UNMANAGED Start\n");
    
        // Initialize COM.
        HRESULT hr = CoInitialize(NULL);
    
        // Create the interface pointer.
        ManagedInterfacePtr CSharpDLLPtr(__uuidof(ManagedCSharpClass));
    
        long nRes = 0;
    
        // Call the Add method.
        CSharpDLLPtr->Add(5, 10, &nRes);
    
        //Callback holder instance
        CPPcallback cppcallback;
    
        //Call COM Managed method which calls our Callback
        CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &nRes);
        wprintf(L"Inside C++ UNMANAGED Main result is %d\n", nRes);
    
        // Uninitialize COM.
        CoUninitialize();
        return 0;
    }
    

    程序输出为:

    Inside C++ UNMANAGED Start
    Inside MANAGED Add Num1=5 Num2=10
    Inside MANAGED CalltheCallbackFun Before Call
    Inside C++ UNMANAGED Callback Param="Message from C# :)"
    Inside MANAGED CalltheCallbackFun After Call Result=77
    Inside C++ UNMANAGED Main result is 77
    

    如果您将 IDispatch 隐藏在场景代码后面的实现代码将几乎与之前答案中的代码一样短,但是您将面对所有 COM 特定对象(例如 BSTRSAFEARRAY),它确实运行缓慢比PInvoke.

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-17
      • 2021-11-09
      相关资源
      最近更新 更多