为什么“十进制”数据类型不闪烁?

时间:2015-05-13 11:11:54

标签: c# .net memory-management

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

4 个答案:

答案 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;
    }
}