gpt4 book ai didi

c# - PInvoke 'class' 与 'ref struct'

转载 作者:太空狗 更新时间:2023-10-29 20:04:41 25 4
gpt4 key购买 nike

当我四处搜索时,我看到帖子说传递 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");
}
}

现在在 C# 上使用 struct 表示“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();
}
}
}

使用 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 的内存不会被修改。问题是为什么不呢?

最佳答案

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

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

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

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

除非您明确告诉 pinvoke 编码器执行此操作。修复:

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

[OutAttribute] 告诉它传播更改回来。

关于c# - PInvoke 'class' 与 'ref struct',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22823960/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com