【问题标题】:How to wrap C library callbacks in C++/CLI如何在 C++/CLI 中包装 C 库回调
【发布时间】:2011-03-15 22:27:32
【问题描述】:

鉴于以下 C 库带有一个要求设置缓冲区的回调事件,如何以类型安全的方式编写适当的 C++/CLI 包装器?

// The callback signature
typedef void (__cdecl *BUFFERALLOCATOR)(void *opaque, void **buffer);

// A struct that contains the context of the library
struct lib_context_base_s
{
    // The stored callback function pointer 
    BUFFERALLOCATOR buffer_allocator;
    // Opaque pointer that contain the local context. Needed in C because
    // C doesn't have closures (functions that knows the context where
    // they are defined)
    void* opaque;
};

typedef struct lib_context_base_s lib_context_base;

// Init the base context
lib_context_base* new_lib_context_base()
{
    return malloc(sizeof(lib_context_base));
}

// Free the base context
void free_lib_context_base(lib_context_base *lib_context_base)
{
    free(lib_context_base);
}

// Set the buffer allocation callback
void set_allocate_buffer_callback(lib_context_base *lib_context_base,
                                  BUFFERALLOCATOR allocate_buffer, void* opaque)
{
    lib_context_base->buffer_allocator = allocate_buffer;
    lib_context_base->opaque = opaque;
}

使用delegate void BufferAllocator(ref IntPtr buffer) 的托管代码应该可以使用该库。

【问题讨论】:

  • 替代方案是在 C# 中使用 DLLImport 进行 P/Invoke?我不会将它用于大型库包装器,因为它不是类型安全的并且容易出错。我不在 C++/CLI 中编写我的应用程序:这对包装器很有用,仅此而已。

标签: c++ c callback c++-cli wrapper


【解决方案1】:

我将坚持类型安全原则:我知道已经有 Marshal.GetFunctionPointerForDelegate 但这需要在 C++/CLI 中强制转换函数指针类型并隐藏编组 unmanaged->managed 的​​工作原理(调试要困难得多,我不喜欢不了解现场发生的事情)。刚刚注意到该方法类似于this,但不需要托管本机类(开销更少)。请告诉我您是否知道如何进一步简化它(保持类型安全和编组控制)并减少开销。

以下是 C++/CLI Wrapper.h 标头:

#include <gcroot.h>

using namespace System;
using namespace System::Runtime::InteropServices;

namespace LibraryWrapper
{
    // Declare the cdecl function that will be used 
    void cdecl_allocate_buffer(void *opaque, void **buffer);

    public ref class Library
    {
    public:
        // The BufferAllocator delegate declaration, available to any clr language
    // [In, Out] attributes needed (?) to pass the pointer as reference
        delegate void BufferAllocator([In, Out] IntPtr% buffer);

    internal:
        // The stored delegate ref to be used later
        BufferAllocator ^_allocate_buffer;

    private:
        // Native handle of the ref Library class, castable to void *
        gcroot<Library^> *_native_handle;
        // C library context
        lib_context_base *_lib_context_base;

    public:
        Library();
        ~Library();
        // The clr callback setter equivalent to the C counterpart, don't need
        // the context because in CLR we have closures
        void SetBufferAllocateCallback(BufferAllocator ^allocateBuffer);
    };
}

遵循 C++/CLi Wrapper.cpp 定义:

#include "wrapper.h"

namespace LibraryWrapper
{
    Library::Library()
    {
        // Construct the native handle
        _native_handle = new gcroot<Library^>();
        // Initialize the library base context
        _lib_context_base = new_lib_context_base();
        // Null the _allocate_buffer delegate instance
        _allocate_buffer = nullptr;
    }

    Library::~Library()
    {
        free_lib_context_base(_lib_context_base);
        delete _native_handle;
    }

    void Library::SetBufferAllocateCallback(BufferAllocator ^allocateBuffer)
    {
        _allocate_buffer = allocateBuffer;
        // Call the C lib callback setter. Use _native_handle pointer as the opaque data 
        set_allocate_buffer_callback(_lib_context_base, cdecl_allocate_buffer,
            _native_handle);
    }

    void cdecl_allocate_buffer(void *opaque, void **buffer)
    {
        // Cast the opaque pointer to the hnative_handle ref (for readability)
        gcroot<Library^> & native_handle = *((gcroot<Library^>*)opaque);
        // Prepare a IntPtr wrapper to the buffer pointer
        IntPtr buffer_cli(*buffer);
        // Call the _allocate_buffer delegate in the library wrapper ref
        native_handle->_allocate_buffer(buffer_cli);
        // Set the buffer pointer to the value obtained calling the delegate
        *buffer = buffer_cli.ToPointer();
    }
}

可以这样使用(C#):

// Allocate a ~10mb buffer in unmanaged memory. Will be deallocated
// automatically when buffer go out of scope
IntPtr _buffer = Marshal.AllocHGlobal(10000000);

// Init the library wrapper
Library library = new Library();

// Set the callback wrapper with an anonymous method
library.SetBufferAllocateCallback(delegate(ref IntPtr buffer)
{
    // Because we have closure, I can use the _buffer variable in the outer scope
    buffer = _buffer;
});

【讨论】:

  • 在不透明参数中存储gcroot&lt;T&gt;* 是正确的方法。与 ISO C++ 中用于包装 C 库回调的方法非常相似。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-29
  • 2014-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-04
  • 1970-01-01
相关资源
最近更新 更多