Marshal.SizeOf 和 sizeof 之间的区别,我只是不明白
Difference between Marshal.SizeOf and sizeof, I just don't get it
直到现在我还想当然地认为 Marshal.SizeOf 是计算非托管堆上可 blittable 结构的内存大小的正确方法(这似乎是 SO 和几乎所有其他地方的共识在网络上)。
但是在阅读了一些针对 Marshal.SizeOf 的注意事项之后(this article 在 "But there's a problem..." 之后)我试了一下,现在我完全糊涂了:
public struct TestStruct
{
public char x;
public char y;
}
class Program
{
public static unsafe void Main(string[] args)
{
TestStruct s;
s.x = (char)0xABCD;
s.y = (char)0x1234;
// this results in size 4 (two Unicode characters)
Console.WriteLine(sizeof(TestStruct));
TestStruct* ps = &s;
// shows how the struct is seen from the managed side... okay!
Console.WriteLine((int)s.x);
Console.WriteLine((int)s.y);
// shows the same as before (meaning that -> is based on
// the same memory layout as in the managed case?)... okay!
Console.WriteLine((int)ps->x);
Console.WriteLine((int)ps->y);
// let's try the same on the unmanaged heap
int marshalSize = Marshal.SizeOf(typeof(TestStruct));
// this results in size 2 (two single byte characters)
Console.WriteLine(marshalSize);
TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize);
// hmmm, put to 16 bit numbers into only 2 allocated
// bytes, this must surely fail...
ps2->x = (char)0xABCD;
ps2->y = (char)0x1234;
// huh??? same result as before, storing two 16bit values in
// only two bytes??? next will be a perpetuum mobile...
// at least I'd expect an access violation
Console.WriteLine((int)ps2->x);
Console.WriteLine((int)ps2->y);
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
这里出了什么问题?字段取消引用运算符 '->' 假定的内存布局是什么? “->”是否是解决非托管结构的正确运算符?或者 Marshal.SizeOf 是非托管结构的错误大小运算符?
我没有找到任何可以用我理解的语言解释这一点的内容。除了“...struct layout is undiscoverable...”和“...in most cases...”之类的废话。
不同之处在于:sizeof 运算符采用类型名称并告诉您需要为该 struct.This 的实例分配多少字节的托管内存不一定是栈内存;当结构是数组元素、class 的字段等时,它们会在堆外分配。相比之下,Marshal.SizeOf 采用类型对象或类型实例,并告诉您需要分配多少字节的非托管内存。由于各种原因,这些可能会有所不同。类型的名称为您提供了线索:Marshal.SizeOf 旨在将结构编组到非托管内存时使用。
两者的另一个区别是sizeof运算符只能取非托管类型的名称;也就是说,一个结构类型,其字段只有整数类型、布尔值、指针等。 (有关确切定义,请参阅规范。)相比之下,Marshal.SizeOf 可以采用任何 class 或结构类型。
What memory layout does the field dereferencing operator '->' assume?
无论 CLI 决定什么
Is '->' even the right operator for addressing unmanaged structs?
这是一个模棱两可的概念。通过 CLI 访问非托管内存中的结构:这些结构遵循 CLI 规则。还有一些结构仅仅是非托管代码(可能是 C/C++)访问同一内存的名义名字。这遵循该框架的规则。编组通常是指P/Invoke,但不一定适用于此。
Or is Marshal.SizeOf the wrong size operator for unmanaged structs?
我默认为 Unsafe.SizeOf<T>
,本质上是 sizeof(T)
- 对于 CLI/IL(包括填充规则等),它是完美的 well-defined,但不是在 C# 中不可能。
我认为您仍然没有回答的一个问题是在您的特定情况下发生了什么:
&ps2->x
0x02ca4370 <------
*&ps2->x: 0xabcd 'ꯍ'
&ps2->y
0x02ca4372 <-------
*&ps2->y: 0x1234 'ሴ'
您正在写入和读取(可能)未分配的内存。由于您所在的内存区域,未检测到它。
这将重现预期的行为(至少在我的系统上,YMMV):
TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize*10000);
// hmmm, put to 16 bit numbers into only 2 allocated
// bytes, this must surely fail...
for (int i = 0; i < 10000; i++)
{
ps2->x = (char)0xABCD;
ps2->y = (char)0x1234;
ps2++;
}
A char
默认封送到 ANSI 字节。这允许与大多数 C 库进行互操作,并且是 .NET 运行时操作的基础。
我认为正确的解决方案是将 TestStruct
更改为:
public struct TestStruct
{
[System.Runtime.InteropServices.MarshalAs(UnmanagedType.U2)]
public char x;
[System.Runtime.InteropServices.MarshalAs(UnmanagedType.U2)]
public char y;
}
UnmanagedType.U2
表示 unsigned 'integer' 2 个字节长,相当于 C 头文件中的 wchar_t
类型。
只要注意细节,就可以将 C 结构无缝移植到 .NET,并为与本机库的互操作打开许多大门。
直到现在我还想当然地认为 Marshal.SizeOf 是计算非托管堆上可 blittable 结构的内存大小的正确方法(这似乎是 SO 和几乎所有其他地方的共识在网络上)。
但是在阅读了一些针对 Marshal.SizeOf 的注意事项之后(this article 在 "But there's a problem..." 之后)我试了一下,现在我完全糊涂了:
public struct TestStruct
{
public char x;
public char y;
}
class Program
{
public static unsafe void Main(string[] args)
{
TestStruct s;
s.x = (char)0xABCD;
s.y = (char)0x1234;
// this results in size 4 (two Unicode characters)
Console.WriteLine(sizeof(TestStruct));
TestStruct* ps = &s;
// shows how the struct is seen from the managed side... okay!
Console.WriteLine((int)s.x);
Console.WriteLine((int)s.y);
// shows the same as before (meaning that -> is based on
// the same memory layout as in the managed case?)... okay!
Console.WriteLine((int)ps->x);
Console.WriteLine((int)ps->y);
// let's try the same on the unmanaged heap
int marshalSize = Marshal.SizeOf(typeof(TestStruct));
// this results in size 2 (two single byte characters)
Console.WriteLine(marshalSize);
TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize);
// hmmm, put to 16 bit numbers into only 2 allocated
// bytes, this must surely fail...
ps2->x = (char)0xABCD;
ps2->y = (char)0x1234;
// huh??? same result as before, storing two 16bit values in
// only two bytes??? next will be a perpetuum mobile...
// at least I'd expect an access violation
Console.WriteLine((int)ps2->x);
Console.WriteLine((int)ps2->y);
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
这里出了什么问题?字段取消引用运算符 '->' 假定的内存布局是什么? “->”是否是解决非托管结构的正确运算符?或者 Marshal.SizeOf 是非托管结构的错误大小运算符?
我没有找到任何可以用我理解的语言解释这一点的内容。除了“...struct layout is undiscoverable...”和“...in most cases...”之类的废话。
不同之处在于:sizeof 运算符采用类型名称并告诉您需要为该 struct.This 的实例分配多少字节的托管内存不一定是栈内存;当结构是数组元素、class 的字段等时,它们会在堆外分配。相比之下,Marshal.SizeOf 采用类型对象或类型实例,并告诉您需要分配多少字节的非托管内存。由于各种原因,这些可能会有所不同。类型的名称为您提供了线索:Marshal.SizeOf 旨在将结构编组到非托管内存时使用。
两者的另一个区别是sizeof运算符只能取非托管类型的名称;也就是说,一个结构类型,其字段只有整数类型、布尔值、指针等。 (有关确切定义,请参阅规范。)相比之下,Marshal.SizeOf 可以采用任何 class 或结构类型。
What memory layout does the field dereferencing operator '->' assume?
无论 CLI 决定什么
Is '->' even the right operator for addressing unmanaged structs?
这是一个模棱两可的概念。通过 CLI 访问非托管内存中的结构:这些结构遵循 CLI 规则。还有一些结构仅仅是非托管代码(可能是 C/C++)访问同一内存的名义名字。这遵循该框架的规则。编组通常是指P/Invoke,但不一定适用于此。
Or is Marshal.SizeOf the wrong size operator for unmanaged structs?
我默认为 Unsafe.SizeOf<T>
,本质上是 sizeof(T)
- 对于 CLI/IL(包括填充规则等),它是完美的 well-defined,但不是在 C# 中不可能。
我认为您仍然没有回答的一个问题是在您的特定情况下发生了什么:
&ps2->x
0x02ca4370 <------
*&ps2->x: 0xabcd 'ꯍ'
&ps2->y
0x02ca4372 <-------
*&ps2->y: 0x1234 'ሴ'
您正在写入和读取(可能)未分配的内存。由于您所在的内存区域,未检测到它。
这将重现预期的行为(至少在我的系统上,YMMV):
TestStruct* ps2 = (TestStruct*)Marshal.AllocHGlobal(marshalSize*10000);
// hmmm, put to 16 bit numbers into only 2 allocated
// bytes, this must surely fail...
for (int i = 0; i < 10000; i++)
{
ps2->x = (char)0xABCD;
ps2->y = (char)0x1234;
ps2++;
}
A char
默认封送到 ANSI 字节。这允许与大多数 C 库进行互操作,并且是 .NET 运行时操作的基础。
我认为正确的解决方案是将 TestStruct
更改为:
public struct TestStruct
{
[System.Runtime.InteropServices.MarshalAs(UnmanagedType.U2)]
public char x;
[System.Runtime.InteropServices.MarshalAs(UnmanagedType.U2)]
public char y;
}
UnmanagedType.U2
表示 unsigned 'integer' 2 个字节长,相当于 C 头文件中的 wchar_t
类型。
只要注意细节,就可以将 C 结构无缝移植到 .NET,并为与本机库的互操作打开许多大门。