这绝对是.NET 的错误。
tl;dr
.NET Framework 2 生成不正确(可能不安全)的存根。
我是怎么发现这个的?
我进行了一些测试:
- 我使用了 4 字节长的结构,而不是 8 字节长的结构。有用!
- 我没有使用 x86,而是使用了 x64。有用!
弄清楚它在所有其他情况下都有效,我决定使用 Windbg 对它进行本机调试,以查看它崩溃的位置(因为 Visual Studio 不允许我“介入”本机)call
与反汇编窗口)。
Guess what I found:
.NET 框架生成了一个正在调用的存根memcpy
,当它尝试复制到时失败edi
,当时的值为 0x16 (==22),这是 C# 代码中发送的参数!
那么,让我们看看如果我向该函数发送一个有效的指针会发生什么:
unsafe
{
long* ptr = &something;
uint ptr_value = (uint)ptr;
Console.WriteLine("Pointer address: {0:X}", (long)ptr);
var statusBlock = someFn(ptr_value);
Console.WriteLine("A: {0}", statusBlock.statusA);
Console.WriteLine("B: {0:X}", statusBlock.statusB);
}
输出:(当给出有效指针时,它可以工作并且不会崩溃!)
Marshal.SizeOf(typeof(StatusBlock)) = 8
Running .NET Version 2
Pointer address: 49F15C
A: 0
B: 0
因此,我得出结论,这是 .NET Framework 2 中无法挽救的问题。
为什么会发生这种情况?
当一个 C 函数被定义为返回一个struct
大于 8 个字节,该函数实际上应返回一个指向struct
在本地函数中stack
并且调用者应该使用memcpy
将其复制到自己的stack
(这是 C 规范的一部分,由编译器实现 - 程序员只需“返回”一个结构体,而编译器则完成繁重的工作)。
然而,对于 8 个字节(或者struct
or long long
),大多数 C 编译器将其返回eax:edx
。 .NET 的开发人员可能错过了这一点。错误可能是有人写的size >= 8
反而size > 8
...
编辑:更糟糕的是,它将结果写在给定的指针上!
before: 0x1111222244445555
after : 0x1234ABCD007BEF5C
它将指针更改为返回值!正如你所看到的,第一个dword
调用后是 0x1234ABCD (如在本机 DLL 中),第二个dword
是指向值的指针,即参数someVal
那是给定的!
更有趣的是,因为如果你将一个指针传递给StatusBlock
struct - 它实际上适用于这种特定情况(因为第一个dword
返回值中用作指针)
Solution
返回一个long
变量并自己创建结构。