如何从 DLL 设置 delegate/callback(C 和 C# 之间的互操作)

How to setup delegate/callback from DLL (interop between C and C#)

我有一个静态库(*.a for iOS),其中包含一些我需要分配给 C# 回调的函数。代码在没有回调的情况下工作正常,但是当我将委托添加到结构时,它失败并出现以下错误:

ArgumentException: The specified structure must be blittable or have
layout information. Parameter name: structure at
FMOD_Listener.LoadPlugins () [0x00000] in <filename unknown>:0  at
FMOD_Listener.Initialize () [0x00000] in <filename unknown>:0 
(Filename: currently not available on il2cpp Line: -1)

这里是本机代码(C):

extern "C" {
    typedef void (F_CALLBACK *basic_callback)  (int *value1);

    typedef struct telephone
    {
        int area_code;
        int number;
        basic_callback  basic_callbck;
    } TELEPHONE;

    F_DECLSPEC F_DLLEXPORT void F_STDCALL AigooRegisterPhone(TELEPHONE **telephone);

    void F_CALLBACK aigoo_basic_callback(int *value1)
    {
        *value1 = *value1 * 10 ;
    }

    F_DECLSPEC F_DLLEXPORT void F_STDCALL AigooRegisterPhone(TELEPHONE **telephone)
    {
        TELEPHONE* myPhone = new TELEPHONE ();
        myPhone->area_code = 929;
        myPhone->number = 823;
        myPhone->basic_callbck = aigoo_basic_callback;
        *telephone = myPhone;
    }
}

这是托管端 C#:

public delegate void basic_callback (ref int value1);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TELEPHONE
{
    public int area_code;
    public int number;
    public basic_callback               basic_callbck;
}

public class FMODPlugInHandler {

    [DllImport ("__Internal")]
    public static extern void AigooRegisterPhone(out IntPtr TelephonePtr);

}

public class FMOD_Listener : MonoBehaviour
{

...

    void LoadPlugins()
    {

        int plugin_result = 0;

        if (Application.platform == RuntimePlatform.IPhonePlayer) {

            IntPtr PhoneIntPtr;
            FMODPlugInHandler.AigooRegisterPhone(out PhoneIntPtr);
            plugin_result = 823823823;
            myLog = "plugin_result = " + plugin_result + " PhoneIntPtr: " + PhoneIntPtr;
            if (PhoneIntPtr != IntPtr.Zero){
                TELEPHONE MyPhone = (TELEPHONE)Marshal.PtrToStructure(PhoneIntPtr, typeof(TELEPHONE));
                plugin_result = 123456;
                myLog = "result = " + plugin_result + " number: " + MyPhone.number ;

                int int_cs = 2;
                plugin_result = MyPhone.basic_callbck(ref int_cs);
                myLog = "result = " + plugin_result + " number: " + MyPhone.number + " int_cs: " + int_cs;
            }
        }        
    }
...
}

来自 MSDN

You can apply this attribute to classes or structures. The common language runtime controls the physical layout of the data fields of a class or structure in managed memory. However, if you want to pass the type to unmanaged code, you can use the StructLayoutAttribute attribute to control the unmanaged layout of the type. Use the attribute with LayoutKind.Sequential to force the members to be laid out sequentially in the order they appear. For , LayoutKind.Sequential controls both the layout in managed memory and the layout in unmanaged memory. For non-blittable types, it controls the layout when the class or structure is marshaled to unmanaged code, but does not control the layout in managed memory. Use the attribute with LayoutKind.Explicit to control the precise position of each data member. This affects both managed and unmanaged layout, for both blittable and non-blittable types. Using LayoutKind.Explicit requires that you use the FieldOffsetAttribute attribute to indicate the position of each field within the type.

C#, Visual Basic, and C++ compilers apply the Sequential layout value to structures by default. For classes, you must apply the LayoutKind.Sequential value explicitly. The Tlbimp.exe (Type Library Importer) also applies the StructLayoutAttribute attribute; it always applies the LayoutKind.Sequential value when it imports a type library.

我想你的代码一定是

StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
public struct TELEPHONE
{
    [FieldOffset(0)]
    public int area_code;
    [FieldOffset(4)]
    public int number;
    [FieldOffset(8)]
    public basic_callback               basic_callbck;
}

这里的问题是委托不是 blittable 类型(在此 page 上搜索 "delegate"),因此当您将委托添加到结构时开始出现此错误。

第一部分错误:

The specified structure must be blittable or have layout information.

在这里很重要。错误的"layout information"部分可以忽略。

最好的选择可能是将委托的输出参数作为单独的参数传递给 AigooRegisterPhone 或在 TELEPHONE 结构中使用 IntPtr 而不是 basic_callback类型。在后一种情况下,您可以调用 Marshal.GetDelegateForFunctionPointer 从本机函数指针获取 C# 委托。

这是基于@Josh Peterson 建议的工作代码。 C代码是一样的。只改变了C#端。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TELEPHONE
{
    public int area_code;
    public int number;
    public **IntPtr** basic_callbck_intptr;
}

...


public class FMOD_Listener : MonoBehaviour
{

...

    void LoadPlugins()
    {

        int plugin_result = 0;

        if (Application.platform == RuntimePlatform.IPhonePlayer) {

            IntPtr PhoneIntPtr;
            FMODPlugInHandler.AigooRegisterPhone(out PhoneIntPtr);
            plugin_result = 823823823;
            myLog = "plugin_result = " + plugin_result + " PhoneIntPtr: " + PhoneIntPtr; 
            if (PhoneIntPtr != IntPtr.Zero){
                TELEPHONE MyPhone = (TELEPHONE)Marshal.PtrToStructure(PhoneIntPtr, typeof(TELEPHONE));
                plugin_result = 123456;
                myLog = "result = " + plugin_result + " number: " + MyPhone.number ; 

                int int_cs = 2; 
                IntPtr basic_callbck_intptr = MyPhone.basic_callbck_intptr;
                basic_callback basic_callbck = Marshal.GetDelegateForFunctionPointer(basic_callbck_intptr, typeof(basic_callback)) as basic_callback;

                basic_callbck(ref int_cs);
                myLog = "result = " + plugin_result + " number: " + MyPhone.number + " int_cs: " + int_cs; 

            }
        }