I'm playing with C++/CLI and calling into a .NET assembly. There is no specific use case, I simply want to learn.
I wrote the assembly in C#. It exports a class with a few methods, and a delegate type which is used in one of the methods:
namespace CsWorker
{
public class CsWorkerClass
{
public delegate int IntCallback(int i);
public int DoSomething(int i)
{
return i + 7;
}
public int DoSomething(int i, IntCallback cb)
{
return i + cb(i);
}
}
}
In the C++/CLI code, I load the assembly, then find the class, invoke its constructor, and store the object reference which the call returns (error checking removed):
auto assembly = System::Reflection::Assembly::LoadFrom(gcnew System::String(pszAssemblyPath));
auto type = assembly->GetType(gcnew System::String(pszType), true);
auto ctor = type->GetConstructor(System::Type::EmptyTypes);
msclr::auto_gcroot<System::Object^> m_pWorker = ctor->Invoke(System::Type::EmptyTypes);
To invoke the method, I retrieve the method with matching signature, in this case: '
int DoSomething(int)
', and invoke it.
auto otype = m_pWorker->GetType();
auto param_types = gcnew array<Type^>(1) { int::typeid };
auto method = otype->GetMethod(gcnew System::String(L"DoSomething"), param_types);
if (method->ReturnType != int::typeid)
throw std::runtime_error("Method has wrong return type");
auto args = gcnew array<System::Object^>(1) { int_value };
auto ret = method->Invoke(m_pWorker.get(), args);
This works fine.
Now I'd like to call the method '
int DoSomething(int, IntCallback)
'. Of course, the type
IntCallback
is not known to the C++ code because I don't import the C# assembly. I call '
otype->GetMethods();
' to retrieve all methods, then iterate over the returned array to find the one with matching arguments list. "Matching", here, means: first parameter type is '
int
' and second parameter type is derived from
System::Delegate
. This also works.
To invoke the method, I must construct the arguments list.
std::function<int(int)>
my_delegate = [](int i) ->
int { return i;};
auto args = gcnew array<System::Object^>(2)
{ int_value, my_delegate };
auto ret = method->Invoke(m_pWorker.get(), args);
Of course, this doesn't compile because I cannot assign a
std::function
(or a C style function pointer or a C++ pointer-to-method) to an
Object^
. The question is, how can I do it?
What I have tried:
I found the wonderful article
C++/CLI: Storing Lambda in a Delegate on CodeProject with magic code which works like a charm when you can name the delegate type at compile time, because this type goes into a template parameter. I tried to replace the delegate type
TDelegate
in the
CreateDelegate
function by a function argument of type
System::Type
, and the same in the function call chain down to
CreateDelegateHelper
.
All attempts failed with various error messages, some at compile time, some at run time. For example:
template< typename TLambda,
typename TReturn, typename... TArgs >
System::Object^ CreateDelegateHelper(System::Type^ type,
TLambda&& lambda, TReturn(__thiscall TLambda::*)
(TArgs...) const)
{
LambdaWrapper<TLambda>^ wrapper =
gcnew LambdaWrapper<TLambda>(std::forward<TLambda>(lambda));
auto ctor = type->GetConstructor(Type::EmptyTypes);
auto args = gcnew array<Object^>(2);
args[0] = wrapper;
args[1] = &LambdaWrapper<TLambda>
::Call<TReturn, TArgs...>;
return ctor->Invoke(args);
}
(Please refer to the mentioned article and its code for definition of '
ref class LambdaWrapper';
'lambda' is a lambda which wraps the callback).
This fails at the line which assigns the pointer-to-member-function
LambdaWrapper::Call
, with this compiler error:
error C3374: can't take address of 'DelegateUtility::LambdaWrapper<TLambda>::Call' unless creating delegate instance
That's what I'm doing, but the compiler doesn't recognize it...