如何从DLL设置委托/回调(C和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 :0 at FMOD_Listener.Initialize () [0x00000] in :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

您可以将此属性应用于类或结构。 公共语言运行库控制托管内存中类或结构的数据字段的物理布局。 但是,如果要将类型传递给非托管代码,可以使用StructLayoutAttribute属性来控制类型的非托管布局。 使用LayoutKind.Sequential属性强制成员按照它们出现的顺序按顺序排列。 对于,LayoutKind.Sequential控制托管内存中的布局和非托管内存中的布局。 对于非blittable类型,它在将类或结构封送到非托管代码时控制布局,但不控制托管内存中的布局。 使用LayoutKind.Explicit属性来控制每个数据成员的精确位置。 对于blittable和non-blittable类型,这都会影响托管和非托管布局 。 使用LayoutKind.Explicit要求您使用FieldOffsetAttribute属性来指示类型中每个字段的位置。

C#,Visual Basic和C ++编译器默认将Sequential布局值应用于结构。 对于类,必须显式应用LayoutKind.Sequential值。 Tlbimp.exe(类型库导入程序)也应用StructLayoutAttribute属性; 它在导入类型库时始终应用LayoutKind.Sequential值。

我认为你的代码必须是

 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类型(在此页面上搜索“委托”),因此当您向结构添加委托时,会发生此错误。

错误的第一部分:

指定的结构必须是blittable或具有布局信息。

这是重要的。 可以忽略错误的“布局信息”部分。

最好的选择可能是将委托的out参数作为AigooRegisterPhone的单独参数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; } }