Как обернуть обратные вызовы библиотеки C в C++/CLI

Учитывая следующую библиотеку 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) .


person ceztko    schedule 15.03.2011    source источник
comment
Альтернативой является P/Invoke с DLLImport в С#? Я бы не стал использовать его для оболочек больших библиотек, потому что он небезопасен для типов и подвержен ошибкам. Я не пишу свое приложение на C++/CLI: это хорошо для оберток, больше ничего.   -  person ceztko    schedule 16.03.2011


Ответы (1)


Я буду настаивать на принципах безопасности типов: я знаю, что Marshal.GetFunctionPointerForDelegate уже существует, но для этого требуется приведение типа указателя функции в C++/CLI и скрывает, как работает сортировка unmanaged->managed (отладка намного сложнее, и мне не нравится непонимание того, что происходит из-за сцена). Только что заметил, что подход похож на этот, но не нужен управляемый собственный класс (меньше накладных расходов). Пожалуйста, скажите мне, знаете ли вы, как еще больше упростить его (поддержание безопасности типов и управление маршалингом) и уменьшить накладные расходы.

Ниже приведен заголовок 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();
    }
}

Можно использовать таким образом (С#):

// 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;
});
person ceztko    schedule 15.03.2011
comment
Сохранение gcroot<T>* в непрозрачном аргументе — правильный подход. Очень похоже на метод, используемый для упаковки обратных вызовов библиотеки C в ISO C++. - person Ben Voigt; 16.03.2011