Click here to Skip to main content
16,015,531 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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:
C#
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):
C++
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.
C++
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.
C++
// This is the function which I intend to use as delegate
std::function<int(int)> 
my_delegate = [](int i) -> 
int {/*something useful*/ 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:
C++
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...
Posted
Updated 17-Aug-23 5:46am
v5

1 solution

had you try the next way:
C++
int MyCallback(int i) {}
public delegate int IntCallback(int i);
IntCallback^ callback = gcnew IntCallback(MyCallback);
// Use callback variable as an argument
 
Share this answer
 
Comments
hans.sch 22-Aug-23 8:51am    
@Maxim, thank you for your reply!

The line `IntCallback^ callback = gcnew IntCallback(MyCallback);` generates compiler error C3352: 'int (__cdecl *__cdecl cb)(int)': the specified function does not match the delegate type 'int (int)'. It only compiles when I declare 'int (__clrcall MyCallback)(int)' but when I do that, I have to propagate the __clrcall attribute into native code, and would have to compile the native code with /clr which may or may not work, but which I don't want to do for reasons beyond this discussion.
Maxim Kartavenkov 22-Aug-23 9:00am    
As I understand your code is already half managed as you are use the C++/CLI. Right, your callback should be managed, but from that managed part you can call unmanaged things.
Maxim Kartavenkov 22-Aug-23 9:08am    
You can look how I design managed/unmanaged callback linkage here: https://www.codeproject.com/Articles/5337264/NET-Wrapper-of-FFmpeg-Libraries#Delegates%20and%20callbacks
the sources are available in github

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900