【问题标题】:Managed to unmanaged callback with managed parameters?使用托管参数管理到非托管回调?
【发布时间】:2015-11-29 22:49:08
【问题描述】:

我在非托管 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)?

【问题讨论】:

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


    【解决方案1】:

    假设您想公开一个托管的 OutputLog 以供 .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
    

    【讨论】:

      【解决方案2】:

      没有办法“转换”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 持有对非托管的引用。

      【讨论】:

      • 您确定这些字符串会准确转换吗? (字符集/嵌入零)
      • @Deduplicator,我没有进一步研究。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-19
      • 1970-01-01
      • 2011-08-05
      相关资源
      最近更新 更多