使用托管参数管理非托管回调?

时间:2015-09-03 23:16:23

标签: visual-c++ callback c++-cli function-pointers mixed-mode

我在非托管C ++中的回调是这样的:

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

它的用法(代码简化):

class OutputLog
{
private:
    ErrorCallback _callback;

public:

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

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

在C ++ / CLI中,我为非托管OutputLog类创建了一个包装类。我将回调函数定义为:

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

所以我知道我可以通过Marshal::GetFunctionPointerForDelegate获取函数指针,但是如何将托管参数(OutputLog^ logString^ message)转换为非托管参数(OutputLog& logstd::string& message)?

2 个答案:

答案 0 :(得分:1)

假设您要公开要使用的受管理的OutputLog for .NET客户端,并将包装的本机OutputLog传递给库,同时允许.NET使用者收到错误通知,您可以使用这些内容。

#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

答案 1 :(得分:0)

没有办法“转换”OutputLog^(如果你打算使用现有的编组函数)。但是(至少)将String^转换为std::string

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

正如其他提到的答案一样,目前尚不清楚你想做什么。如果要使用非托管记录器在托管代码中记录错误消息,可以使用以下代码进行编码:

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;
}

托管delegate已删除,managed::OutputLogger包含对非托管版本的引用。