Why is the "decimal" data type not lit?

GCHandle.Alloc refuses to bind an array to structures containing a "decimal" data type, but at the same time works fine with "double". What is the reason for this and can I somehow get around this?

I know that I can use unsafe / fixed instead to get a pointer to an array, but that won't work with generics.: --(

Complete code sample to demonstrate the problem. The first one works Alloc, but the second fails

The object contains non-primitive or non-reproducible data.

    public struct X1
    {
        public double X;
    }

    public struct X2
    {
        public decimal X;
    }

      

Now try this:

        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

      

+3


source to share


4 answers


  var handle2 = GCHandle.Alloc(x2, GCHandleType.Pinned);

      

The runtime makes a hard guess as to what you are going to call handle.AddrOfPinnedObject()

. For sure you have very little reason to place the pinning. This returns an unmanaged pointer, IntPtr

in C #. Differs from a managed pointer, the type you get with a keyword fixed

.

It also assumes that you are going to pass this pointer to code that takes care of the size and representation of the value. But otherwise, unable to inject conversion, this code builds directly on IntPtr. This requires the value type to be blittable, geeky word, which means that the bytes in the value can simply be interpreted or copied directly without any conversion and with decent coefficients, so that code that uses IntPtr will be able to recognize the value correctly.

The problem with some .NET types, for example, the bool type is notorious. Just try this same code with bool instead of decimal and notice that you get the same exception. System.Boolean is a very complex type of interaction, there is no dominant standard that describes how it should look. These are 4 bytes in C and Winapi, 2 bytes in COM Automation, 1 byte in C ++ and several other languages. In other words, the likelihood that "other code" would make sense to interpret the value of 1 byte of .NET is pretty slim. An unpredictable size is especially troublesome, which discards all subsequent members.



It's the same with System.Decimal, there is no widespread standard that overwhelms its internal format. Many languages ​​don't support it at all, especially C and C ++, and if you write code in such a language, you need to use a library. Which could have used IEEE 754-2008 decimal numbers, but while Johnny has been suffering from the "too many standards" problem lately. At the time the CLI specification was written, the IEEE 854-1987 standard was around, but it was widely ignored. Still a problem today, there are very few processor designs that support decimal places, I only know PowerPC.

In short, you need to create your own soft type to store decimal places. The .NET designers decided to use the COM Automation currency type to implement System.Decimal, the dominant implementation, then thanks to Visual Basic. This is very unlikely, too much code that depends on the internal format, making this code the most likely to be compatible and fast:

    public struct X2 {
        private long nativeDecimal;
        public decimal X {
            get { return decimal.FromOACurrency(nativeDecimal); }
            set { nativeDecimal = decimal.ToOACurrency(value); }
        }
    }

      

You might consider uint [] and Decimal.Get / SetBits () as well, but I think this is unlikely to be faster, you will have to try.

+12


source


If you like hacks ( and only if you like hacks ) (but note that this should work)

[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;
}

      

and then:



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);
}

      

At the same time, I use two fairly well-known hacks: using [StructLayout(LayoutKind.Explicit)]

, you can overlay two value types, such as C union and , you can even overlay two arrays of value types. The last problem is: Length

not "recalculated", so if you overlay byte[]

on long[]

, if you put an array of 8 bytes in there, both fields will show Length

8, but clearly if you try to access long[1]

, the program will crash. In this case, this is not a problem, because both structures are the same sizeof

.

Note that I used 4x uint

, but I could have used 2x ulong

or 16x byte

.

+1


source


Shiny types do not require conversion when passed between managed and unmanaged code. MSDN

This does not apply to decimal. How can another application or whoever is consuming your data understand the decimal structure? A workaround is to split decimal numbers into 2 integers, 1 for digits, 1 for decimal base, like 12.34 by 1234 and 2 (1234/10 ^ 2).

To convert decimal to binary correctly use GetBits, the opposite operations are a bit tricky, this page has an example.

0


source


This piece of code (refactoring from another SO question that I can't find right now) works fine with decimal[]

. Your problem is that the decimal value is blittable but not primitive, and the structure containing the non-primitive type is not pinnable (GCHandle.Alloc shows the error "Object contains non-primitive or non-reproducible data") ..

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

      

-1


source







All Articles