使用C#中基于vtable的本机DLL接口

时间:2015-07-30 21:37:56

标签: c# com-interop

前段时间我用C ++编写了一个Windows DLL来封装我想在不同的C ++和Delphi编译器之间共享的一些功能。我总是喜欢COM允许我使用对象的方式,而不是在DLL中使用平面C API。因为对于这个DLL,我不需要COM的全部功能而且没有引用计数,我决定实现一个有点模仿COM的DLL接口。我有一个导出的C函数,它允许我创建一个对象并返回一个vtable接口指针。通过这个指针,我可以在C ++和Delphi中以编译器中立方式调用对象上的方法。当我完成使用接口指针时,我会调用它上的释放。

现在我想使用C#中的这个DLL,但我不知道如何导入该接口。此外,我有一个C#客户端必须实现的回调接口。以下是C ++接口头文件(稍微缩短了)。

struct CksReadParams
{
    ...
};


struct ICksAdapter
{
    virtual ~ICksAdapter();

    virtual int32_t __cdecl Release() = 0;
    virtual int32_t __cdecl ReadValues(CksReadParams* params) = 0;
};


struct ICksAdapterCallback
{
    virtual ~ICksAdapterCallback();

    virtual void __cdecl OnConnected(int32_t clientID) = 0;
    virtual void __cdecl OnData(int32_t clientID, const char* csvLine) = 0;
    virtual void __cdecl OnLog(int32_t clientID, const char* message) = 0;
    virtual void __cdecl OnError(int32_t clientID, int32_t code, const char* message) = 0;
};


extern "C"
{
    typedef int(__cdecl *CA_PCreateAdapter)(ICksAdapterCallback* callback, ICksAdapter** adapter);

    __declspec(dllexport) int32_t __cdecl CA_CreateAdapter(ICksAdapterCallback* callback, ICksAdapter** adapter);
}

你能给我一个关于如何在C#中使用它的草图,特别是如何实现回调接口,如何处理__cdecl调用约定以及如何处理回调中实际为UTF8编码的C ++字符串的char *参数?

1 个答案:

答案 0 :(得分:1)

我编写了以下代码,似乎可以满足我的需求。它主要受到这篇博文Implementing an unmanaged C++ interface callback的启发。该代码主要基于Marshal.GetDelegateForFunctionPointer和Marshal.GetFunctionPointerForDelegate构建,它们从.NET 4.5开始可用,在我的情况下是可接受的。总而言之,您需要知道C ++对象的vtable,并且需要在非托管内存中为回调接口伪造vtable。

如果值得付出努力,我会留给读者。在我的情况下,它至少允许我重用我的DLL。下次我可能会坚持使用平面C API或首先使用COM。

class CksAdapterCallback
{       
    private IntPtr instance;

    public CksAdapterCallback()
    {
        instance = Marshal.AllocHGlobal(IntPtr.Size * 6);

        IntPtr vtblPtr = IntPtr.Add(instance, IntPtr.Size);
        Marshal.WriteIntPtr(instance, vtblPtr);

        Marshal.WriteIntPtr(vtblPtr, IntPtr.Zero); //dummy entry for the destructor

        OnConnectedInternal = new OnConnectedDelegate(OnConnected);
        Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnConnectedInternal));

        OnDataInternal = new OnDataDelegate(OnData);
        Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, 2*IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnDataInternal));

        ...
    }


    ~CksAdapterCallback()
    {
        Marshal.FreeHGlobal(instance);
    }


    public IntPtr Instance
    {
        get { return instance; }
    }


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void OnConnectedDelegate(IntPtr instance, Int32 clientID);
    OnConnectedDelegate OnConnectedInternal;

    void OnConnected(IntPtr instance, Int32 clientID)
    {
        ...
    }


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate void OnDataDelegate(IntPtr instance, Int32 clientID, IntPtr data);
    OnDataDelegate OnDataInternal;

    void OnData(IntPtr instance, Int32 clientID, IntPtr data)
    {
        ...
    }

    ...
}


class CksAdapterProxy
{        
    private IntPtr instance;

    public CksAdapterProxy(IntPtr instance)
    {
        this.instance = instance;

        IntPtr vtblPtr = Marshal.ReadIntPtr(instance, 0);

        IntPtr funcPtr = Marshal.ReadIntPtr(vtblPtr, 1 * IntPtr.Size);            
        ReleaseInternal = Marshal.GetDelegateForFunctionPointer<ReleaseDelegate>(funcPtr);

        ...
    }


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    delegate Int32 ReleaseDelegate(IntPtr instance);
    ReleaseDelegate ReleaseInternal;

    ...


    public Int32 Release()
    {
        return ReleaseInternal(instance);
    }

    ...
}


public class CksAdapterTest
{   
    [DllImport("CKS\\CksAdapter.dll")]
    [return: MarshalAs(UnmanagedType.I4)]
    static extern int CA_CreateAdapter(IntPtr adapterCallback, out IntPtr adapter);

    CksAdapterProxy adapter = null;
    CksAdapterCallback adapterCallback = new CksAdapterCallback();


    public CksAdapterTest()
    {            
        IntPtr nativeAdapter = IntPtr.Zero;
        int result = CA_CreateAdapter(adapterCallback.Instance, out nativeAdapter);

        if(result == 0)
        {
            adapter = new CksAdapterProxy(nativeAdapter);

            ...

            adapter.Release();
        }
    }
}