Search code examples
delegatesc++-climixed-mode

c++/cli: delegate to managed method taking native argument


I would like to have a way to make a call to a method of a reference class which takes a native argument. Creating a delegate seems the most obvious choice but it doesn't work with such methods.

Please have a look at following snippet

#pragma managed(push, off)
struct Native{};
#pragma managed(pop)

#pragma managed(push, on)
ref struct Managed{
    // methods of managed class can take...
    void method1(int){}              // ...value types as an argument
    void method2(System::String^){}  // ...reference types as an argument
    void method3(Native){}           // ...native types as an argument, NICE!
};

int main(array<System::String ^>^ /*args*/) {
    Managed^ m = gcnew Managed;

    auto del1 = gcnew System::Action<int>(m, &Managed::method1);             // ok
    auto del2 = gcnew System::Action<System::String^>(m, &Managed::method2); // ok
    auto del3 = gcnew System::Action<Native>(m, &Managed::method3); // error C3225: generic type argument for 'T' cannot be 'Native', it must be a value type or a handle to a reference type
}
#pragma managed(pop)

Solution

  • The error message says:

    error C3225: generic type argument for 'T' cannot be 'Native', it must be a value type or a handle to a reference type

    It's not saying you can't make a delegate out of that method, just that you can't represent it as an Action<Native>.

    You can either declare your own delegate type, or use a parameter that's valid to use in a generic.

    Use your own delegate type

    public delegate void ActionOfNative(Native n);
    
    auto del3 = gcnew ActionOfNative(m, &Managed::method3); // ok
    

    Use a parameter that's valid to use in a generic

    This does change the semantics of the parameter you're passing, but you could pass a pointer instead. The IntPtr type is effectively the same as a void*. You can use that as your parameter type. You do have to convert the IntPtr back to a Native* yourself, though.

    void method4(IntPtr ip){Native* np = (Native*)ip.ToPointer(); }
    
    auto del4 = gcnew System::Action<IntPtr>(m, &Managed::method4); // ok
    
    // Here's how you call del4.
    Native n;
    Native* np = &n;
    IntPtr ip = IntPtr(np);
    
    del4(IntPtr(&n));
    del4(IntPtr(np));
    del4(ip);