0

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.

Community
  • 1
  • 1
Gabe Thorns
  • 1,426
  • 16
  • 20
  • 1
    `ReadProcessMemory` is going to be hard work. I would use `WM_COPYDATA` and let Windows do the cross-process marshalling for you. Then all you need is `Marshal.StructureToPtr`. `Marshal.PtrToStructure`. – David Heffernan Oct 14 '11 at 17:31
  • @David. I do not have access to the native code, so I do not see how could I use WM_COPYDATA. If possible, could you clarify this a little bit please? Also, I understand that the data being passed with WM_COPYDATA must not contain pointers or other references to objects not accessible to the application receiving the data. I have too many structs to marshal and many of them have pointers and references to other structs, which I think would be a problem in this approach. Thanks! – Gabe Thorns Oct 14 '11 at 18:20
  • ReadProcessMemory it is then. You need to sort out error checking though. Check all return values. Don't call GetLastError. Use SetLastError=true in the DllImport and call Marshal.GetLastWin32Error. – David Heffernan Oct 14 '11 at 21:29
  • Why are you using explicit offsets? I'd have expected sequential. Why did you assume struct is packed? Seems unlikely. I'd build a test DLL first and check that I can marshal to that first before going cross process. – David Heffernan Oct 14 '11 at 21:31
  • @David. Thanks again for your reply. I will follow your recomendations and post updates on my progress. Cheers. – Gabe Thorns Oct 14 '11 at 21:36
  • Looking at your structs, I'm pretty sure they are wrong and that would explain everything. Put the union in a separate struct with explicit layout, all fields offset 0. Then do the other structs layout sequential. – David Heffernan Oct 14 '11 at 21:46
  • @DavidHeffernan. About your question on explicit offset, I actually changed the struct a few times to get it to work on the Synchronous versions of the methods, that unlike que Asynchronous versions return the structure directly using an output parameter. My first implementation was sequential, and the Union was a different struct but it didn't work and I believe it was precisely because of the Union in the original signature. The struct is perfectly marshalled on the synchronous versions with the signature I have now. – Gabe Thorns Oct 14 '11 at 21:46
  • The first thing I would do to diagnose this is examine the memory referenced by `msg.LParam`. That is, allocate a `byte[]` buffer that's the expected size of the structure, and call `Marshal.Copy(msg.LParam, buffer, 0, buffer.Length)`. Then examine the buffer to see if it contains the data you expected. – Jim Mischel Oct 14 '11 at 21:48
  • @JimMischel. Thanks for the advise. I'll give it a try and post updates on my progress. Cheers. – Gabe Thorns Oct 14 '11 at 21:51
  • Jim's advice is very good. I'd be surprised if the struct was packed, but only you can know that. Of course it's perfectly possible. Now, you declare `lpBuffer` as Int32 but it's a pointer. Should be `IntPtr`. In a 64 bit process that would kill you good. – David Heffernan Oct 14 '11 at 21:58
  • @DavidHeffernan. Indeed. The reason I had it as an Int32 is because my application is expected to work only on 32 bits WinXP. I am just in the process of implementing both your suggestions. Thanks a lot again. – Gabe Thorns Oct 14 '11 at 22:05
  • All the same declare it as `IntPtr`, but if you are on XP32 then that's clearly not the problem – David Heffernan Oct 14 '11 at 22:06
  • By the way, there's no reason to allocate a `GCHandle` for `msg.LParam`. The GC isn't managing the memory the parameter points to. And, incidentally, you're leaking the `GCHandle`. You have to explicitly call `Free` to release it. Otherwise you'll leak a handle every time this message is received. It's good practice to use a `try ... finally { gch.Free(); }` whenever you're using `GCHandle`. – Jim Mischel Oct 14 '11 at 22:47
  • @JimMischel. Thanks for the observation about GC. I am aware this code produces many leaks, as it is still an alpha in which many aspects that should (must) be included in a final release are missing. – Gabe Thorns Oct 15 '11 at 03:41
  • @JimMischel: I did remove the GCHandle.Alloc for msg.LParam, remove all the calls to Kernel32 methods and executed the test. It worked and my struct got correctly filled. Post it as an answer and I will accept it. Yet, I would like to know what is going on with the GCHandle.Alloc and why, by using it, I could not get the correct marshaling of the structure. If possible, add an explanation on your answer. Thank! – Gabe Thorns Oct 26 '11 at 13:49

1 Answers1

0

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.

Gabe Thorns
  • 1,426
  • 16
  • 20