代码之家  ›  专栏  ›  技术社区  ›  Dialecticus

C++ DLL中的回调C语言功能实现

  •  4
  • Dialecticus  · 技术社区  · 15 年前

    我正在为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 以及函数calledFromLib中的代码是什么?

    其中一个让我哑口无言的是:如何删除C++代码中由C?

    4 回复  |  直到 13 年前
        1
  •  5
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    有些事情你应该注意。第一种情况是,如果您是从非托管代码调用.NET委托,那么除非您遵循一些非常狭窄的约束,否则您将很痛苦。

    理想情况下,您可以在C中创建一个委托#将其传递到托管代码中,将其封送到一个函数指针中,在任意时间内保持它,然后调用它而不产生任何不良影响。.NET文档是这么说的。

    因此,使用函数指针的最有效方法是:

    • C代码调用非托管代码,传入委托。
    • 非托管代码会做一些工作,可能会调用函数指针。
    • 非托管代码返回托管代码。

    考虑到这一点,假设我们在C#中有:

    public void PerformTrick(MyManagedDelegate delegate)
    {
        APIGlue.CallIntoUnamangedCode(delegate);
    }
    

    然后在托管C++中(不是) C++/CLI ):

    static CallIntoUnmanagedCode(MyManagedDelegate *delegate)
    {
        MyManagedDelegate __pin *pinnedDelegate = delegate;
        SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
        CallDeepIntoUnmanagedCode(p); // this will call p
    }
    

    我最近在C++和CLI中没有做过这件事——语法不同——我认为它看起来像这样:

    // This is declared in a class
    static CallIntoUnamangedCode(MyManagedDelegate ^delegate)
    {
        pin_ptr<MyManagedDelegate ^> pinnedDelegate = &delegate;
        SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
        CallDeepIntoUnmanagedCode(p); // This will call p
    }
    

    当你真的真的需要在调用前挂起函数指针一段时间后,我已经在C++中做了以下操作:

    1. 生成注册/注销例程,将新委托添加到哈希表中,为哈希int增加一个计数器。

    所发生的情况是,代理不再具有执行转换的thunk,因为它们是隐含的。他们可以自由地闲逛,因为他们被 GC CLR 根据需要释放。我还看到这个方法失败了,特别是在代码静态地在时间开始时注册回调并期望它们一直保持到时间结束的情况下。我在ASP.NET代码背后以及服务器端代码中看到过这种失败 Silverlight WCF

    举一个例子来说明什么时候会发生这种情况—假设您有一个包含如下函数的库:

    typedef void * (*f_AllocPtr) (size_t nBytes);
    typedef void *t_AllocCookie;
    
    extern void RegisterAllocFunction(f_AllocPtr allocPtr, t_AllocCookie cookie);
    

    我们的期望是,当您调用分配内存的API时,它将被矢量化到所提供的 f_AllocPtr . 信不信由你,你可以用C写这个。很甜蜜:

    public IntPtr ManagedAllocMemory(long nBytes)
    {
        byte[] data = new byte[nBytes];
        GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
        unsafe {
            fixed (byte *b = &data[0]) {
                dataPtr = new IntPtr(b);
                RegisterPointerHandleAndArray(dataPtr, dataHandle, data);
                return dataPtr;
            }
        }
    }
    

    登记员把三胞胎塞进保险箱。这样,当调用相应的free时,可以执行以下操作:

    public void ManagedFreeMemory(IntPtr dataPointer)
    {
        GCHandle dataHandle;
        byte[] data;
        if (TryUnregister(dataPointer, out dataHandle, out data)) {
            dataHandle.Free();
            // do anything with data?  I dunno...
        }
    }
    

    当然,这是愚蠢的,因为分配的内存现在被固定在GC堆中,并且会将其碎片化到地狱——但关键是这是可行的。

    但是,我个人也看到了这种失败,除非实际的指针是短暂的。这通常意味着包装您的API,这样当您调用一个例程来完成一个特定的任务时,它就会注册回调,执行任务,然后将回调拉出来。

        2
  •  6
  •   dbeachy1    12 年前

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

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

    这是因为尽管不能将托管委托引用作为成员变量存储在 类,您仍然可以从非托管代码检索并调用托管委托,这是上述两个本机C++方法所做的。

        3
  •  1
  •   Peter Mortensen Pieter Jan Bonestroo    13 年前

    事实证明,最初问题的答案相当简单,一旦你知道了,整个回调问题就没有问题了。输入缓冲区参数替换为参数对 unsigned char *input, int input_length ,并用参数对替换输出缓冲区参数 unsigned char **output, int *output_length

    public delegate int CallbackDelegate(byte[] input, int input_length,
                                         out byte[] output, out int output_length);
    

    C++中的包装器应该是这样的

    void FunctionCalledFromLib(const std::vector<unsigned char>& input, std::vector<unsigned char>& output)
    {
        unsigned char *output_aux;
        int output_length;
    
        FunctionImplementedInCSharp(
            &input[0], input.size(), &ouput_aux, &output_length);
    
        output.assign(output_aux, output_aux + output_length);
    
        CoTaskMemFree(output_aux); // IS THIS NECESSARY?
    }
    

    最后一行是小拼图的最后一部分。我必须打电话吗 CoTaskMemFree ,还是元帅会自动帮我?

    至于plinth的漂亮文章,我希望用一个静态函数来绕过整个问题。

        4
  •  0
  •   Community Mohan Dere    8 年前

    使用C++和CLI没有任何意义。

    这里是 a real world example 从我的项目。

    public ImageSurface(byte[] pngData)
        : base(ConstructImageSurfaceFromPngData(pngData), true)
    {
        offset = 0;
    }
    
    private static int offset;
    
    private static IntPtr ConstructImageSurfaceFromPngData(byte[] pngData)
    {
        NativeMethods.cairo_read_func_t func = delegate(IntPtr closure, IntPtr out_data, int length)
        {
            Marshal.Copy(pngData, offset, out_data, length);
            offset += length;
            return Status.Success;
        };
        return NativeMethods.cairo_image_surface_create_from_png_stream(func, IntPtr.Zero);
    }
    

    用于将PNG数据从C#传输到本机cairo API。

    您可以看到C函数指针 cairo_read_func_t cairo_image_surface_create_from_png_stream .

    Here 是一个类似的例子。