C # appelant la fonction C qui retourne une structure avec un tableau de caractères de taille fixe

Donc, il y a eu beaucoup de variantes de cette question, et après avoir regardé plusieurs, je ne peux toujours pas comprendre.

C’est le code C:

typedef struct { unsigned long Identifier; char Name[128]; } Frame; Frame GetFrame(int index); 

C’est le code C #:

 struct Frame { public ulong Identifier; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.I1, SizeConst = 128)] public char[] Name; } [DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern Frame GetFrame(int index); 

C’est la dernière tentative que j’ai essayée en C #, et cela semble assez logique, mais j’obtiens l’erreur “La signature de la méthode n’est pas compatible avec PInvoke.” Donc, je suis un peu perdu sur quoi essayer ensuite. Toute aide est appréciée.

Merci Kevin

Kevin a ajouté ceci comme modification à ma réponse

Je devrais plutôt changer mon code C:

 void GetFrame(int index, Frame * f); 

et utilisez à la place pour C #:

 struct Frame { public uint Identifier; [MarshalAsAtsortingbute(UnmanagedType.ByValTStr, SizeConst = 128)] public ssortingng Name; } [DllImport("XNETDB.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] private static extern void GetFrame(int index, ref Frame f); 

Le problème est que la fonction native renvoie un type non blittable en tant que valeur de retour.

http://msdn.microsoft.com/en-us/library/ef4c3t39.aspx

P / Invoke ne peut pas avoir de types non compressibles comme valeur de retour.

Vous ne pouvez pas p / Invoke cette méthode. [ EDIT Il est effectivement possible, voir la réponse de JaredPar ]

Renvoyer 132 octets par valeur est une mauvaise idée. Si ce code natif est à vous, je le corrigerais. Vous pouvez résoudre ce problème en allouant les 132 octets et en renvoyant un pointeur. Ajoutez ensuite une méthode FreeFrame pour libérer cette mémoire. Maintenant, cela peut être p / Invoked.

Alternativement, vous pouvez le changer pour accepter un pointeur sur la mémoire Frame à remplir.

La signature PInvoke que vous avez choisie présente deux problèmes.

Le premier est facile à corriger. Vous avez une mauvaise traduction de unsigned long . En C, un unsigned long représente généralement que 4 octets. Vous avez choisi le type C # long qui est de 8 octets. Changer le code C # pour utiliser uint résoudra ce problème.

La seconde est un peu plus difficile. Comme l’a souligné Tergiver, le CLR Marshaller ne prend en charge une structure dans la position de retour que si elle est blittable. Blittable est une manière élégante de dire qu’il a exactement la même représentation de la mémoire dans le code natif et le code managé. La définition de structure que vous avez choisie n’est pas lisible, car elle contient un tableau nested.

Cela peut être contourné si vous vous souvenez que PInvoke est un processus très simple. Le Marshaller CLR a juste besoin que vous répondiez à 2 questions avec la signature de vos types et méthodes pinvoke

  • Combien d’octets est-ce que je copie?
  • Dans quelle direction doivent-ils aller?

Dans ce cas, le nombre d’octets est sizeof(unsigned long) + 128 == 132 . Il suffit donc de créer un type géré, d’une taille de 132 octets, qui est blittable. Le moyen le plus simple de le faire est de définir un blob pour gérer la partie tableau

 [StructLayout(LayoutKind.Sequential, Size = 128)] struct Blob { // Intentionally left empty. It's just a blob } 

Ceci est une structure sans membres qui apparaîtra au marshaller comme ayant une taille de 128 octets (et en bonus c’est blissable!). Maintenant, nous pouvons facilement définir la structure du Frame comme une combinaison d’un uint et de ce type

 struct Frame { public int Identifier; public Blob NameBlob; ... } 

Nous avons maintenant un type blittable avec une taille que le marshaller verra comme 132 octets. Cela signifie que cela fonctionnera parfaitement avec la signature GetFrame vous avez définie.

La seule partie qui rest vous donne access au caractère actuel char[] pour le nom. Ceci est un peu délicat mais peut être résolu avec un peu de magie de marshal.

 public ssortingng GetName() { IntPtr ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal(128); Marshal.StructureToPtr(NameBlob, ptr, false); return Marshal.PtrToSsortingngAnsi(ptr, 128); } finally { if (ptr != IntPtr.Zero) { Marshal.FreeHGlobal(ptr); } } } 

Remarque: je ne peux pas commenter la partie consacrée à la convention d’appel car je ne connais pas bien l’API GetFrame mais c’est une chose sur laquelle je voudrais absolument vérifier.

Une autre option de JaredPar consiste à utiliser la fonctionnalité de tampon de taille fixe C #. Cela nécessite toutefois que vous activiez le paramètre pour autoriser le code non sécurisé, mais cela évite d’avoir 2 structures.

 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 ssortingng GetNameFromFrame(Frame frame) { //Option 1: Use if the ssortingng in the buffer is always null terminated //return Marshal.PtrToSsortingngAnsi(new IntPtr(frame.Name)); //Option 2: Use if the ssortingng might not be null terminated for any reason, //like if were 128 non-null characters, or the buffer has corrupt data. return Marshal.PtrToSsortingngAnsi(new IntPtr(frame.Name), SIZE).Split('\0')[0]; } static void Main() { Frame a = GetFrame(0); Console.WriteLine(GetNameFromFrame(a)); } }