2

I would like to pass a function pointer (or similar) as a callback function to the constructor of a C# class, called from C++/CLI. The C# class is a sub-module; the C++ side is the main program. I'm getting errors reported by Visual Studio 2017, and I can't work out the correct syntax to use. (I'm a C++ programmer, but have close to zero experience with CLI and C#.) I find plenty of examples on how to set up callbacks the other way around, but from C# to C++/CLI I find little information.

Can somebody tell me what the correct syntax is, or show a different approach to achieve the same goal if this one is fundamentally flawed?

C# code (seems fine):

namespace MyNamespace
{
    public class MyCSharpClass
    {
        private Action<string> m_logger;

        public MyCSharpClass(Action<string> logger) => m_logger = logger;

        public void logSomething()
        {
            m_logger("Hello world!");
        }
    }
}

C++/CLI code (errors are in the second gcnew line with the System::Action):

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_MyCSharpClass
{

public:

    ILBridge_MyCSharpClass(ManagedDll_MyCSharpClass* pManagedDll_MyCSharpClass)
        : m_pManagedDll_MyCSharpClass(pManagedDll_MyCSharpClass)
    {
        m_pImpl = gcnew MyCSharpClass::MyCSharpClass(
            gcnew System::Action<System::String^>^(this, &ILBridge_MyCSharpClass::log)
        );
    }

    void log(System::String^ message) const
    {
        // ...
    }
}

The errors reported:

error C3698: 'System::Action<System::String ^> ^': cannot use this type as argument of 'gcnew'
note: did you mean 'System::Action<System::String ^>' (without the top-level '^')?
error C3364: 'System::Action<System::String ^>': invalid argument for delegate constructor; delegate target needs to be a pointer to a member function

If I remove the "^" as suggested, the C3698 error disappears but the C3364 error remains.

I'm following the design pattern suggested here, though not using code generation: http://blogs.microsoft.co.il/sasha/2008/02/16/net-to-c-bridge/

Captain Normal
  • 441
  • 4
  • 14
  • I'm not really familiar with CLI, but some other samples on the matter would have `public ref class ILBridge_MyCSharpClass` (note the `ref`). Also, can you please separate the creation of the `Action` from the `MyCSharpClass` constructor call, so we know which part exactly is faulty? – grek40 Aug 18 '17 at 07:30
  • Adding the ref solves the syntax problem, but brings with it a whole load of other problems and I think it subverts the point of the bridge. It makes me think there is something more fundamentally wrong - that this is simply not the right way to pass a callback to C# from CLI. I'll edit the question slightly - the gcnew I meant was the second one containing the System::Action. – Captain Normal Aug 18 '17 at 10:21

3 Answers3

1

Edit: essential solution

An Action in C++ CLI, can be created from a function (not a member function but free or static) or from the member function of a managed ref class.

In order to call a native member function from an Action, the native member call needs to be wrapped in a managed member function.

class NativeClassType;

ref class ManagedWrapper
{
    typedef void(NativeClassType::*MemberFunc)(System::String^);
    NativeClassType* nativeObject;
    MemberFunc memberFunction;

public:
    ManagedWrapper(NativeClassType* obj, MemberFunc wrappedFunction)
        : nativeObject(obj), memberFunction(wrappedFunction)
    {
        // Action that can be used in other managed classes to effectively invoke the member function from NativeClassType
        auto actionObject = gcnew System::Action<System::String^>(this, &ManagedWrapper::CallWrapped);
    }

    void CallWrapped(System::String^ msg)
    {
        // forward the call
        (nativeObject->*memberFunction)(msg);
    }
};

Original answer and full example

I played around a little and as far as I can tell, you will need to use native member function pointer handling at some point in order to callback to native member functions...

The following example code provides a managed (ref) class for static function callback and another one for member function callback. The native class NativeManaged is using both bridge classes to demonstrate different callbacks.

ref class ILBridge_Logger
{
private:
    System::Action<System::String^>^ loggerCallback;

public:

    ILBridge_Logger(void (*logFn)(System::String^))
    {
        loggerCallback = gcnew System::Action<System::String^>(logFn);
    }
    ILBridge_Logger(System::Action<System::String^>^ logFn)
    {
        loggerCallback = logFn;
    }



    void Test(System::String^ msgIn)
    {
        log(msgIn);
    }

    void log(System::String^ message)
    {
        loggerCallback(message);
    }
};


template<typename CallbackObject>
ref class ILBridge_MemberLogger : public ILBridge_Logger
{
    CallbackObject* o;
    void (CallbackObject::*logFn)(System::String^);
public:

    ILBridge_MemberLogger(CallbackObject* o, void (CallbackObject::*logFn)(System::String^))
        : ILBridge_Logger(gcnew System::Action<System::String^>(this, &ILBridge_MemberLogger::logMember)), o(o), logFn(logFn)
    {
    }

    // translate from native member function call to managed
    void logMember(System::String^ message)
    {
        (o->*logFn)(message);
    }
};


class NativeManaged
{
    gcroot<ILBridge_Logger^> Impl1;
    gcroot<ILBridge_Logger^> Impl2;
public:
    NativeManaged()
    {
        Impl1 = gcnew ILBridge_Logger(gcnew System::Action<System::String^>(log1));
        Impl2 = gcnew ILBridge_MemberLogger<NativeManaged>(this, &NativeManaged::log2);
    }

    void Test(System::String^ msgIn)
    {
        Impl1->Test(msgIn);
        Impl2->Test(msgIn);
    }

    // static logger callback
    static void log1(System::String^ message)
    {
        System::Console::WriteLine(L"Static Log: {0}", message);
    }

    // member logger callback
    void log2(System::String^ message)
    {
        System::Console::WriteLine(L"Member Log: {0}", message);
    }
};


int main(array<System::String ^> ^args)
{
    NativeManaged c;
    c.Test(L"Hello World");
    return 0;
}

Note: there might be more elegant ways of handling member function pointers with the C++11/14/17 features that I'm not aware of.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • Thanks! Using your answer as an example I found a solution. Conceptually your answer was spot-on, but it adds complexity and does not directly answer the OP. I posted below what I think is the best answer, but I'd like to give you the "accepted answer" points because I wouldn't have found it without your help; not sure what the policy is. Maybe you could steal what I wrote, prepend to your answer, leave your answer as extended information, I'll delete my answer and accept yours? – Captain Normal Aug 18 '17 at 16:01
  • @CaptainNormal I edited my answer so it contains the information at top that I identified as the "missing part" to solve your question. Accept it if you think it's a good answer, otherwise you can also accept your own. I know my initial answer did just throw some working code around without being very specific to the question code. – grek40 Aug 18 '17 at 17:12
  • That's great, thanks! Your edit contains the essential bit of information - and extra information is available in your extended answer and a little context my answer for anyone who needs it. – Captain Normal Aug 19 '17 at 07:22
0

You can't use c# delegates like function pointer. But you can make unsafe c++ cli method which call c# methods.

J.Nash
  • 1
0

For reference, here is the solution I ended up with. The C# code is the same as in the OP. A managed (ref) class was needed, as suggested by grek40 in his answer. In order to use the managed class by my managed DLL, the original IL Bridge class was still needed.

#pragma once
#pragma managed

#include <vcclr.h>

class ManagedDll_MyCSharpClass;
class ILBridge_MyCSharpClass;

ref class Managed_MyCSharpClass
{
    ILBridge_MyCSharpClass* m_pILBridge_MyCSharpClass;
    void (ILBridge_MyCSharpClass::*m_logger)(System::String^);
    MyCSharpClass::MyCSharpClass^ m_pImpl;

public:
    Managed_MyCSharpClass(ILBridge_MyCSharpClass* pILBridge_MyCSharpClass, void (ILBridge_MyCSharpClass::*logger)(System::String^))
        : m_pILBridge_MyCSharpClass(pILBridge_MyCSharpClass)
        , m_logger(logger)
    {
        m_pImpl = gcnew MyNamespace::MyCSharpClass(
            gcnew System::Action<System::String^>(this, &Managed_MyCSharpClass::log)
        );
    }

    void log(System::String^ message)
    {
        (m_pILBridge_MyCSharpClass->*m_logger)(message);
    }
};

class ILBridge_MyCSharpClass
{
public:

    ILBridge_MyCSharpClass(ManagedDll_MyCSharpClass* pManagedDll_MyCSharpClass)
        : m_pManagedDll_MyCSharpClass(pManagedDll_MyCSharpClass)
    {
        m_pManaged_MyCSharpClass = gcnew Managed_MyCSharpClass(this, &ILBridge_MyCSharpClass::log);
    }

    void log(System::String^ message)
    {
        // ...
    }

private:
    ManagedDll_MyCSharpClass* m_pManagedDll_MyCSharpClass;
    gcroot<Managed_MyCSharpClass^> m_pManaged_MyCSharpClass;
};
Captain Normal
  • 441
  • 4
  • 14