PInvoke 在返回结构时仅适用于 64 位

Posted

技术标签:

【中文标题】PInvoke 在返回结构时仅适用于 64 位【英文标题】:PInvoke only works in 64 bit, when returning a struct 【发布时间】:2019-06-24 07:09:14 【问题描述】:

我有以下 C++ 代码,可编译为 dll:

typedef struct _RGB 
    unsigned char R, G, B;
 RGB;

extern "C" __declspec(dllexport) RGB __stdcall TestMethod1() 
    RGB rgb1,2,3;
    return rgb;

并在 C# 中使用:

static void Main(string[] args)

    var res = TestMethod1();


[DllImport(@"D:\Develop\res\VSProjects\ConsoleApp1\Debug\Dll1.dll", CallingConvention = CallingConvention.StdCall)]
static extern RGB TestMethod1();

[StructLayout(LayoutKind.Sequential)]
struct RGB  public byte R, G, B; 

当以 x86 运行时,将 dll 构建为 x86 后,我收到错误 Attempted to read or write protected memory.。在 x64 中它工作正常。

当我使用托管/本机调试器时,我看到它在 return rgb; 上崩溃。

将返回类型更改为long(C# 中为int)时,即使在 x86 上也能正常工作。

RGB 结构 is blittable 那么为什么我会遇到这个问题?

【问题讨论】:

您应该添加Pack 参数。 [StructLayout(LayoutKind.Sequential, Pack = 1)] 到 StructLayout,因为你的结构是 3 个字节长。对于 c++ 你应该检查#pragma pack(push, 1) @JeroenvanLangen 不,这根本不会有任何区别,因为该结构包含 3 个字节并且所有可能的对齐方式都是相同的。实际上是错误的,因为 C++ 结构没有打包。 @DavidHeffernan 我不相信这是重复的,因为另一个问题是返回一个指针,这显然是行不通的。我正在返回结构本身,该问题中没有任何内容可以解释为什么它不起作用。 很好。我阅读了该问题中的 C# 代码,该代码假定结构是按值返回的。无论如何,你现在知道解决方案了。 如果你想了解它,你需要发现二进制接口的两侧是如何操作的。这可以通过例如在 CPU/asm 级别调试函数调用来完成。 【参考方案1】:

不要将结构用于“复杂”返回类型,更喜欢这样的:

C++:

extern "C" __declspec(dllexport) void __stdcall TestMethod2(RGB *prgb) 
    prgb->R = 1;
    prgb->G = 2;
    prgb->B = 3;

C#:

[DllImport(@"D:\smo\source\repos\ConsoleApplication4\Debug\Dll1.dll")]
static extern void TestMethod2(ref RGB rgb);

static void Main(string[] args)

    var rgb = new RGB();
    TestMethod2(ref rgb);

请注意,在您的特定情况下,它会失败,因为结构大小为 3,因此如果您像这样更改结构,则可以使其工作:

C++:

typedef struct _RGB 
    unsigned char R, G, B, A;
 RGB;

C#

[StructLayout(LayoutKind.Sequential)]
struct RGB  public byte R, G, B, A; 

使用此定义,大小将为 4,因此 C++ 编译器将生成一个返回 int32 值的代码,而不是返回 - 可能 - 对某些内部存储器的引用,该引用将在执行到达 .网络端。这纯粹是运气(或 hack),我猜这取决于 C++ 编译器。

【讨论】:

Microsoft writes 表示Formatted value types that contain only blittable types 可以返回。它没有说unless the size is 3。为什么这很重要? @wezten - 这不是 blittable 与 non-bilittable 的问题。这是“从非托管到托管的函数的返回值”问题。正如我所说,如果您通过引用传递结构,它工作正常。 Microsoft 的所有示例都遵循该模式。 在那篇文章中写着Structures that are returned from platform invoke calls must be blittable types. Platform invoke does not support non-blittable structures as return types.。然后它继续解释包含 blittable 类型的复杂类型本身是 blittable,这意味着它们可以被返回。 @wezten - 他们在这里谈论的“返回类型”不应与函数在二进制级别返回的内容相混淆。他们在谈论 ref 和 out 类型。同样,C stdcall 或 cdecl 函数在 32 位进程中返回的值是 32 位值,存储在 EAX 句点中(因此,当您传递结构时,编译器之外的结果是未定义的)。在使用同一个 C 编译器编译的两个函数之间返回一个结构可以正常工作,因为它知道自己的内部结构,但仅此而已。 谢谢你,这真的很清楚。将LayoutKind.Sequential 结构作为参数(按值)传递是否安全?

以上是关于PInvoke 在返回结构时仅适用于 64 位的主要内容,如果未能解决你的问题,请参考以下文章

将带有结构字段的结构从 c++ 返回到 c# pinvoke

使用 64 位分子和分母的 pi 的最佳有理近似值是多少?

如何将此程序转换为适用于 VB 6.3 的 64 位计算机?

适用于 64 位 Windows 7 的廉价 Windows 驱动程序签名

Qsettings 适用于 32 位 apk 但不在我的 64 位 apk 中创建文件

FileOpenDialog 生成的 CDN_SELCHANGE 通知消息适用于 32 位构建而不是 64 位构建