views:

2413

answers:

3

I am trying to convert an array of the RECT structure (given below) into an IntPtr, so I can send the pointer using PostMessage to another application.

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

    // lots of functions snipped here
}

// so we have something to send, in reality I have real data here
// also, the length of the array is not constant
RECT[] foo = new RECT[4]; 
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(foo[0]) * 4);
Marshal.StructureToPtr(foo, ptr, true); // -- FAILS

This gives an ArgumentException on the last line ("The specified structure must be blittable or have layout information."). I need to somehow get this array of RECTs over to another application using PostMessage, so I really need a pointer to this data.

What are my options here?

UPDATE: This seems to work:

 IntPtr result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.RECT)) * foo.Length);
 IntPtr c = new IntPtr(result.ToInt32());
 for (i = 0; i < foo.Length; i++)
 {
     Marshal.StructureToPtr(foo[i], c, true);
     c = new IntPtr(c.ToInt32() + Marshal.SizeOf(typeof(Win32.RECT)));
 }

UPDATED AGAIN to fix what arbiter commented on.

A: 

You could try the following:

 RECT[] rects = new RECT[ 4 ];
 IntPtr[] pointers = new IntPtr[4];
 IntPtr result =  Marshal.AllocHGlobal(IntPtr.Size * rects.Length);
 for (int i = 0; i < rects.Length; i++)
 {
     pointers[i] = Marshal.AllocHGlobal (IntPtr.Size);
     Marshal.StructureToPtr(rects[i], pointers[i], true);
     Marshal.WriteIntPtr(result, i * IntPtr.Size, pointers[i]);
 }
 // the array that you need is stored in result

And don't forget to free everything after you are finished.

devdimi
Except that this is an array of pointers to RECTs not an array of RECTs. And you are allocating pointer sized blocks of memory for each of the pointers[i] but then marshaling 16 bytes (sizeof(RECT)) to that spot. This type of overflow is a recipe for disaster.
Stephen Martin
+4  A: 

StructureToPtr expects struct object, and foo is not structure it is array, that is why exception occurs.

I can suggest you to write structures in cycle (sadly, StructureToPtr does not have overload with Index):

long LongPtr = ptr.ToInt64(); // Must work both on x86 and x64
for (int I = 0; I < foo.Length; I++)
{
    IntPtr RectPtr = new IntPtr(LongPtr);
    Marshal.StructureToPtr(foo[I], RectPtr, false); // You do not need to erase struct in this case
    LongPtr += Marshal.SizeOf(typeof(Rect));
}

Another option is to write structure as four integers, using Marshal.WriteInt32:

for (int I = 0; I < foo.Length; I++)
{
    int Base = I * sizeof(int) * 4;
    Marshal.WriteInt32(ptr, Base + 0, foo[I].Left);
    Marshal.WriteInt32(ptr, Base + sizeof(int), foo[I].Top);
    Marshal.WriteInt32(ptr, Base + sizeof(int) * 2, foo[I].Right);
    Marshal.WriteInt32(ptr, Base + sizeof(int) * 3, foo[I].Bottom);
}

And the last, you can use unsafe keyword, and work with pointers directly.

arbiter
A: 

Arbiter has given you one good answer for how to marshal arrays of structs. For blittable structs like these I, personally, would use unsafe code rather than manually marshaling each element to unmanaged memory. Something like this:

RECT[] foo = new RECT[4];
unsafe
{
    fixed (RECT* pBuffer = foo)
    {
        //Do work with pointer
    }
}

or you could pin the array using a GCHandle.

Unfortunately, you say you need to send this information to another process. If the message you are posting is not one of the ones for which Windows provides automatic marshaling then you have another problem. Since the pointer is relative to the local process it means nothing in the remote process and posting a message with this pointer will cause unexpected behavior, including likely program crash. So what you need to do is write the RECT array to the other process' memory not your own. To do this you need to use OpenProcess to get a handle to the process, VitualAllocEx to allocate the memory in the other process and then WriteProcessMemory to write the array into the other process' virtual memory.

Unfortunately again, if you are going from a 32bit process to a 32bit process or from a 64bit process to a 64bit process things are quite straightforward but from a 32bit process to a 64bit process things can get a little hairy. VirtualAllocEx and WriteProcessMemory are not really supported from 32 to 64. You may have success by trying to force VirtualAllocEx to allocate its memory in the bottom 4GB of the 64bit memory space so that the resultant pointer is valid for the 32bit process API calls and then write with that pointer. In addition, you may have struct size and packing differences between the two process types. With RECT there is no problem but some other structs with packing or alignment issues might need to be manually written field by field to the 64bit process in order to match the 64bit struct layout.

Stephen Martin