当我用Google搜索时,我看到帖子说传递C#class
与使用PInvoke时将ref struct
传递给C API相同(这里有帖子C# PInvoke struct vs class access violation)。
然而,在运行示例时,我看到的行为与预期不同。其中ref struct
充当真正的指针而'class'不是
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");
}
}
现在使用struct
进入C#进行“动物”:
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();
}
}
}
使用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
但是当我尝试使用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();
}
}
输出结果为:
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
因此,当我使用类时,分配给Animal的内存不会被修改。问题是为什么不呢?
答案 0 :(得分:11)
[StructLayout(LayoutKind.Sequential, ...)]
这才是最重要的。您已将该属性应用于类声明。您还将它应用于 struct 声明,但实际上并不是必需的,C#编译器会自动为结构发出此声明。模数CharSet属性。
类的默认[StructLayout]属性是不是 LayoutKind.Sequential,它是LayoutKind.Auto。这是CLR利用的东西,它将重新组织类中的字段以提供最佳布局。您可以在this post中了解有关它的更多信息。
最大的区别在于它使一个类不闪烁。这是一百美元的单词意味着pinvoke marshaller不能只是将普通指针传递给托管对象,它必须将托管对象转换为具有所请求布局的非托管对象。它通过分配内存和复制字段来实现。结果是,本机代码对副本所做的任何更改都不会传播回原始托管对象。
除非你明确告诉pinvoke marshaller这样做。修正:
[DllImportAttribute(...)]
public static extern void ChangeName([In, Out]Animal pAnimal);
[OutAttribute]告诉它传播更改。