Search code examples
visual-c++callbackc++-clifunction-pointersmixed-mode

Managed to unmanaged callback with managed parameters?


My callback in unmanaged C++ is this:

typedef void (*ErrorCallback)(OutputLog& log, std::string& message);

It's usage (code is simplified):

class OutputLog
{
private:
    ErrorCallback _callback;

public:

    void Error(std::string& message)
    {
         // print message to console/stream here

         if (_callback)
         {
             _callback(*this, message);
         }
    }
};

In C++/CLI I created a wrapper class for my unmanaged OutputLog class. I defined the callback function as such:

public delegate void ErrorCallback(OutputLog^ log, String^ message);

So I know I can get the function pointer via Marshal::GetFunctionPointerForDelegate, but how do I convert the managed parameters (OutputLog^ log and String^ message) to their unmanaged counterparts (OutputLog& log and std::string& message)?


Solution

  • Assuming you want to expose a managed OutputLog for .NET clients to consume, and pass the wrapped, native OutputLog to a library while allowing .NET consumers to be notified of errors, you could use something along these lines.

    #include "stdafx.h"
    #include <string>
    
    #pragma region NATIVE
    typedef void (*ErrorCallback)(class OutputLog& log, const std::string& message, void* userData);
    
    class OutputLog
    {
    private:
        ErrorCallback m_callback;
        void* m_userData;
    
    public:
        OutputLog()
            : m_callback(nullptr), m_userData(nullptr) { }
    
        void SetCallback(ErrorCallback callback, void* userData) {
            m_callback = callback;
            m_userData = userData;
        }
    
        void Error(const std::string& message)
        {
             if (m_callback) {
                 m_callback(*this, message, m_userData);
             }
        }
    };
    #pragma endregion
    
    #pragma region MANAGED
    #include <msclr/gcroot.h>
    
    using namespace System;
    using namespace System::Runtime::CompilerServices;
    
    class NativeErrorCallbackHandler
    {
    public:
        NativeErrorCallbackHandler(ref class OutputLogManaged^ owner);
    private:
        static void OnError(class OutputLog& log, const std::string& message, void* userData);
        msclr::gcroot<OutputLogManaged^> m_owner;
    };
    
    public delegate void ErrorEventHandler(ref class OutputLogManaged^ log, String^ message);
    
    public ref class OutputLogManaged
    {
    public:
        OutputLogManaged()
            : m_nativeOutputLog(new OutputLog),
            m_nativeHandler(new NativeErrorCallbackHandler(this)) { }
    
        ~OutputLogManaged() { // = Dispose
            this->!OutputLogManaged();
        }
    
        !OutputLogManaged() // = Finalize
        {
            delete m_nativeOutputLog;
            m_nativeOutputLog = nullptr;
            delete m_nativeHandler;
            m_nativeHandler = nullptr;
        }
    
        event ErrorEventHandler^ Error
        {
            [MethodImplAttribute(MethodImplOptions::Synchronized)]
            void add(ErrorEventHandler^ value) {
                m_managedHandler = safe_cast<ErrorEventHandler^>(Delegate::Combine(value, m_managedHandler));
            }
    
            [MethodImplAttribute(MethodImplOptions::Synchronized)]
            void remove(ErrorEventHandler^ value) {
                m_managedHandler = safe_cast<ErrorEventHandler^>(Delegate::Remove(value, m_managedHandler));
            }
    
        private:
            void raise(OutputLogManaged^ log, String^ message) {
                auto managedHandler = m_managedHandler;
                if (managedHandler != nullptr)
                    managedHandler(this, message);
            }
        }
    
    internal:
        void RaiseErrorEvent(String^ message) {
            Error(this, message);
        }
    
        OutputLog* GetNative() { return m_nativeOutputLog; }
    
    private:
        OutputLog* m_nativeOutputLog;
        NativeErrorCallbackHandler* m_nativeHandler;
        ErrorEventHandler^ m_managedHandler;
    };
    
    NativeErrorCallbackHandler::NativeErrorCallbackHandler(OutputLogManaged^ owner)
        : m_owner(owner)
    {
        m_owner->GetNative()->SetCallback(&OnError, this);
    }
    
    void NativeErrorCallbackHandler::OnError(OutputLog& log, const std::string& message, void* userData)
    {
        static_cast<NativeErrorCallbackHandler*>(userData)->m_owner->RaiseErrorEvent(
            gcnew String(message.c_str(), 0, message.size()));
    }
    #pragma endregion
    
    #pragma region Test
    void Test(OutputLog& log)
    {
        log.Error("This is a test.");
    }
    
    void OnError(OutputLogManaged^ sender, String^ message)
    {
        Console::WriteLine(message);
    }
    
    int main(array<System::String ^> ^args)
    {
        OutputLogManaged managedLog;
        managedLog.Error += gcnew ErrorEventHandler(&OnError);
    
        Test(*managedLog.GetNative());
        return 0;
    }
    #pragma endregion