C# / C++ 回调类(而非函数)互操作 - 如何实现?

3
抱歉,如果有重复的地方 - 我很难找到答案(我发现了一些关于使用函数回调的C++函数的问题,并且还有一些答案是在从C/C++调用时使用类作为回调,但是...
  • 我在使用C#。
  • 我正在调用一个C++函数
  • 我不能改变C++函数的签名。
  • 此外,我正在使用p/invoke的动态方法而不是静态绑定(我的示例如下);

我可以处理这样的情况:函数采用简单值或包含简单值的结构,并返回简单值,但在这种情况下,我有一个接受回调对象的C函数。

根据在线上的一些想法,我尝试制作具有相同签名的类,然后固定该类并将其传递。 但是,我收到了C#错误“对象是不可吹口哨的”(它没有任何变量!)。

头文件:

再次抱歉,如果我的示例中有任何错误,我已尝试删除所有不相关的代码并爆炸宏,但我希望您理解正在发生的事情的本质。

    struct someData_t
    {
    int length; /**< JSON data length */
    char* pData; /*< JSON data */
    };

    namespace FOO {
    class ICallback
    {
    public: virtual ~ICallback() {}

        virtual void Callback(const someData_t &response) = 0;
    };
    }

    extern "C" __declspec(dllexport) void process(const someData_t *inData, FOO::ICallback *listener);

我的C#文件:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace Scratchpad {
    class Program {
    static void Main(string[] args) {
        Console.Out.WriteLine("I'm in Managed C#...");

        IntPtr user32 = NativeMethods.LoadLibrary(@"somelongpath\my_c.dll");

        IntPtr pAddressOfFunctionToCall = NativeMethods.GetProcAddress(user32, "process");

        process proc = (process)Marshal.GetDelegateForFunctionPointer(pAddressOfFunctionToCall, typeof(process));

        String someJson = "{ \"type\":\"someTestJson\"}";
        byte[] rawdata = Encoding.UTF8.GetBytes(someJson);

        someData myData = new someData();
        int dataLength = rawdata.Length * Marshal.SizeOf(typeof(byte)); // I realise byte is size 1 but..

        myData.length = rawdata.Length;

        myData.pData = Marshal.AllocHGlobal(dataLength);
        Marshal.Copy(rawdata, 0, myData.pData, dataLength);

        Console.Out.WriteLine("Size of mydata: " + Marshal.SizeOf(myData));
        IntPtr unmanagedADdr = Marshal.AllocHGlobal(Marshal.SizeOf(myData));

        Marshal.StructureToPtr(myData, unmanagedADdr, true);

        // ################################################################
        // FIXME: This area still working
        Callbacker myCallback = new Callbacker();

        GCHandle gch = GCHandle.Alloc(myCallback, GCHandleType.Pinned);

        IntPtr mycallbackPtr = gch.AddrOfPinnedObject();

        // FIXME: close of working area.
        // ################################################################
        // CALL THE FUNCTION!
        proc(unmanagedADdr, mycallbackPtr);


        myData = (someData) Marshal.PtrToStructure(unmanagedADdr, typeof(someData));

        Marshal.FreeHGlobal(unmanagedADdr);
        Marshal.FreeHGlobal(myData.pData);
        gch.Free();
        unmanagedADdr = IntPtr.Zero;

        bool result = NativeMethods.FreeLibrary(user32);

        Console.Out.WriteLine("Fini!)");
    }

    private delegate void process(IntPtr data, IntPtr callback);

    [StructLayout(LayoutKind.Sequential)]
    private struct someData { 
        public int length; 
        public IntPtr pData; 
    }

    private class Callbacker {
        public void Callback(someData response) {
        Console.WriteLine("callback Worked!!!");
        }
    }

    }

    static class NativeMethods {
    [DllImport("kernel32.dll")]
    public static extern IntPtr LoadLibrary(string dllToLoad);

    [DllImport("kernel32.dll")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName);

    [DllImport("kernel32.dll")]
    public static extern bool FreeLibrary(IntPtr hModule);
    }
}

任何建议都受欢迎。

您无法在此处传递托管类的指针,因为您的托管类 Callbacker 没有实现 FOO::ICallback 接口。 - AccessViolation
1个回答

2
您可以为实现 ICallback 接口的托管 Callbacker 类创建一些非托管封装器,例如这样:
typedef void (*PointerToManagedFunctionToInvoke)(const someData_t&);

class UnmanagedDelegate : public FOO::ICallback {
    private:
        PointerToManagedFunctionToInvoke managedCallback;
    public:
        UnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback)
        : managedCallback(inManagedCallback) {}

        virtual void Callback(const someData_t &response)
        {
            managedCallback(response);
        }
};

// Export this to managed part
UnmanagedDelegate* CreateUnmanagedDelegate(PointerToManagedFunctionToInvoke inManagedCallback)
{
    return new UnmanagedDelegate(inManagedCallback);
}

在 C# 部分,您可以创建一个委托作为 PointerToManagedFunctionToInvoke 进行封送,将其传递给 CreateUnmanagedDelegate 接收未管理的实现 ICallback,并使用该实现传递给您的 process

请注意,managedCallback 应该保持在 C# 端分配,而 UnmanagedDelegate 类对象存在时,您应该删除它,当它不再使用时。

或者,您可以使用轻量级的 C++/CLI 来实现这个包装器。


非常感谢!我花了一些时间来处理并实现它 - 我所做的是创建了一个第三方库,直接将其绑定到C#项目,而不是通过pInvoke。当我编译您的代码时,它抱怨类末尾缺少分号(我不是世界上最好的C++程序员,但这样对吗!?)我现在怀疑,由于我在C中有这个包装层,我可以重新定义我的结构体以包括来自公共.h文件的内容...但它有效。非常感谢! - Jmons
1
是的,在类声明后应该加上分号 - 是我的错 - 没有编译代码检查语法错误。不用谢,但请非常小心内存管理 - 一个简单的错误可能会导致内存泄漏和错误。这不是通常的C#垃圾回收代码。 - AccessViolation

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接