user3485103
user3485103

Reputation: 33

C# Memory layout issue

I would like some enlightenment on some memory layout issue am having and struggling to make sense of it. This is my program.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Management;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace ManagementSysInfo
{
    class Program
    {
        public enum ACTION_TYPE :uint
        {
            NONE,  // 0
            RESTART, // 1.
            ABORT, // 2.
            WAIT //3
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct ACTIONS
        {
            public ACTION_TYPE type;
            public int delay;

        }

        static void Main(string[] args)
        {
            int numAction = 3;

            ACTIONS action1 = new ACTIONS()
            {
                type = ACTION_TYPE.RESTART,
                delay = 10000
            };

            ACTIONS action2, action3;
            action2 = action3 = action1;
            ACTIONS[] sc_act_array = new[] { action1, action2, action3 };

            int[] actionsArray = new int[numAction * 2];
            int index = -1;
            foreach (ACTIONS act in sc_act_array)
            {
                actionsArray[++index] = (int)act.type;
                actionsArray[++index] = (int)act.delay;
            }

            int buffsize = Marshal.SizeOf(actionsArray[0]) * actionsArray.Length ;
            IntPtr actions_ptr = Marshal.AllocHGlobal(buffsize);

            Marshal.Copy(actionsArray, 0, actions_ptr, sc_act_array.Length);

            int i =Marshal.SizeOf(actions_ptr);

            Marshal.FreeHGlobal(actions_ptr);
        }
    }
}

Ok what i am trying to acheive here is marshal an array of structure to unmanaged memory. But after some reading here and there people seems to say that the marshal class in the interop service doesn't provide a clean way to marshal array of structures. And it's better sometimes if doable( I guess for not complex struct ) to express it as an array of primitive. Hence my actionsArray variable holds the packed content of the struct array sc_act_array. And my actions_ptr will hold a reference to the content of actionsArray in the unmanaged memory. From my understding if everything goes right, I can read the information marshalled by using the pointer and adding some offset to it. Since every element in the array occipies a 4 bytes memory space ( correct me if am wrong) I had that mapping in my head thinking that's how the data in the unmanaged memory should map the one marshalled from managed memory :

(actions_ptr, 0) = actionsArray[0] = 1

(actions_ptr, 4) = actionsArray[1] = 10000

(actions_ptr, 8) = actionsArray[2] = 1

(actions_ptr, 12) = actionsArray[3] = 10000

(actions_ptr, 16) = actionsArray[4] = 1

(actions_ptr, 20) = actionsArray[5] = 10000

When you run the program everything goes well, no exception. But when i debbug and inspect the content by offsetting the pointer by a value of 4 byte i get something wierd, the following comes from my Immediate window :

Marshal.ReadInt32(actions_ptr, 0) 1 Marshal.ReadInt32(actions_ptr, 4) 10000 Marshal.ReadInt32(actions_ptr, 8) 1 Marshal.ReadInt32(actions_ptr, 12) 1 Marshal.ReadInt32(actions_ptr, 16) 192 Marshal.ReadInt32(actions_ptr, 20)

Marshal.ReadInt32(actions_ptr, 24) 461628657 Marshal.ReadInt32(actions_ptr, 28) -2147483640

I am struggling to understand why the memory is not layed out as I expected. Am i missing something here? I thought that it might be related to the fact that the CPU likes to process data in specifics chunk (ie: 8 bytes) and somehow it's adding some padding inbetween. Or maybe it's just the fact that Marshal.copy copies the data to unmanaged memory in a non sequential way.

Can anybody shed some light please.

Thanks

Upvotes: 2

Views: 255

Answers (1)

xanatos
xanatos

Reputation: 111940

Marshaling of arrays of simple structs from C# to C/C++ works perfectly well. The problem is in the opposite direction: you can't create an array C/C++ side and marshal it to C#, because the CLR can't know the size of the array.

Test code:

C#

public enum ACTION_TYPE : uint
{
    NONE,  // 0
    RESTART, // 1.
    ABORT, // 2.
    WAIT //3
}

[StructLayout(LayoutKind.Sequential)]
public struct ACTIONS
{
    public ACTION_TYPE type;
    public int delay;

}

[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void testarray(ACTIONS[] actions, int len);

var actions = new[] 
{
    new ACTIONS { type = ACTION_TYPE.RESTART, delay = 11 },
    new ACTIONS { type = ACTION_TYPE.ABORT, delay = 22 },
    new ACTIONS { type = ACTION_TYPE.WAIT, delay = 33 },
};

testarray(actions, actions.Length);

C++

enum ACTION_TYPE : unsigned int
{
    NONE,  // 0
    RESTART, // 1.
    ABORT, // 2.
    WAIT //3
};

struct ACTIONS
{
    ACTION_TYPE type;
    int delay;
};

__declspec(dllexport) void __stdcall testarray(ACTIONS* actions, int len)
{
    for (int i = 0; i < len; i++)
    {
        printf("%u %d\n", actions[i].type, actions[i].delay);
    }
}

and for your error

Marshal.Copy(actionsArray, 0, actions_ptr, actionsArray.Length);

This is the correct Marshal.Copy. You were copying too few bytes.

Upvotes: 2

Related Questions