0

数日前にこの質問を投稿しましたが、IntPtr を構造体にマーシャリングすることについて、いくつか疑問があります。

事は次のようになります: 私が参照している質問で述べたように、私はネイティブ Dll で非同期メソッドを呼び出します。これらのメソッドは、Windows メッセージで完了を伝えます。Windows メッセージを正しく受け取り、その中に lParam プロパティ (IntPrt 型) を受け取りました。私がフォローしているドキュメントによると、この lParam は、メソッドの実行結果を持つ構造体を指しています。特定の例として、私が埋めようとしている構造の 1 つが次のように定義されています。

元の 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

私のコード:

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

この場合、プロセスの境界を越えることは正しいですか? ReadProcessMemory のベース アドレスとして lParam を使用するのは正しいですか? CLR は lParam を使用できないものに変えていますか? 299 例外が発生するのはなぜですか? FWMAIN32.EXE のプロセス ID を正しく取得できますが、lParam がそのメモリ空間内を指していることを確認するにはどうすればよいですか? 「安全でない」の使用を検討する必要がありますか? 誰もそのアプローチを推奨できますか? 構造体をカスタムマーシャリングする他の方法はありますか?

1 つの投稿にあまりにも多くの質問があることは承知していますが、それらはすべてこの問題の解決を指していると思います。事前にご協力いただきありがとうございます。長くなってしまい申し訳ありません。

4

1 に答える 1

0

これは自分で取らなければならないと思います。したがって、上記のコメントで述べたように、

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

ラインはトリックをしました。管理コンテキストのポインターが非管理コンテキストの構造体を指している場合、ポインターのアドレスには実際には何もないため、GC はそれを収集することを理解しました。実際は逆です。管理されたコンテキストで、管理されていないコンテキストからポイントされているオブジェクトまたは構造体を保持する場合、管理されたコンテキストのポインターがそれを指していないため、GC はそれを収集できます。したがって、GC を維持するためにそれを固定する必要があります距離で。

したがって、この場合、プロセスの境界を越える必要はありませんでした。CLR がマーシャリングをうまく処理し、Marshal.PtrToStructure だけが必要だったので、Kernell32 メソッドの呼び出しを削除しました。

クレジットは、私を正しい方向に向けてくれたジムとデビッドに送られます。

于 2011-11-03T20:15:22.073 に答える