So, there have been many variants of this question, and after looking at several I still can\'t figure it out.
This is the C code:
typedef struct
{
u
The problem is that the native function returns a non-blittable type as a return value.
http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx
P/Invoke cannot have non-blittable types as a return value.
You cannot p/Invoke that method. [EDIT It is actually possible, see JaredPar's answer]
Returning 132 bytes by value is a bad idea. If this native code is yours, I would fix it. You can fix it by allocating the 132 bytes and returning a pointer. Then add a FreeFrame method to release that memory. Now it can be p/Invoked.
Alternately, you could change it to accept a pointer to the Frame memory that it will fill in.
Another option to JaredPar's is to utilize the C# fixed size buffer feature. This does however require you to turn on the setting to allow unsafe code, but it avoids having 2 structs.
class Program
{
private const int SIZE = 128;
unsafe public struct Frame
{
public uint Identifier;
public fixed byte Name[SIZE];
}
[DllImport("PinvokeTest2.DLL", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern Frame GetFrame(int index);
static unsafe string GetNameFromFrame(Frame frame)
{
//Option 1: Use if the string in the buffer is always null terminated
//return Marshal.PtrToStringAnsi(new IntPtr(frame.Name));
//Option 2: Use if the string might not be null terminated for any reason,
//like if were 128 non-null characters, or the buffer has corrupt data.
return Marshal.PtrToStringAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0];
}
static void Main()
{
Frame a = GetFrame(0);
Console.WriteLine(GetNameFromFrame(a));
}
}
There are two problems with the PInvoke signature that you've chosen.
The first is easy to fix. You have a mistranslation of unsigned long
. In C an unsigned long
is typically only 4 bytes. You chose the C# type long
which is 8 bytes. Changing the C# code to use uint
will fix this.
The second is a bit harder. As Tergiver pointed out the CLR Marshaller only supports a struct in the return position if it's blittable. Blittable is a fancy way of saying that it has the exact same memory representation in native and managed code. The struct definition you've chosen isn't blittable because it has a nested array.
This can be worked around though if you remember that PInvoke is a very simple process. The CLR Marshaller really just needs you to answer 2 questions with the signature of your types and pinvoke methods
In this case the number of bytes is sizeof(unsigned long) + 128 == 132
. So all we need to do is build up a managed type that is blittable and has a size of 132 bytes. The easiest way to do this is to define a blob to handle the array portion
[StructLayout(LayoutKind.Sequential, Size = 128)]
struct Blob
{
// Intentionally left empty. It's just a blob
}
This is a struct with no members that will appear to the marshaller as having a size of 128 bytes (and as a bonus it's blittable!). Now we can easily define the Frame
structure as a combination of an uint
and this type
struct Frame
{
public int Identifier;
public Blob NameBlob;
...
}
Now we have a blittable type with a size the marshaller will see as 132 bytes. This means it will work just fine with the GetFrame
signature you've defined
The only part left is giving you access to the actual char[]
for the name. This is a bit tricky but can be solved with a bit of marshal magic.
public string GetName()
{
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(128);
Marshal.StructureToPtr(NameBlob, ptr, false);
return Marshal.PtrToStringAnsi(ptr, 128);
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptr);
}
}
}
Note: I can't comment on the calling convention portion because I'm unfamiliar with the GetFrame
API but that's something i would definitely check on.