none
Pinned array of struct with char RRS feed

  • Question

  • Hello there,


    The runtime seems to be treating Char with much more "respect" as Int16 (although a close friend :-). I stumble on that fact in a generic code that was pinning array of <T> where T : struct.

     

    I was really expecting to be able to deal with pure-struct (not including ref type) in a generic way, but yet again I was wrong. An array of simple struct with two char is not pinnable. (Althouh two Int16 will be ok for GCHandle.Alloc(.. Pinned))

     

    In the quest of understanding what is a Primitive or Blittable or Pinnable or fixe’able,   I try all values type with reflection. I even test the “blittablility” of those array with Buffer.BlockCopy() which seems to be even more restrictive that GCHandle and UnsafeAddrOfPinnedArrayElement.

     

    So I can pin array of char (hint) or int16,

    So I can pin array of struct { int16 }

    So I can NOT pin array of struct { char   }, but why ? (especially considering that (hint) is OK)

     

    (trying various StructLayout did not help me to understand)

     

     

     

    Thanks in advance

    • Edited by Boing3000 Wednesday, November 18, 2009 11:23 AM
    Thursday, November 5, 2009 7:47 AM

Answers

  • Use [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] on your struct Struct_2Chars. That will make it work.

    To answer some of your related questions:
    - Blittable types are types which have the same representation in managed and unmanaged world and don't require interop marshaler. Note that System.Char is non-blittable type.
    - Pinning is mechanism to make managed object 'freeze' and not being moved by GC (used for marshaling).
    - fixed is C# language keyword for pinning (it is not .NET term/definition).
    - Primitive types ... I couldn't find really good definition anywhere, but it looks like it is language specific, it is not .NET term/definition. In the scope of GCHandle.Alloc it is defined as non-primitive type = non-blittable type.

    System.Char is non-blittable type and it is marshaled by default from Unicode (in managed world) to ANSI (in native world). Unless you explictly set the char's marshaling to Unicode, it will have to change encoding which might lead to information loss as not all Unicode chars are encodable in ANSI.

    GCHandle.Alloc allows to pin an object, but only blittable objects - see ArgumentException condition. This API could in theory avoid its limitation to blittable types, but the designers of this API decided otherwise (maybe because it would be too tempting for developers to pin more often? ... that's just my guess).

    Hope this helps,
    -Karel
    • Marked as answer by Boing3000 Thursday, November 26, 2009 7:55 AM
    Wednesday, November 25, 2009 11:56 PM
    Moderator

All replies

  • Hi,
    As the exception says:
        "Object contains non-primitive or non-blittable data. "
    char is non-primitive type, it cannot be marshaled to unmanaged code directly, you may consider using byte (for unmanaged type TCHAR , WCHAR ) or ushort (for unmanaged type CHAR ) instead.

    Thanks,
    Eric
    Please remember to mark helpful replies as answers and unmark them if they provide no help.
    Friday, November 13, 2009 9:41 AM
  • Hello,

     

    Yes, true enough, but why then let me pinned a simple array of  System.Char ? And if I do agree that an enum type is not quite primitive (in the sense that no even the number/role of the bit used is guarantee), or even the bool type is fuzzy (sizeof() is 4 or 1 ?), but the System.Char ???

    Pinning some array containing   struct { IntPtr _; } is allowed. So even IntPtr is more blittable or primitive then Char (when embeded in struct (xith or without fixed buffer)), and that is strange for me. I always though that a UTF16 was a kind of universal representation (free of those code page “context”) and as such, perfectly safe to move around in memory (managed or not).

     

     

     

    Friday, November 13, 2009 10:41 AM
  • Hello there,


    The runtime seems to be treating Char with much more "respect" as Int16 (although a close friend :-). I stumble on that fact in a generic code that was pinning array of <T> where T : struct.

     

    I was really expecting to be able to deal with pure-struct (not including ref type) in a generic way, but yet again I was wrong. An array of simple struct with two char is not pinnable. (Althouh two Int16 will be ok for GCHandle.Alloc(.. Pinned))

     

    In the quest of understanding what is a Primitive or Blittable or Pinnable or fixe’able,   I try all values type with reflection. I even test the “blittablility” of those array with Buffer.BlockCopy() which seems to be even more restrictive that GCHandle and UnsafeAddrOfPinnedArrayElement.

     

    So I can pin array of char ** or int16,

    So I can pin array of struct { int16 }

    So I can NOT pin array of struct { char   }, but why ? (especially considering that ** is OK)

     

    (trying various StructLayout did not help me to understand)

     

     

     

    Thanks in advance


    Don't worry about this.

    You can create a property that looks like a Char yet internally manipulates an Int16 field, struct blittability is not affected by the presence of properties.

    Just hide the conversion to/from within the property, e.g.

        public struct DataStructure
        {
            private Int16 field;
            
            public Char Field
            {
            get {
                return (Convert.ToChar(field));
                }
            set {
                field = Convert.ToInt16(field);
                }
               
            }
        }
    


    Also, note that you can declare blittable arrays as follows:

            private fixed Int16 field[32];
    


    Out of interest, why are you needing to pin?

    Hugh

    Hugh Moran - http://www.morantex.com
    • Edited by Morantex Saturday, November 14, 2009 12:22 PM clarify
    • Proposed as answer by Morantex Wednesday, November 18, 2009 5:26 PM
    • Unproposed as answer by Boing3000 Thursday, November 19, 2009 7:27 AM
    Saturday, November 14, 2009 12:09 PM
  • -->Out of interest, why are you needing to pin?

    Well, I'll need a pin, like I will need a fixed() to use unsafe pointer on fixed buffer. I have given up the hope to have a constrain like where T : blittable (so I could use SizeOf or fixed() statement in generic) , but not the hope to be able to handle, in generic code, some primitive struct.
    The pin and unsafe context is only needed because I'll have huge array of them, and I don't want to waste memory/cpu just to have to copy them to a byte [] (it crashes anymay, hence this thread). All this because FileStream missed a Write(Array ) signature. So I will have to pin the array to "blit" them on disk (I don't consider that to be marshalling). Maybe NativeOverlapped can help me to write array on disk, but a void* is far easier to manadge.
    I will also pin them while unsafe sorting/copying.

    And creating my own MyChar, even with implicit operator to System:Char does not sound reasonable to me. A char is just a char, or so I'd think.


    Anyway, thanks for you answer !

    Monday, November 16, 2009 9:12 AM
  • I am able to pin array of structs with 2 chars without any problem:

    #include <stdio.h>
    value struct Struct_2Chars
    {
        System::Char v1;
        System::Char v2;
    };
    void main()
    {
        array<Struct_2Chars>^ arr = gcnew array<Struct_2Chars>(3);
        arr[0].v1 = 'a'; arr[0].v2 = 'b';
        arr[1].v1 = 'c'; arr[1].v2 = 'd';
        arr[2].v1 = 'e'; arr[2].v2 = 'f';
        pin_ptr<Struct_2Chars> p = &arr[1];
        Struct_2Chars * np = p;
        printf_s("arr[1] = { '%c', '%c' }\n", np->v1, np->v2);
    }
    


    What did I miss?
    -Karel
    Monday, November 23, 2009 6:11 PM
    Moderator
  • You did not miss anything, I miss to provide the example :-)

    Here it is, it involve GCHandle. I cannot use fixed because they a not allowed on generics even ‘where : struct’ is used (there is no ‘where : blittable’ clause)
    This example throw an exception. Change the using alias to UInt16, and it works great. That is a mystery for me.

    using System;
    using System.Runtime.InteropServices;
    using MyChar = System.Char; //System.UInt16 works great here
    
    namespace ConsoleApplication1
    {
        public struct Struct_2Chars
        {
            public MyChar v1;
            public MyChar v2;
        }
        class Program
        {
            static unsafe void Main(string[] args)
            {
                Struct_2Chars[] safeArr = new Struct_2Chars[3];
                fixed (Struct_2Chars* arr = &safeArr[0]) // yes ok
                {
                    arr[0].v1 = (MyChar)'a'; arr[0].v2 = (MyChar)'b';
                    arr[1].v1 = (MyChar)'c'; arr[1].v2 = (MyChar)'d';
                    arr[2].v1 = (MyChar)'e'; arr[2].v2 = (MyChar)'f';
                    Struct_2Chars* np = &arr[1];
                    Console.WriteLine("np = {{ '{0}', '{1}' }}", np->v1, np->v2);
                }
                Console.ReadLine();
                GCHandle pinn = GCHandle.Alloc(safeArr, GCHandleType.Pinned);
                    // Exception if  MyChar is char
                IntPtr address = Marshal.UnsafeAddrOfPinnedArrayElement(safeArr, 1);
                Struct_2Chars* np2 = (Struct_2Chars*)address.ToPointer();
                Console.WriteLine("np = {{ '{0}', '{1}' }}", np2->v1, np2->v2);
                pinn.Free();
                Console.ReadLine();
            }
        }
    }
    Tuesday, November 24, 2009 8:04 AM
  • Use [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] on your struct Struct_2Chars. That will make it work.

    To answer some of your related questions:
    - Blittable types are types which have the same representation in managed and unmanaged world and don't require interop marshaler. Note that System.Char is non-blittable type.
    - Pinning is mechanism to make managed object 'freeze' and not being moved by GC (used for marshaling).
    - fixed is C# language keyword for pinning (it is not .NET term/definition).
    - Primitive types ... I couldn't find really good definition anywhere, but it looks like it is language specific, it is not .NET term/definition. In the scope of GCHandle.Alloc it is defined as non-primitive type = non-blittable type.

    System.Char is non-blittable type and it is marshaled by default from Unicode (in managed world) to ANSI (in native world). Unless you explictly set the char's marshaling to Unicode, it will have to change encoding which might lead to information loss as not all Unicode chars are encodable in ANSI.

    GCHandle.Alloc allows to pin an object, but only blittable objects - see ArgumentException condition. This API could in theory avoid its limitation to blittable types, but the designers of this API decided otherwise (maybe because it would be too tempting for developers to pin more often? ... that's just my guess).

    Hope this helps,
    -Karel
    • Marked as answer by Boing3000 Thursday, November 26, 2009 7:55 AM
    Wednesday, November 25, 2009 11:56 PM
    Moderator
  • Yes, that helps me a lot THANK YOU !

    Now, CharSet is the only one attribute that I did not try to change. I would never taught that the default value would be different that the native representation.

    So I'll have again to make pseudo generic code and emit the ldelema instruction that's definitely more the fixed that I need than this marhsalling mayhem.


    Thanks again.

    Thursday, November 26, 2009 8:17 AM