定义如下结构:
struct TestBool
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I4, SizeConst = 4)]
internal bool[] b;
[MarshalAs(UnmanagedType.Bool)]
public bool b2;
}
|
在运行时动态生成该结构的一个变量:
int type_marshal_size = Marshal.SizeOf( typeof(TestBool) );
IntPtr ptr = Marshal.AllocHGlobal(type_marshal_size);
ZeroMemory(ptr.ToInt32(), type_marshal_size);
int[] ia = new int[] { 1, 0, 1, 0 };
Marshal.Copy( ia, 0, ptr, ia.Length);
object obj = Marshal.PtrToStructure(ptr, typeof(TestBool) );
//Fix_net11_Marshal_PtrToStructure_bug( obj );
TestBool tb = (TestBool) obj;
Console.WriteLine( string.Format("Runtime type of bool[]: {0,15}", tb.b.GetType().FullName ) );
Console.WriteLine( string.Format("Runtime type of bool[]'s Element: {0,15}",
tb.b.GetType().GetElementType().FullName ) );
Console.WriteLine( string.Format("Runtime type of bool: {0,15}", tb.b2.GetType().FullName ) );
for(int i = 0; i < tb.b.Length; i++)
{
Console.WriteLine( string.Format("{0,-8}=> {1}", ia[i], tb.b[i] ) );
}
|
PtrToStructure 把一段unmanaged 的内存, 映射成.NET中一个boxed对象, 这个boxed的对象所包含的实际结构通过类型参数指定.
这里的问题在于: 这段unmanaged的内存, 我有意全部位清0(这通过kernel32.dll中的ZeroMemory实现)后显式设置为指定的值1, 0, 1, 0. 但是生成的对象在运行时其成员b的真实类型变成了sbyte[], 其每个元素的类型也是sbyte, 这与源代码中的声明类型bool[]和bool是不同的. 输出结果如下:
注意通过 GetType这些reflect方法得到的运行时类型变成了 System.SByte.
同时, 奇怪的是string.Format能生b的每个元素的正确ToString结果为True, 虽然其运行时类型不是True.
在一些依赖于成员运行时类型的代码中这个SByte类型会引起异常, 比如:
bool[] ba = new bool[]{True, False};
Array.Copy(ba, 0, tb.b, 0, ba.Length);
这个简单的操作, 把bool数组ba复制到声明类型为bool[]的TestBool成员b中, 看似天经地义, 实际上会引起一个运行时异常, 说两个数组类型不一致.
在运行时通过Quick Watch查看更会出现下面的奇怪结果:
注意整个数组的类型显示为 bool[], 但每个元素的类型却显示为 sbyte. 原因是.NET的集成调试环境显示数组的类型时并没有动态去得到其运行时真正的类型, 直接使用来自metadata的信息. 而数组的每个成员则是动态调用得到.
具体.NET内部如何"实现"这个bug当然我并不清楚.
.NET 2.0把这个bug解了. 下面是运行.NET2.0的C#编译器的结果(我把.NET 2.0的csc.exe改成csc2.exe了)
对于必需.NET 1.1的情况, 下面是一个辅助函数, 在PtrToStructure后立即调用该函数可以修正该bug:
///
/// .NET 1.1 has the following bug:
///
/// struct value_type
/// {
/// [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.I4, SizeConst = 4)]
/// public bool[] b;
/// }
///
/// int type_marshal_size = Marshal.SizeOf( typeof(TestBool) );
/// ZeroMemory(ptr.ToInt32(), type_marshal_size);
/// Marshal.PtrToStructure(ptr, typeof(value_type) )
///
/// Console.WriteLine( tb.b.GetType().FullName );
/// Console.WriteLine( tb.b.GetType().GetElementType().FullName );
///
/// The result is sbyte[], sbyte. !NOT! Boolean[] and Boolean
///
/// non-null
/// .NET 2.0 fixed the bug
public static void Fix_net11_Marshal_PtrToStructure_bug( object obj)
{
Debug.Assert(obj != null, "obj is null");
Type t = obj.GetType();
foreach(FieldInfo fi in t.GetFields(BindingFlags.Instance | BindingFlags.Public |
BindingFlags.NonPublic| BindingFlags.DeclaredOnly))
{
object field_obj = fi.GetValue(obj);
if( fi.FieldType.IsArray )
{
if (fi.FieldType.GetElementType() == typeof(bool))
{
if( fi.FieldType.GetArrayRank() != 1 )
{
Debug.Assert(false, "Not intent to tanckle multi-dimension bool array");
return;
}
bool[] real_bool_arr = new bool[ (field_obj as Array).GetLength(0) ];
for(int i = 0 ; i < real_bool_arr.Length; i++)
{
long val = Convert.ToInt64((field_obj as Array).GetValue(i));
real_bool_arr[i] = (val != 0);
}
fi.SetValue( obj, real_bool_arr);
Debug.Assert( ReferenceEquals(fi.GetValue(obj), real_bool_arr), "Fail to FieldInfo.SetValue" );
}
}
else if( fi.FieldType.IsPrimitive == false && fi.FieldType != typeof(string) &&
fi.FieldType != typeof(decimal) && fi.FieldType.IsEnum == false)
{
Fix_net11_Marshal_PtrToStructure_bug( field_obj );
fi.SetValue( obj, field_obj);
}
}
}
|
测试源文件:
|
文件: | PtrToStructure.zip |
大小: | 1KB |
下载: | 下载 |
|
阅读(1783) | 评论(0) | 转发(0) |