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
)?
假设你想公开一个托管的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
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 <msclrmarshal_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.
下一篇: 使用托管参数管理非托管回调?