简体   繁体   中英

How to call a C++ delegated function from C#, when C++ call C# functions?

I would like to call a C# library from C++.

The C# library, request a delegated function, in order to report the results.

Maybe my purpose is confusing: the conception is, C++ call C# function, and the C# function would call a callback function from C++.

I have been blocked in the C# call C++ callback function , the COM Interop is arcane for me.

My example code be :

C# code :

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;
        }
    }
}

The C++ code :

#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;
}

The C++ calls C# function works. But the line CSharpDLLPtr->CalltheCallbackFun(&cppcallback, &aa);

would go into the function QueryInterface, GetTypeInfo then back to C++ main function directly. That is The line in C#

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

Would not be reached.

How should I do to register a C++ callback function in C#?

I don't like COM approach (too many code and registrations) and I would recommend you to use the PInvoke and Managed CLI instead.

But in your case (assuming you already have built big COM infrastructure) I can advise to use little PInvoke specific workaround. The Idea is to pass pointer to your CallBack function as IntPtr ( void* C++ equivalent). And then on C# side convert it back to Delegate with

Marshal.GetDelegateForFunctionPointer

COM will not spoil IntPtr . I've checked how IntPtr converted to COM interface. It turns out that on x64 it becomes LongLong and for x86 it is Long . So that type is Ideal to hold pointers (BTW you can pass pointer to your class inside Callback ). That means that AnyCPU configuration will be useless.

Here is code example C# rewriten part:

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;
        }
    }
}

And C++ client part:

#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;
}

The output is:

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

Although I still recommend to use PInvoke and Managed CLI or at least approach from my previous answer I want to add COM approach answer.

Using COM wrappers for delegates is not good idea. The following code declares Callback as Interface from C# and takes object which implements this 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;
        }
    }
}

In order to use this callback from C++ you need to make special object. Here is the whole program code:

#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;
}

The program output is:

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

If you would hide IDispatch implementation behind the scene code will be almost as short as code from previous answer BUT you will face all that COM specific objects (like BSTR , SAFEARRAY ) it definetly works slowly than PInvoke .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM