草庐IT

c# - 为 C++ DLL 实现回调 C# 函数

coder 2024-02-13 原文

我正在为我的 C++ 库编写一个 DLL 包装器,以便从 C# 调用。此包装器还应具有从库中调用并在 C# 中实现的回调函数。这些函数有例如 std::vector 作为输出参数。我不知道该怎么做。如何通过回调函数将未知大小的缓冲区从 C# 传递到 C++?

举个例子

CallbackFunction FunctionImplementedInCSharp;

void FunctionCalledFromLib(const std::vector<unsigned char>& input, std::vector<unsigned char>& output)
{
    // Here FunctionImplementedInCSharp (C# delegate) should somehow be called
}

void RegisterFunction(CallbackFunction f)
{
    FunctionImplementedInCSharp = f;
}

CallbackFunction应该怎么定义,FunctionCalledFromLib里面的代码是什么?

让我感到困惑的一件事是:如何删除 C++ 代码中由 C# 创建的缓冲区?

最佳答案

至少从 Visual Studio 2013 开始,有一种安全的方法可以将回调从 C# 传递到 C++,并让 C++ 存储它们并稍后从非托管代码异步调用它们。您可以做的是创建一个托管的 C++/CX 类(例如,名为“CallbackManager”)以在映射中保存回调委托(delegate)引用,为每个引用指定一个枚举值。然后,您的非托管代码可以通过委托(delegate)的关联枚举值从托管 C++/CX CallbackManager 类检索托管委托(delegate)引用。这样您就不必存储原始函数指针,因此您不必担心委托(delegate)被移动或被垃圾收集:它在整个生命周期中都保留在托管堆中。

在 CallbacksManager.h 中的 C++ 端:

#include <unordered_map>
#include <mutex>

using namespace Platform;

namespace CPPCallbacks
{
    // define callback IDs; this is what unmanaged C++ code will pass to the managed CallbacksManager class to retrieve a delegate instance
    public enum class CXCallbackType
    {
        cbtLogMessage,
        cbtGetValueForSetting
        // TODO: add additional enum values as you add more callbacks
    }

    // defines the delegate signatures for our callbacks; these are visible to the C# side as well
    public delegate void LogMessageDelegate(int level, String^ message);
    public delegate bool GetValueForSettingDelegate(String^ settingName, String^* settingValueOut);
    // TODO: define additional callbacks here as you need them

     // Singleton WinRT class to manage C# callbacks; since this class is marked 'public' it is consumable from C# as well
    public ref class CXCallbacksManager sealed
    {
    private:
        CXCallbacksManager() { }  // this is private to prevent incorrect instantiation

    public:
        // public methods and properties are all consumable by C# as well
        virtual ~CXCallbacksManager() { }

        static property CXCallbacksManager^ Instance
        {
            CXCallbacksManager^ get();
        }

        bool UnregisterCallback(CXCallbackType cbType);
        void UnregisterAllCallbacks();
        Delegate^ GetCallback(CXCallbackType cbType);

        // define callback registration methods
        RegisterLogMessageCallback(LogMessageDelegate^ cb) { RegisterCallback(CXCallbackType::cbtLogMessage, cb); }
        RegisterGetValueForSettingCallback(GetValueForSettingDelegate^ cb) { RegisterCallback(CXCallbackType::GetValueForSetting, cb); }
        // TODO: define additional callback registration methods as you add more callbacks

    private:
        void RegisterCallback(CXCallbackType cbType, Delegate^ rCallbackFunc);

        typedef unordered_map<CXCallbackType, Delegate^> CALLBACK_MAP;
        typedef pair<CXCallbackType, Delegate^> CBType_Delegate_Pair;

        // Note: IntelliSense errors shown for static data is a Visual Studio IntellSense bug; the code below builds fine
        // See http://social.msdn.microsoft.com/Forums/windowsapps/en-US/b5d43215-459a-41d6-a85e-99e3c30a162e/about-static-member-of-ref-class?forum=winappswithnativecode
        static mutex s_singletonMutex;
        static CXCallbacksManager^ s_rInstance;

        mutex m_callbackMapMutex;
        CALLBACK_MAP m_callbacksMap;   // key=CallbackType, value = C# delegate (function) pointer
    };
}  

在 CallbacksManager.cpp 中,我们实现了由 C# 和我们的非托管 C++ 代码访问的托管 C++/CX 类:

#include <assert.h>
#include "CXCallbacksManager.h"

using namespace Platform;

namespace CPPCallbacks
{
    // define static class data
    CXCallbacksManager^ CXCallbacksManager::s_rInstance;
    mutex CXCallbacksManager::s_singletonMutex;

    // Returns our singleton instance; this method is thread-safe
    CXCallbacksManager^ CXCallbacksManager::Instance::get()
    {
        s_singletonMutex.lock();

        if (s_rInstance == nullptr)
            s_rInstance = ref new CXCallbacksManager();  // this lives until the application terminates

        s_singletonMutex.unlock();
        return s_rInstance;
    }

    // Register a C# callback; this method is thread-safe
    void CXCallbacksManager::RegisterCallback(const CXCallbackType cbType, Delegate^ rCallbackFunc)
    {
        _ASSERTE(rCallbackFunc);

        m_callbackMapMutex.lock();
        m_callbacksMap.insert(CBType_Delegate_Pair(cbType, rCallbackFunc)); 
        m_callbackMapMutex.unlock();
    }

    // Unregister a C# callback; this method is thread-safe
    // Returns: true on success, false if no callback was registered for callbackType
    bool CXCallbacksManager::UnregisterCallback(const CXCallbackType cbType)
    {
        m_callbackMapMutex.lock();
        const bool bRemoved = (m_callbacksMap.erase(cbType) > 0);
        m_callbackMapMutex.unlock();

        return bRemoved;
    }

    // Unregister all callbacks; this method is thread-safe
    void CXCallbacksManager::UnregisterAllCallbacks()
    {
        // must lock the map before iterating across it
        // Also, we can't change the contents of the map as we iterate across it, so we have to build a vector of all callback types in the map first.
        vector<CXCallbackType> allCallbacksList;
        m_callbackMapMutex.lock();

        for (CALLBACK_MAP::const_iterator it = m_callbacksMap.begin(); it != m_callbacksMap.end(); it++)
            allCallbacksList.push_back(it->first);

        for (unsigned int i = 0; i < allCallbacksList.size(); i++)
        {
            CALLBACK_MAP::const_iterator it = m_callbacksMap.find(allCallbacksList[i]);
            if (it != m_callbacksMap.end())     // sanity check; should always succeed
                UnregisterCallback(it->first);
        }
        m_callbackMapMutex.unlock();
    }

    // Retrieve a registered C# callback; returns NULL if no callback registered for type
    Delegate^ CXCallbacksManager::GetCallback(const CXCallbackType cbType)
    {
        Delegate^ rCallbackFunc = nullptr;
        m_callbackMapMutex.lock();

        CALLBACK_MAP::const_iterator it = m_callbacksMap.find(cbType);
        if (it != m_callbacksMap.end())
            rCallbackFunc = it->second;
        else
            _ASSERTE(false);    // should never happen! This means the caller either forgot to register a callback for this cbType or already unregistered the callback for this cbType.

        m_callbackMapMutex.unlock();
        return rCallbackFunc;
    }
}

委托(delegate)实例仍由我们的 CXCallbacksManager 类存储在托管堆中,因此现在可以轻松安全地在 C++ 端存储回调,以便非托管代码稍后异步调用。下面是C#端注册两个回调:

using CPPCallbacks;

namespace SomeAppName
{
    internal static class Callbacks
    {
        // invoked during app startup to register callbacks for unmanaged C++ code to invoke asynchronously
        internal static void RegisterCallbacks()
        {
            CPPCallbacks.CXCallbacksManager.Instance.RegisterLogMessageCallback(new LogMessageDelegate(LogMessageDelegateImpl));
            CPPCallbacks.CXCallbacksManager.Instance.RegisterGetValueForSettingCallback(new GetValueForSettingDelegate(GetValueForSettingDelegateImpl));
            // TODO: register additional callbacks as you add them
        }

        //-----------------------------------------------------------------
        // Callback delegate implementation methods are below; these are invoked by C++
        // Although these example implementations are in a static class, you could also pass delegate instances created 
        // from inside a non-static class, which would maintain their state just like any other instance method (i.e., they have a 'this' object).
        //-----------------------------------------------------------------

        private static void LogMessageDelegateImpl(int level, string message)
        {
            // This next line is shown for example purposes, but at this point you can do whatever you want because 
            // you are running in a normal C# delegate context.
            Logger.WriteLine(level, message);
        }

        private static bool GetValueForSettingDelegateImpl(String settingName, out String settingValueOut)
        {
            // This next line is shown for example purposes, but at this point you can do whatever you want because 
            // you are running in a normal C# delegate context.
            return Utils.RetrieveEncryptedSetting(settingName, out settingValueOut);   
        }
    };
}

最后,这里是如何从非托管 C++ 代码调用已注册的 C# 回调:

#include <assert.h>
#include <atlstr.h>   // for CStringW
#include "CXCallbacksManager.h"

using namespace CPPCallbacks;

// this is an unmanaged C++ function in the same project as our CXCallbacksManager class
void LogMessage(LogLevel level, const wchar_t *pMsg)
{
    _ASSERTE(msg);

    auto rCallback = static_cast<LogMessageDelegate^>(CXCallbacksManager::Instance->GetCallback(CXCallbackType::cbtLogMessage));
    _ASSERTE(rCallback);
    rCallback(level, ref new String(pMsg));   // invokes C# method
}

// this is an unmanaged C++ function in the same project as our CXCallbacksManager class
// Sets settingValue to the value retrieved from C# for pSettingName
// Returns: true if the value existed and was set, false otherwise
bool GetValueForSetting(const wchar_t *pSettingName, CStringW &settingValue)
{
    bool bRetCode = false;

    auto rCallback = static_cast<GetValueForSettingDelegate^>(CXCallbacksManager::Instance->GetCallback(CXCallbackType::cbtGetValueForSetting));
    _ASSERTE(rCallback);
    if (rCallback)    // sanity check; should never be null
    {
        String^ settingValueOut;
        bRetCode = rCallback(ref new String(pSettingName), &settingValueOut);

        // store the retrieved setting value to our unmanaged C++ CStringW output parameter
        settingValue = settingValueOut->Data(); 
    }
    return bRetCode;
}

这一切都有效,因为虽然您不能将托管委托(delegate)引用作为成员变量存储在非托管 类中,但您仍然可以从非托管代码中检索和调用托管委托(delegate),这就是上面两个 native C++ 方法可以。

关于c# - 为 C++ DLL 实现回调 C# 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3365639/

有关c# - 为 C++ DLL 实现回调 C# 函数的更多相关文章

  1. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  2. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  3. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  4. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  5. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  6. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  7. ruby - 有人可以帮助解释类创建的 post_initialize 回调吗 (Sandi Metz) - 2

    我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法

  8. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  9. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  10. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

随机推荐