I have a static library (*.a for iOS) that contains some functions that I need to assign to a callback from C#. The code works fine without the callback, but when I add the delegate to the structure, it fails with the following error:
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)
Here is the native code (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;
}
}
This is the managed side 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;
}
}
}
...
}
From 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.
I think your code must be
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;
}
The problem here is that a delegate is not a blittable type (search for "delegate" on this page), so this error started to occur when you added a delegate to the struct.
The first part of the error:
The specified structure must be blittable or have
layout information.
is what matters here. The "layout information" part of the error can be ignored.
The best option is probably to pass an out parameter for the the delegate as a separate argument to AigooRegisterPhone
or to use an IntPtr
in the TELEPHONE
struct instead of the basic_callback
type. In the later case, you can call Marshal.GetDelegateForFunctionPointer
to get a C# delegate from the native function pointer.
Here is the working code based on the suggestion from @Josh Peterson. The C code is the same. Only changed the C# side.
[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;
}
}