PInvoke 'class' Versus 'ref struct'

When I googled around, I saw messages saying that passing a C # class same as passing a ref struct to the C API when using PInvoke (here is one of the C # PInvoke struct vs class access violation messages).

However, when I run the example, I see a different behavior than expected. Where the ref struct acts like a real pointer, while the "class" is not

C code:

 //PInvokeProvider.h #include "stdafx.h" typedef struct Animal_s { char Name[10000]; } Animal; extern "C" void __declspec(dllexport) ChangeName(Animal* pAnimal); //PInvokeProvider.cpp #include "stdafx.h" #include <stdio.h> #include "PInvokeProvider.h" extern "C" { void ChangeName(Animal* pAnimal) { printf("Entered C++\n"); printf("Recieved animal : %s\n", pAnimal->Name); printf("This function will change the first letter of animal to 'A'\n"); pAnimal->Name[0] = 'A'; printf("Animal changed to : %s\n", pAnimal->Name); printf("Leaving C++\n"); } } 

Now in C # using struct for `Animal ':

 namespace PInvokeConsumer { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct Animal { /// char[10000] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10000)] public string Name; public Animal(string name) { Name = name; } } public partial class NativeMethods { [DllImportAttribute("PInvokeProvider.dll", EntryPoint = "ChangeName", CallingConvention = CallingConvention.Cdecl)] public static extern void ChangeName(ref Animal pAnimal); } internal class Program { public static void Main(string[] args) { Animal animal = new Animal("Lion"); Console.WriteLine("Animal : {0}", animal.Name); Console.WriteLine("Leaving C#"); NativeMethods.ChangeName(ref animal); Console.WriteLine("Back to C#"); Console.WriteLine("Animal : {0}", animal.Name); Console.ReadKey(); } } } 

The output using ref struct as expected, will change the first letter to "A" in the C # area:

 Animal : Lion Leaving C# Entered C++ Recieved animal : Lion This function will change the first letter of animal to 'A' Animal changed to : Aion Leaving C++ Back to C# Animal : Aion 


However, when I try to use class instead of ref struct :

 namespace PInvokeConsumer { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public class Animal { /// char[10000] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10000)] public string Name; public Animal(string name) { Name = name; } } public partial class NativeMethods { [DllImportAttribute("PInvokeProvider.dll", EntryPoint = "ChangeName", CallingConvention = CallingConvention.Cdecl)] public static extern void ChangeName(Animal pAnimal); } public static void Main(string[] args) { Animal animal = new Animal("Lion"); Console.WriteLine("Animal : {0}", animal.Name); Console.WriteLine("Leaving C#"); NativeMethods.ChangeName(animal); Console.WriteLine("Back to C#"); Console.WriteLine("Animal : {0}", animal.Name); Console.ReadKey(); } } 

Output:

 Animal : Lion Leaving C# Entered C++ Recieved animal : Lion This function will change the first letter of animal to 'A' Animal changed to : Aion Leaving C++ Back to C# Animal : Lion 

Therefore, when I use the class, the memory allocated for Animal does not change. The question is why not?

+6
source share
1 answer
  [StructLayout(LayoutKind.Sequential, ...)] 

It is important. You must apply this attribute to the class declaration. You also applied it to the declaration of the structure, but it was not actually necessary, the C # compiler automatically generates this for the structures. Modulo property of CharSet.

By default, the [StructLayout] attribute for classes is not LayoutKind.Sequential, it is LayoutKind.Auto. This is what the CLR takes advantage of; it will reorganize the fields in the class to create the best layout. You can read about it in this post .

The big difference is that it makes the class non-blittable. Which is a hundred-dollar word, which means that the pinvoke marker cannot just pass a simple pointer to a managed object, it must convert the managed object to an unmanaged one that has the requested layout. He does this by allocating memory and copying fields. As a result, any changes that the native code makes to the copy do not propagate back to the original managed entity.

Unless you explicitly specify a pinvoke marker, do this. Fix:

  [DllImportAttribute(...)] public static extern void ChangeName([In, Out]Animal pAnimal); 

[OutAttribute] reports that it pushes changes back.

+11
source

All Articles