GCHandle.Alloc拒绝使用包含“十进制”数据类型的结构来固定数组,但同时使用“double”工作正常。这是什么原因,我能以某种方式解决它吗?
我知道我可以使用unsafe / fixed来获取指向数组的指针,但这不适用于泛型。 : - (
演示问题的完整示例代码。第一个Alloc工作,但第二个失败了
对象包含非原始数据或非blittable数据。
public struct X1
{
public double X;
}
public struct X2
{
public decimal X;
}
现在试试这个:
var x1 = new[] {new X1 {X = 42}};
var handle1 = GCHandle.Alloc(x1, GCHandleType.Pinned); // Works
var x2 = new[] { new X2 { X = 42 } };
var handle2 = GCHandle.Alloc(x2, GCHandleType.Pinned); // Fails
答案 0 :(得分:15)
var handle2 = GCHandle.Alloc(x2, GCHandleType.Pinned);
运行时很难假设您要调用handle.AddrOfPinnedObject()
。当然,你没有理由分配钉扎手柄。这将返回一个非托管指针,一个C#中的IntPtr
。与托管指针不同,使用fixed
关键字获得的类型。
它还假设您要将此指针传递给关注值大小和表示的代码。但是否则无法注入转换,该代码将直接在IntPtr上派对。这需要值类型为 blittable ,这是一个令人讨厌的词,这意味着可以简单地直接解释或复制值中的字节而无需任何转换,并且使用IntPtr的代码可以使用相当大的几率能够正确识别价值。
某些.NET类型存在问题, bool 类型因此而臭名昭着。只需使用bool而不是十进制尝试相同的代码,并注意您将获得完全相同的异常。 System.Boolean是一个非常困难的互操作类型,没有主导标准来描述它应该是什么样子。它是C语言和Winapi中的4个字节,COM自动化中的2个字节,C ++中的1个字节以及其他几种语言。换句话说,"其他代码"将解释1字节.NET值相当渺茫。不可预测的大小特别令人讨厌,这会抛弃所有后来的成员。
与System.Decimal大致相同,没有广泛采用的标准来确定其内部格式。许多语言根本不支持它,特别是C和C ++,如果用这种语言编写代码,那么你需要使用一个库。哪个可能使用IEEE 754-2008小数,但这是一个约翰尼 - 最近来的,并且受到太多标准的影响"问题。在编写CLI规范时,IEEE 854-1987标准已经出现,但它被广泛忽略。今天还是一个问题,很少有处理器设计支持小数,我只知道PowerPC。
长话短说,您需要创建自己的blittable类型来存储小数。 .NET设计者决定使用COM Automation Currency 类型来实现System.Decimal,这是当时的主流实现,这要归功于Visual Basic。这种情况极不可能发生变化,过多的代码依赖于内部格式,使得这些代码最容易兼容和快速:
public struct X2 {
private long nativeDecimal;
public decimal X {
get { return decimal.FromOACurrency(nativeDecimal); }
set { nativeDecimal = decimal.ToOACurrency(value); }
}
}
你也可以考虑uint []和Decimal.Get / SetBits(),但我认为它不太可能更快,你必须尝试。
答案 1 :(得分:2)
如果你喜欢黑客(,只有当你喜欢黑客)(但请注意应该工作)
[StructLayout(LayoutKind.Explicit)]
public struct DecimalSplitted
{
[FieldOffset(0)]
public uint UInt0;
[FieldOffset(4)]
public uint UInt1;
[FieldOffset(8)]
public uint UInt2;
[FieldOffset(12)]
public uint UInt3;
}
[StructLayout(LayoutKind.Explicit)]
public struct DecimalToUint
{
[FieldOffset(0)]
public DecimalSplitted Splitted;
[FieldOffset(0)]
public decimal Decimal;
}
[StructLayout(LayoutKind.Explicit)]
public struct StructConverter
{
[FieldOffset(0)]
public decimal[] Decimals;
[FieldOffset(0)]
public DecimalSplitted[] Splitted;
}
然后:
var decimals = new decimal[] { 1M, 2M, decimal.MaxValue, decimal.MinValue };
DecimalSplitted[] asUints = new StructConverter { Decimals = decimals }.Splitted;
// Works correctly
var h = GCHandle.Alloc(asUints, GCHandleType.Pinned);
// But here we don't need it :-)
h.Free();
for (int i = 0; i < asUints.Length; i++)
{
DecimalSplitted ds = new DecimalSplitted
{
UInt0 = asUints[i].UInt0,
UInt1 = asUints[i].UInt1,
UInt2 = asUints[i].UInt2,
UInt3 = asUints[i].UInt3,
};
Console.WriteLine(new DecimalToUint { Splitted = ds }.Decimal);
}
我同时使用两个相当着名的黑客:使用[StructLayout(LayoutKind.Explicit)]
你可以叠加两个值类型,比如C union 和,你甚至可以叠加两个值类型阵列。最后一个有问题:Length
不是&#34; t&#34;重新计算&#34;,所以如果你将byte[]
与long[]
重叠,如果你放在那里8个字节的数组,这两个字段将显示8的Length
,但显然如果您尝试访问long[1]
,程序将崩溃。在这种情况下,这不是一个问题,因为两个结构具有相同的sizeof
。
请注意,我使用了4x uint
,但我可以使用2x ulong
或16x byte
。
答案 2 :(得分:0)
Blittable类型在它们之间传递时不需要转换 托管和非托管代码。 MSDN
这不是Decimal的情况。其他应用程序或使用您的数据的人将如何理解十进制结构?解决方法是将十进制分解为2个整数,1表示数字,1表示十进制基数,例如12.34表示1234和2表示(1234/10 ^ 2)。
要将Decimal正确转换为二进制使用GetBits,相反的操作有点棘手,this page就是一个例子。
答案 3 :(得分:-1)
这段代码(从另一个SO问题重构,我现在找不到)与decimal[]
一起工作得很好。您的问题是十进制是blittable ,但不是原始的,包含非基本类型的结构不是可固定的(GCHandle.Alloc显示错误“对象包含非原始或不闪烁的数据。“)。
Why is decimal not a primitive type?
/// <summary>
/// Helper class for generic array pointers
/// </summary>
/// <typeparam name="T"></typeparam>
internal class GenericArrayPinner<T> : IDisposable
{
GCHandle _pinnedArray;
private T[] _arr;
public GenericArrayPinner(T[] arr)
{
_pinnedArray = GCHandle.Alloc(arr, GCHandleType.Pinned);
_arr = arr;
}
public static implicit operator IntPtr(GenericArrayPinner<T> ap)
{
return ap._pinnedArray.AddrOfPinnedObject();
}
/// <summary>
/// Get unmanaged poinetr to the nth element of generic array
/// </summary>
/// <param name="n"></param>
/// <returns></returns>
public IntPtr GetNthPointer(int n)
{
return Marshal.UnsafeAddrOfPinnedArrayElement(this._arr, n);
}
public void Dispose()
{
_pinnedArray.Free();
_arr = null;
}
}