Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PInvoke 'class' Versus 'ref struct'

Tags:

c#

pinvoke

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

However when running an example I am seeing a different behavior than expected. Where ref struct acts as a real pointer while 'class' does 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 onto 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 is as expected changing the the first letter to 'A' in C# realm:

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();
    }
}

The output is:

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

So when I use a class the memory allocated to Animal does not get modified. The question is why not?

like image 416
Nickolay Kondratyev Avatar asked Apr 02 '14 22:04

Nickolay Kondratyev


People also ask

Are structs faster than classes?

So based on the above theory we can say that Struct is faster than Class because: To store class, Apple first finds memory in Heap, then maintain the extra field for RETAIN count. Also, store reference of Heap into Stack. So when it comes to access part, it has to process stack and heap.

What is the difference between struct and class C#?

Difference between Structs and Classes: Struct are value types whereas Classes are reference types. Structs are stored on the stack whereas Classes are stored on the heap. Value types hold their value in memory where they are declared, but a reference type holds a reference to an object in memory.

Can we use struct instead of class?

Class instances each have an identity and are passed by reference, while structs are handled and mutated as values. Basically, if we want all of the changes that are made to a given object to be applied the same instance, then we should use a class — otherwise a struct will most likely be a more appropriate choice.

When would you use a struct?

Structs are particularly useful for small data structures that have value semantics. Complex numbers, points in a coordinate system, or key-value pairs in a dictionary are all good examples of structs.


1 Answers

  [StructLayout(LayoutKind.Sequential, ...)]

This is what matters. You had to apply that attribute to the class declaration. You also applied it to the struct declaration but that wasn't actually necessary, the C# compiler emits this automatically for structs. Modulo the CharSet property.

The default [StructLayout] attribute for classes is not LayoutKind.Sequential, it is LayoutKind.Auto. Which is something the CLR takes advantage of, it will reorganize the fields in a class to come up with the best possible layout. You can read more about it in this post.

The big difference is that it makes a class non-blittable. Which is a hundred dollar word that means that the pinvoke marshaller cannot just pass a plain pointer to the managed object, it has to convert the managed object to an unmanaged one that has the requested layout. It does so by allocating memory and copying the fields. With the consequence that any changes that the native code makes to the copy doesn't get propagated back to the original managed object.

Unless you explicitly tell the pinvoke marshaller to do this. Fix:

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

The [OutAttribute] tells it to propagate changes back.

like image 75
Hans Passant Avatar answered Oct 01 '22 22:10

Hans Passant