0
votes

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)?

2

2 Answers

1
votes

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
0
votes

There's no way to "convert" the OutputLog^ (if you intend to use existing marshalling-functions). But there's (at minimum) one to convert a String^ to std::string:

#include <msclr\marshal_cppstd.h>
String^ manStr = "BLAH";
std::string stdStr = msclr::interop::marshal_as<std::string>(manStr);

As in other answers mentioned, it's not clear what you want to do. If you want to log error messages in managed code using the unmanaged logger, you can code something like this:

namespace unmanaged
{
    class OutputLog;
    typedef void(*ErrorCallback)(OutputLog& log, std::string& message);

    class OutputLog
    {
    public:
        ErrorCallback _callback;

        void Error(std::string& message)
        {
            if (_callback)
            {
                _callback(*this, message);
            }
        }
    };
}

namespace managed
{
    ref class OutputLog
    {
    private:
        unmanaged::OutputLog *m_log = nullptr;

    public:
        OutputLog() {}
        OutputLog(unmanaged::OutputLog *log)
            : m_log(log) {}

        void Error(String^ message)
        {
            // Do something managed stuff, then use the unmanaged logger.
            if (m_log != nullptr)
            {
                std::string stdStrMessage = msclr::interop::marshal_as<std::string>(message);
                m_log->Error(stdStrMessage);
            }
        }
    };
}

void PrintMsg(unmanaged::OutputLog& log, std::string& msg)
{
    cout << msg << endl;
}

int main(array<System::String ^> ^args)
{
    unmanaged::OutputLog *unmanOL = new unmanaged::OutputLog();
    unmanOL->_callback = PrintMsg;
    managed::OutputLog^ manOL = gcnew managed::OutputLog(unmanOL);

    manOL->Error("Hello");

    return 0;
}

The managed delegate was removed and managed::OutputLogger holds a reference to the unmanaged one.