Fill struct from IntPtr received in lParam property of Window Message going across process boundaries in C#

StackOverflow https://stackoverflow.com/questions/7771320

Question

I posted this question a few days ago, and I have some follow up doubts about marshaling an IntPtr to a struct.

The thing goes like this: As stated in the question I am referencing, I make calls to asynchronous methods on a native Dll. These methods communicate their completion with Windows Messages. I receive the Windows Message correctly now and, within it, an lParam property (of type IntPrt). According to the documentation I am following, this lParam points to the struct that has the results of the execution of the method. As a particular example, one of the structures I am trying to fill is defined as follows:

Original C signature:

typedef struct _wfs_result {
    ULONG RequestID;
    USHORT hService;
    TIMESTAMP tsTimestamp;  /*Win32 SYSTEMTIME structure according to documentation*/
    LONG hResult;
    union {
        DWORD dwCommandCode;
        DWORD dwEventID;
    } u;
    LPVOID lpBuffer;
    } WFSRESULT, *LPWFSRESULT;

My C# definition:

[StructLayout(LayoutKind.Sequential), Serializable]
public struct Timestamp
{
    public ushort wYear;
    public ushort wMonth;
    public ushort wDayOfWeek;
    public ushort wDay;
    public ushort wHour;
    public ushort wMinute;
    public ushort wSecond;
    public ushort wMilliseconds;
}
[StructLayout(LayoutKind.Explicit), Serializable]
public struct WFSResult
{
    [FieldOffset(0), MarshalAs(UnmanagedType.U4)]
    public uint RequestID;

    [FieldOffset(4), MarshalAs(UnmanagedType.U2)]
    public ushort hService;

    [FieldOffset(6), MarshalAs(UnmanagedType.Struct, SizeConst = 16)]
    public Timestamp tsTimestamp;

    [FieldOffset(22), MarshalAs(UnmanagedType.U4)]
    public int hResult;

    [FieldOffset(26), MarshalAs(UnmanagedType.U4)]
    public UInt32 dwCommandCode;

    [FieldOffset(26), MarshalAs(UnmanagedType.U4)]
    public UInt32 dwEventID;

    [FieldOffset(30), MarshalAs(UnmanagedType.U4)]
    public Int32 lpBuffer;
}

Now the fun part: the native Dll I am calling belongs to an independent process, FWMAIN32.EXE, which is running in the same machine (single instance). I believe the Window Message that I receive, which is application specific (above WM_USER), returns an LParam that is not really pointing to the struct I am expecting, and that the struct resides somewhere in the memory space of the FWMAIN32.EXE process.

Initially, I tried to just Marshal.PtrToStructure (with little hope actually) and the struct got filled with garbage data. I also tried with GetLParam with same outcome. Finally, I tried to go across process boundaries with the ReadProcessMemory API, as explained in these posts:

C# p/invoke, Reading data from an Owner Drawn List Box

http://www.codeproject.com/KB/trace/minememoryreader.aspx

I get the exception code 299 (ERROR_PARTIAL_COPY: Only part of a ReadProcessMemory or WriteProcessMemory request was completed.) And additionally the byte[] I get from using ReadProcessMemory is: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

My code:

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

namespace XFSInteropMidleware
{
    public class CrossBoundaryManager
    {
        [DllImport("kernel32")]
        static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

        [DllImport("kernel32")]
        static extern Int32 ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [In, Out] byte[] lpBuffer, UInt32 dwSize, out IntPtr lpNumberOfBytesRead);

        [DllImport("kernel32")]
        static extern Int32 CloseHandle(IntPtr hObject);

        [DllImport("kernel32")]
        static extern int GetLastError();

        private const string nativeProcessName = "FWMAIN32";
        private IntPtr hProcess = IntPtr.Zero;

        const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L | 0xFFF);

        static int dwSize = 34; //The size of the struct I want to fill
        byte[] lpBuffer = new byte[dwSize];

        public void OpenProcess()
        {
            Process[] ProcessesByName = Process.GetProcessesByName(nativeProcessName);

            hProcess = CrossBoundaryManager.OpenProcess(CrossBoundaryManager.PROCESS_ALL_ACCESS, 1, (uint)ProcessesByName[0].Id);
        }

        public byte[] ReadMemory(IntPtr lParam, ref int lastError)
        {
            try
            {
                IntPtr ptrBytesReaded;
                OpenProcess();

                Int32 result = CrossBoundaryManager.ReadProcessMemory(hProcess, lParam, lpBuffer, (uint)lpBuffer.Length, out ptrBytesReaded);

                return lpBuffer;
            }
            finally
            {
                int processLastError = GetLastError();

                if (processLastError != 0)
                {
                    lastError = processLastError;
                }

                if (hProcess != IntPtr.Zero)
                    CloseHandle(hProcess);
            }
        }

        public void CloseProcessHandle()
        {
            int iRetValue;
            iRetValue = CrossBoundaryManager.CloseHandle(hProcess);
            if (iRetValue == 0)
                throw new Exception("CloseHandle failed");
        }
    }
}

And I use it like this:

protected override void WndProc(ref Message m)
{
    StringBuilder sb = new StringBuilder();

    switch (m.Msg)
    {
        case OPEN_SESSION_COMPLETE:
            GCHandle openCompleteResultGCH = GCHandle.Alloc(m.LParam); //So the GC does not eat the pointer before I can use it

            CrossBoundaryManager manager = new CrossBoundaryManager();

            int lastError = 0;
            byte[] result = manager.ReadMemory(m.LParam, ref lastError);

            if (lastError != 0)
            {
                txtState.Text = "Last error: " + lastError.ToString();
            }

            StringBuilder byteResult = new StringBuilder();
            for (int i = 0; i < result.Length; i++)
            {
                byteResult.Append(result[i].ToString() + " ");
            }

            sb.AppendLine("Memory Read Result: " + byteResult.ToString());
            sb.AppendLine("Request ID: " + BitConverter.ToInt32(result, 0).ToString());
            txtResult.Text += sb.ToString();

            manager.CloseProcessHandle();

            break;                
    }
    base.WndProc(ref m);
}

Is it correct to go across process boundaries in this case? Is it correct to use lParam as the base address for ReadProcessMemory? Is the CLR turning lParam to something I cannot use? Why I am getting the 299 exception? I correctly get the process ID of FWMAIN32.EXE, but how can I be sure the lParam is pointing inside its memory space? Should I consider the use of "unsafe"? Could anyone recommend that approach? Are there any other ways to custom marshal the struct?

Too many questions on a single post, I know, but I think they all point to resolving this issue. Thank you all for your help in advance, and sorry I had to make it so long.

Was it helpful?

Solution

I guess I have to take this one myself. So, as stated in the comments above, removing the

GCHandle openCompleteResultGCH = GCHandle.Alloc(m.LParam);

line did the trick. I understood that when a pointer in manage context is pointing to a struct in unmanaged context, the GC would collect it as the pointer really had nothing in its address. It is in fact the other way around. When, in managed context, we hold an object or struct that is being pointed from an unmanaged context, the GC could collect it because no pointer in the managed context is pointing to it, thus the need to pin it in order to keep the GC at distance.

So, in the end, there was no need go across process boundaries in this case. I removed the call to the Kernell32 methods, as the CLR handles the marshalling quiet well and Marshal.PtrToStructure was all I needed.

Credit goes to Jim and David who pointed me in the right direction.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top