1、Array in stack

对于这样的struct:typedef struct { int XY[2]; } Point2D;

要在.NET为一个非托管函数传递这样一个结构体,原来得这样定义:

struct Point2D
{ [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)] public int[] XY; }

现在可以这么写(不过得用unsafe上下文):

unsafe struct Point2D { public fixed int XY[2]; }

不过这个功能还非常有限,不知道是出于什么原因考虑,只允许在strcut里面定义这样的数组,并且只能使用bool, byte, short, int, long, char, sbyte, ushort, uint, ulong, float和double这样的primitive类型。

也可以把数组作为局部变量分配在堆栈上,只是语法不太一样,那就是stackalloc关键字:int* fib = stackalloc int[100];,也要unsafe上下文。这可以提高不少效率。这是.NET 1.x就有的功能,只是似乎没人用这个...

PS. 对于只允许使用primitive类型,我认为是没道理的,最起码应该允许所有值类型的栈内数组。设计者们为啥这么考虑呢?怕堆栈溢出?据我测试.NET的堆栈空间也是1M左右,大部分情况下这么大的栈空间都被浪费了。

2、Function pointer as a return value

在.NET 1.x做P/Invoke时,对于那些回调函数,可以使用Delegate类型的参数作为函数指针传入。但有些非托管函数的返回值也是个函数指针,此时.NET 1.1变得无能为力,要调用这个函数,你得用native代码再写个包装,总之很麻烦。

.NET 2.0的System.Runtime.InteropServices.Marshal类为此需求新增了两个方法:

public static Delegate GetDelegateForFunctionPointer ( IntPtr ptr, Type t ); public static IntPtr GetFunctionPointerForDelegate ( Delegate d );

3、Marshal过程支持更多的类型

这是一个很细的问题,比如这样一个非托管struct:typedef struct { Point2D XYZ[3]; } Point2DX3;

对应到C#,你也许会这样写:

struct Point2D { [MarshalAs(UnmanagedType.ByValArray, SizeConst=2)] public int[] XY; } struct Point2DX3 { [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)] public Point2D[] XYZ; }

事实上这是不可行的。在.NET 1.x,结构体内嵌定长数组的类型必须是primitive类型,否则不能进行marshal过程。事实上调用Marshal.SizeOf时,会弹出异常说“无法得到大小”云云...

.NET 2.0把这个问题给改了(与其说是个增强,还不如说是修正了这个bug),Marshal.SizeOf(typeof(Point2DX3))现在可以正常运行,输出24 == 3 * 2 * sizeof(int)。

让Marshal.SizeOf正常工作非常重要,得不到对象的大小,内存对齐都无法保证,marshal过程根本就不可行。

此外,正常工作的Marshal.SizeOf可以使这种“序列化”方式也总能正常工作(这在我以前的blog贴过,这里是Generic版本):

unsafe class BinarySerializer { public static byte[] Struct2Bytes<T>(T obj) { int size = Marshal.SizeOf(obj); byte[] bytes = new byte[size]; fixed (byte* pb = &bytes[0]) { Marshal.StructureToPtr(obj, (IntPtr)pb, true); } return bytes; } public static T Bytes2Struct<T>(byte[] bytes) { fixed (byte* pb = &bytes[0]) { return (T)Marshal.PtrToStructure((IntPtr)pb, typeof(T)); } } }

和.NET内置的序列化机制相比,这个方案效率要高得多。和.NET内置的序列化机制相似,这里需要对象或结构体内部的所有成员都能使用Marshal.SizeOf得到其大小,对应于“对象或结构体内部的所有成员都带有[Serializable]特性或者实现了ISerializable接口”。

PS. 这个序列化方案效率虽高,但不如.NET内置的方案可靠。.NET的编译器可以检查一个类型能否支持序列化,而这个方案得由程序员判断Marshal.SizeOf能否工作(比如Marshal.SizeOf绝对不会知道string类型实际占用多少内存,也就是说类型实例的大小必须是固定的才行)。