从窗口消息的lParam属性中接收到的IntPtr填充结构体在c#中跨越进程边界

本文关键字:结构体 填充 跨越 边界 进程 IntPtr 消息 窗口 lParam 属性 | 更新日期: 2023-09-27 18:11:21

我几天前发布了这个问题,并且我对将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进程的内存空间中的某个地方。

一开始,我只是试图元帅。PtrToStructure(实际上几乎没有希望)和结构体被垃圾数据填充。我也尝试了GetLParam与相同的结果。最后,我尝试使用ReadProcessMemory API来跨越进程边界,正如在这些帖子中所解释的那样:

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

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

我得到异常代码299 (ERROR_PARTIAL_COPY:只有一部分ReadProcessMemory或WriteProcessMemory请求被完成。)另外,我从ReadProcessMemory中得到的字节[]是: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指向它的内存空间呢?我是否应该考虑使用"不安全"?有人能推荐这种方法吗?还有其他方法来自定义封送结构吗?

我知道在一个帖子上有太多的问题,但我认为他们都指向解决这个问题。提前感谢大家的帮助,很抱歉让我花这么长时间。

从窗口消息的lParam属性中接收到的IntPtr填充结构体在c#中跨越进程边界

我想我得自己拍了。因此,正如上面的注释所述,删除

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

行起了作用。我理解,当管理上下文中的指针指向非托管上下文中的结构时,GC将收集它,因为指针在其地址中确实没有任何内容。事实上,情况正好相反。在托管上下文中,当我们持有一个对象或结构体时,它是从非托管上下文中指向的,GC可以收集它,因为托管上下文中没有指针指向它,因此需要固定它以保持GC的距离。

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

感谢Jim和David,他们给我指明了正确的方向。