How to wrap C library callbacks in C++/CLI

北慕城南 提交于 2019-12-04 16:39:11
ceztko

I will insist on type-safe principles: I know there's already Marshal.GetFunctionPointerForDelegate but that requires function pointer type cast in C++/CLI and hides how marshalling unmanaged->managed works (debugging is much harder and I don't like not understanding what's happening behing the scene). Just noticed the approach is similar to this but doesn't need a managed native class (less overhead). Please, tell me if you know how to further simplify it (mantaining type safety and marshaling control) and reduce overhead.

The following is the C++/CLI Wrapper.h header:

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

Follows C++/CLi Wrapper.cpp defines:

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

Can be used in this way (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;
});
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!