Reputation: 300
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#?
Upvotes: 1
Views: 692
Reputation: 1357
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
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
Upvotes: 3
Reputation: 1357
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
.
Upvotes: 1