PInvoke'class'Versus'ref struct'

Nic*_*yev 7 c# pinvoke

当我用Google搜索时,我看到帖子说传递C#classref struct使用PInvoke时传递给C API 相同(这里是一个帖子C#PInvoke struct vs class access violation).

但是,在运行示例时,我看到的行为与预期不同.在ref struct"类"中没有作为真正指针的地方

C代码:

//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");
    }
}
Run Code Online (Sandbox Code Playgroud)

现在进入C#使用struct"动物":

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

使用的输出ref struct正如预期的那样在C#域中将第一个字母改为'A':

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
Run Code Online (Sandbox Code Playgroud)


但是,当我尝试使用class而不是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();
    }
}
Run Code Online (Sandbox Code Playgroud)

输出是:

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
Run Code Online (Sandbox Code Playgroud)

因此,当我使用类时,分配给Animal的内存不会被修改.问题是为什么不呢?

Han*_*ant 13

  [StructLayout(LayoutKind.Sequential, ...)]
Run Code Online (Sandbox Code Playgroud)

这才是最重要的.您必须将该属性应用于类声明.您还将它应用于结构声明但实际上并不是必需的,C#编译器会自动为结构发出此声明.模数CharSet属性.

类的默认[StructLayout]属性不是 LayoutKind.Sequential,它是LayoutKind.Auto.这是CLR利用的东西,它将重新组织类中的字段以提供最佳布局.您可以在这篇文章中阅读更多相关信息.

最大的区别在于它使一个类不闪烁.这是一百美元的单词意味着pinvoke marshaller不能只是将普通指针传递给托管对象,它必须托管对象转换为具有所请求布局的非托管对象.它通过分配内存和复制字段来实现.结果是,本机代码对副本所做的任何更改都不会传播回原始托管对象.

除非你明确告诉pinvoke marshaller这样做.固定:

    [DllImportAttribute(...)]
    public static extern void ChangeName([In, Out]Animal pAnimal);
Run Code Online (Sandbox Code Playgroud)

[OutAttribute]告诉它传回更改.