Not sure if I have understood your question, but I think you are making a bad assumption cosindering that unsafe code is the same as unmanaged code. This is wrong. The keyword unsafe is required when you want to use pointer arithmetic in C#, but since C# is a managed language, it always works with managed data.
What I mean is that, whether you use pointers or not, any instance of a class which you use in C# will be allocated in the managed heap. Having this in mind, this is how interop works:
You have a pointer to an unmanaged object, and you want to be able to get this object in C#. Well, you will have to declare the classes in C# and use Marshal.PtrToStructure in order to get a managed version of that object which you can use in C#.
Now, you have a managed object in C# and want to pass it to unmanaged code. Well, you will have to call Marshal.StructureToPtr in order to make a copy of your managed object into an unmanaged block of memory.
On the other hand, I am not sure of this, but maybe these two classes will meet the requirements for the code snippet you have posted:
[StructLayout(LayoutKind.Sequential)]
class InnerStruct
{
[MarshalAs(UnmanagedType.I4)]
public int a;
public float b;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=16)]
public byte[] text;
}
[StructLayout(LayoutKind.Sequential)]
class OuterStruct
{
[MarshalAs(UnmanagedType.I4)]
public int flag;
[MarshalAs(UnmanagedType.I4)]
public int num;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=20)]
public InnerStruct[] data;
}
Remember you don't need to use pointers in C#. Whether you use them or not, C# only works with managed objects, so use them as any other managed object and then use Marshal class.