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
如何将此程序转换为适用于 VB 6.3 的 64 位计算机?
适用于 64 位 Windows 7 的廉价 Windows 驱动程序签名