在 C# 中跨进程边界的窗口消息的 lParam 属性中接收的 IntPtr 填充结构

发布于 2024-12-10 06:26:42 字数 6275 浏览 0 评论 0原文

我发布了这个问题几天前,我对将 IntPtr 编组到结构体有一些后续疑问。

事情是这样的: 正如我引用的问题中所述,我调用本机 Dll 上的异步方法。这些方法通过 Windows 消息来传达其完成情况。我现在可以正确接收 Windows 消息以及其中的 lParam 属性(类型为 IntPrt)。 根据我下面的文档,这个 lParam 指向具有该方法执行结果的结构。作为一个特定的示例,我尝试填充的结构之一定义如下:

原始 C 签名:

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;

我的 C# 定义:

[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;
}

现在有趣的部分:我正在调用的本机 Dll 属于一个独立进程 FWMAIN32.EXE,它是在同一台机器上运行(单实例)。我相信我收到的窗口消息(特定于应用程序(WM_USER 之上))返回的 LParam 并未真正指向我期望的结构,并且该结构驻留在 FWMAIN32.EXE 进程的内存空间中的某个位置。

最初,我尝试只使用 Marshal.PtrToStructure (实际上希望不大),但结构中充满了垃圾数据。我也尝试使用 GetLParam 得到相同的结果。最后,我尝试使用 ReadProcessMemory API 跨越进程边界,如以下帖子中所述:

C# p/invoke,从所有者绘制的列表框读取数据

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

我收到异常代码 299(ERROR_PARTIAL_COPY:仅完成了 ReadProcessMemory 或 WriteProcessMemory 请求的一部分。) 另外,我使用 ReadProcessMemory 得到的 byte[] 是: 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

我的代码:

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");
        }
    }
}

我这样使用它:

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);
}

在这种情况下跨越进程边界是否正确? 使用lParam作为ReadProcessMemory的基地址是否正确? CLR 是否将 lParam 转换为我无法使用的内容? 为什么我会收到 299 例外? 我正确地获取了 FWMAIN32.EXE 的进程 ID,但如何确定 lParam 指向其内存空间内部? 我应该考虑使用“不安全”吗?有人可以推荐这种方法吗? 还有其他方法来自定义编组结构吗?

我知道,一篇文章有​​太多问题,但我认为它们都指向解决这个问题。提前感谢大家的帮助,很抱歉我不得不拖这么久。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

北凤男飞 2024-12-17 06:26:42

我想我必须亲自拿走这个。因此,正如上面评论中所述,删除该

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

行就可以了。我了解到,当管理上下文中的指针指向非托管上下文中的结构时,GC 会收集它,因为该指针的地址中实际上没有任何内容。事实上相反。当在托管上下文中,我们持有从非托管上下文指向的对象或结构时,GC 可以收集它,因为托管上下文中没有指针指向它,因此需要固定它以保持 GC在远处。

因此,最终,在这种情况下没有必要跨越流程边界。我删除了对 Kernell32 方法的调用,因为 CLR 可以很好地处理编组,而 Marshal.PtrToStructure 就是我所需要的。

感谢吉姆和大卫,他们为我指明了正确的方向。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文