none
Marshalling a struct that has an array RRS feed

  • Question

  • Hi,

    I'm trying to pass an IntPtr to a struct to a C function (the library is compiled to a dll using CLR), but I'm having some problems when it contains an array, for instance TestStruct2 in the example below. This doesn't work because an int[] isn't "blittable".

    My (more or less) only working solution is ExportableArray, which does some magic stuff (at first glance anyways) to keep the IntPtr up to date.

    1    [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
    2    struct TestStruct2 
    3    { 
    4        public int[] m_TestArray; 
    5        public int m_ArraySize; 
    6    } 
    7 
    8    [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
    9    struct TestStruct3 
    10    { 
    11        public ExportableArray<int> m_TestArray; 
    12    } 
    13 
    14 
    15 
    16    public struct ExportableArray<T> 
    17    { 
    18 
    19        public IntPtr m_ArrayPtr; 
    20        private int m_ArraySize; 
    21 
    22        public T[] MyArray 
    23        { 
    24            get 
    25            { 
    26                int maxoffset = (int)m_ArraySize * Marshal.SizeOf( typeof( T ) ); 
    27                byte[] old = new byte[maxoffset]; 
    28                Marshal.Copy( m_ArrayPtr, old, 0, maxoffset ); 
    29 
    30                GCHandle handle = dict[GetHashCode()]; 
    31                handle.Free(); 
    32 
    33                T[] r2 = new T[m_ArraySize]; 
    34                handle = GCHandle.Alloc( r2, GCHandleType.Pinned ); 
    35                m_ArrayPtr = handle.AddrOfPinnedObject(); 
    36 
    37                int offset = 0; 
    38                while ( offset < maxoffset ) 
    39                { 
    40                    Marshal.WriteByte( m_ArrayPtr, offset, old[offset] ); 
    41                    ++offset; 
    42                } 
    43 
    44 
    45                dict[GetHashCode()] = handle; 
    46 
    47                return r2; 
    48            } 
    49            set 
    50            { 
    51                if ( dict == null ) 
    52                { 
    53                    dict = new Dictionary<int, GCHandle>(); 
    54                } 
    55 
    56                if ( dict.ContainsKey( GetHashCode() ) ) 
    57                { 
    58                    GCHandle oldhandle = dict[GetHashCode()]; 
    59                    if ( oldhandle.IsAllocated ) 
    60                        oldhandle.Free(); 
    61                } 
    62 
    63 
    64                m_ArraySize = value.Length; 
    65 
    66                GCHandle handle = GCHandle.Alloc( value, GCHandleType.Pinned ); 
    67                m_ArrayPtr = handle.AddrOfPinnedObject(); 
    68 
    69                dict[GetHashCode()] = handle; 
    70            } 
    71        } 
    72 
    73        static Dictionary<int, GCHandle> dict; 
    74    } 
    75 


    Using something like MarshalAs(UnmanagedType.ByValArray)
    doesn't work, because the struct layout must match the layout of a struct in C.
    ExportableArray does not currently work when the generic type is something that Marshal.Copy can't handle, such as another struct.



    Thursday, August 14, 2008 4:23 PM

Answers

  • Custom marshallers do not work on structures.  Try this:

        [StructLayout(LayoutKind.Sequential)]
        public struct STestArrayStruct {
          public IntPtr m_arr;
          public int m_arrCount;
          public void Init(int[] values) {
            m_arrCount = values.Length;
            m_arr = Marshal.AllocHGlobal(values.Length * 4);
            Marshal.Copy(values, 0, m_arr, values.Length);
          }
          public void Destroy() {
            Marshal.FreeHGlobal(m_arr);
            m_arr = IntPtr.Zero;
          }
        }

    You must call Init() before P/Invoking the function that uses the structure, Destroy() afterwards.

    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Tuesday, August 19, 2008 12:03 PM
    Monday, August 18, 2008 12:48 PM
    Moderator

All replies

  • Well, it can't be UnmanagedType.ByValArray, the unmanaged code would never know where to read the m_ArraySize member.  Only LPArray makes sense.   If that's not it, you'll need to post the C/C++ declaration.
    Hans Passant.
    Friday, August 15, 2008 3:01 AM
    Moderator
  • Sorry, forgot to mention that I've tried (of course) LPArray but that it didn't help, although a different thing happens.

    My code for turning it into an IntPtr I can pass to Managed C++ looks like this:
                int size = Marshal.SizeOf( teststruct2 ); 
                IntPtr alloced = Marshal.AllocHGlobal( size ); 
                Marshal.StructureToPtr( teststruct2, alloced, true ); 


    When the struct is defined in this way:
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
        struct TestStruct2 
        { 
            [MarshalAs(UnmanagedType.LPArray)] 
            public int[] arr; 
            public int m_ArraySize; 
        } 

    The "int size = ..." line casts this exception:
    "Type 'ModEditor.TestStruct2' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed."

    When it's defined like this:
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
        struct TestStruct2 
        { 
            public int[] arr; 
            public int m_ArraySize; 
        } 

    Instead, the third line (StructureToPtr) throws this exception:
    "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

    So the first way seems better, but I'm not sure what I can do about it.


    The c definition of the struct is:
    struct STestStruct2 
        uint32*     m_arr; 
        uint32      m_arrCount; 
    }; 





    Friday, August 15, 2008 7:20 AM
  • Change arr in your C# TestStruct to an IntPtr instead (or int* if you don't mind using unsafe code). You can allocate the array with Marshal.Alloc* and populate it with Marshal.Copy.

    Mattias, C# MVP
    Friday, August 15, 2008 9:33 AM
    Moderator
  • That's pretty much what I'm trying to accomplish with TestStruct3/ExportableArray. However, Marshal.Copy only works with basic types such as byte[]and not when I want to have an array of other structs.

    It seems the only problem I need to solve is to be able to get an IntPtr to an array of struct. I've tried using GCHandle but it can't handle string members.


    Friday, August 15, 2008 2:43 PM
  • We can't help you until you post the C/C++ declaration.
    Hans Passant.
    Friday, August 15, 2008 11:16 PM
    Moderator
  • The things Marshal.Copy can't handle can usually be taken care of with RtlMoveMemory (aka MoveMemory or CopyMemory) in kernel32.dll, which you can call with PInvoke.

    Mattias, C# MVP
    Monday, August 18, 2008 7:49 AM
    Moderator

  • nobugz said:

    We can't help you until you post the C/C++ declaration.


    Hans Passant.


    The C definition is found at the bottom of my post above, but below is another test example I'm trying to accomplish now. I've looked a bit into implementing a custom marshaler, but no luck there either.


    struct STestStructSimple 
        uint32 m_a; 
    }; 
     
    struct STestArrayStruct 
        STestStructSimple* m_arr; 
        uint32             m_arrCount; 
    }; 
     
     
     
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
        struct TestStructSimple 
        { 
            public uint a; 
        } 
     
        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
        struct TestArrayStruct 
        { 
            [MarshalAs( UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(ArrayMarshaler)) ] 
            public TestStructSimple[] arr; 
        } 

    Calling Marshal.SizeOf() on TestArrayStruct generates the same exception as before, and none of the functions in my marshaler is called:

    "Type 'ModEditor.TestArrayStruct' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed."

    I'm now leaning towards having two separate "layouts" for each struct, i.e. a struct that just has an IntPtr and a count for each array (other member fields work), and a class that is the one that the editor edits and which holds a reference to the struct.

    I.e. something like this:

        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Ansi )] 
        struct TestArrayStruct 
        { 
            public IntPtr ptr; 
            public int arr_size; 
        } 
     
        class TestArrayClass 
        { 
            ArrayStruct m_Arr; 
            private int[] m_Ints; 
     
            public int[] Ints 
            { 
                get { return m_Ints; } 
                set { m_Ints = value; } 
            } 
        } 

    Does this seem like a good idea? The class would need a WriteToStruct function or something that allocated the ptrs and stuff.

    Thanks for the help!

    /Anders

    Monday, August 18, 2008 11:58 AM
  • Custom marshallers do not work on structures.  Try this:

        [StructLayout(LayoutKind.Sequential)]
        public struct STestArrayStruct {
          public IntPtr m_arr;
          public int m_arrCount;
          public void Init(int[] values) {
            m_arrCount = values.Length;
            m_arr = Marshal.AllocHGlobal(values.Length * 4);
            Marshal.Copy(values, 0, m_arr, values.Length);
          }
          public void Destroy() {
            Marshal.FreeHGlobal(m_arr);
            m_arr = IntPtr.Zero;
          }
        }

    You must call Init() before P/Invoking the function that uses the structure, Destroy() afterwards.

    Hans Passant.
    • Marked as answer by Zhi-Xin Ye Tuesday, August 19, 2008 12:03 PM
    Monday, August 18, 2008 12:48 PM
    Moderator
  • That's along the lines I've tried so far. The problem is that I don't have just int arrays which means I can't use Marshal.Copy directly.

    Here's an example of the hierarchy I'm trying to get working right now. I have managed to create the correct structure but the code becomes very specialized and it doesn't feel "pretty".

    1 
    2struct STestStruct 
    3
    4    uint32      m_a; 
    5    uint32      m_b; 
    6    const char* m_c; 
    7}; 
    8 
    9struct STestStruct2 
    10
    11    uint32*     m_arr; 
    12    uint32      m_arrCount; 
    13    STestStruct m_s; 
    14}; 
    15 
    16 
    17struct STestArrayStruct 
    18
    19    STestStruct2* m_arr; 
    20    uint32        m_arrCount; 
    21}; 
    22 
    23 


    Going the other way seems to be an even worse problem though. I can't do Marshal.StructureFromPtr on a struct that contains an array, so I somehow need to "unwrap" that one first.

    I can't believe that marshaling an array of all things should cause this much problems, even if it is dynamic. How come so many marshaling options (like CustomMarshaler) aren't available on structs? Is it just something that hasn't been implemented yet?
    Tuesday, August 19, 2008 2:38 PM
  • Hi nobugz,

    What to do when it have some reference in class or struct in Windows Mobile CF

    Then what to do for getting the size of such class or struct which have some refrence to other struct like in the case below.

    [StructLayout(LayoutKind.Sequential)]
        public class ForwardList
        {
            [Internal]
            byte[] m_data;
          
            public ForwardList(int size)
            {
                m_data = new byte[size];
                TotalSize = size;
            }
          
            public byte[] Data { get { return m_data; } }
          
            public int TotalSize
            {
                get { return BitConverter.ToInt32(m_data, 0); }
                set { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, m_data, 0, 4); }
            }       
           
            public int NumEntries
            {
                get { return BitConverter.ToInt32(m_data, 4); }
                set { Buffer.BlockCopy(BitConverter.GetBytes(value), 0, m_data, 4, 4); }
            }       
           
            public LineForward[] List
            {
                get
                {
                    LineForward[] lf = new LineForward[NumEntries];
                    for (int i = 0; i < NumEntries; i++)
                        lf[i] = LineForward.FromByteArray(m_data, Marshal.SizeOf(typeof(LineForward)) * i);
                    return lf;
                }
            }

        }

     

    [StructLayout(LayoutKind.Sequential)]
        public struct LineForward
        {
            public static LineForward FromByteArray(byte[] data, int offset)
            {
                LineForward lf = new LineForward();
                lf.ForwardMode = (ForwardMode)BitConverter.ToInt32(data, offset);
                lf.CallerAddressSize = BitConverter.ToInt32(data, offset + 4);
                lf.CallerAddressOffset = BitConverter.ToInt32(data, offset + 8);
                lf.DestCountryCode = BitConverter.ToInt32(data, offset + 12);
                lf.DestAddressSize = BitConverter.ToInt32(data, offset + 16);
                lf.DestAddressOffset = BitConverter.ToInt32(data, offset + 20);
                return lf;

            }
            public ForwardMode ForwardMode;
            public int CallerAddressSize;
            public int CallerAddressOffset;
            public int DestCountryCode;
            public int DestAddressSize;
            public int DestAddressOffset;

        }

     

    This is what i have done but getting an exception of Notsupportedexception

    int dwSize = Marshal.SizeOf(typeof(ForwardList));

    Help me.

    Thanks in advance

    Monday, May 17, 2010 1:49 PM