C# Get Path/Filename by Handle (hWnd) 32 and 64bit

2019-05-03 20:20发布

I got the following code to get path/filename by handle:

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int GetWindowThreadProcessId(IntPtr handle, out uint processId);

    public static string GetProcessPath(IntPtr hwnd)
    {
        uint pid = 0;
        GetWindowThreadProcessId(hwnd, out pid);
        Process proc = Process.GetProcessById((int)pid);
        return proc.MainModule.FileName.ToString();
    }

it works perfect in 32 bit but I get error in 64bit > "Only part of the ReadProcessMemory or WriteProcessMemory request was completed." the project is compiled as x86 (platform target x86).

How can I fix it?

~Thanks Ron

4条回答
趁早两清
2楼-- · 2019-05-03 20:43

It appears from your question that you've currently compiled your program as a 32-bit application. However, the process you're trying to query (assuming you're running on a 64-bit version of Windows) is undoubtedly a 64-bit one. That kind of thing isn't allowed. Although you can run 32-bit applications on 64-bit versions of Windows, they run under the dedicated Windows on Windows (WOW64) subsystem. That's why you're getting a Win32Exception claiming that "Only part of a ReadProcessMemory or WriteProcessMemory request was completed". I agree it's not the most descriptive of error messages if you don't already know how Windows manages 32-bit and 64-bit processes, but armed with this knowledge it makes at least a little more sense.

The solution is to compile your application as a 64-bit application (x64), or for "Any CPU". Everything should work as expected after that. If possible, I suggest using "Any CPU", which will allow the application to run in 32-bit mode on 32-bit OSes and 64-bit on 64-bit OSes. Which is really the ideal set of circumstances, assuming that:

  1. You've written your P/Invoke definitions correctly (i.e., using IntPtr where appropriate, instead of Integer).
  2. You're not relying on 3rd-party DLLs (to which you don't have the source code) that were compiled as 32-bit.
查看更多
叛逆
3楼-- · 2019-05-03 20:44

This thing is absolutely possible from a 32 bit application when checking a 64 bit process, though not as straightforward as it would be if it were a 64 bit compiled application:

    [Flags]
    enum ProcessAccessFlags : uint
    {
        All = 0x001F0FFF,
        Terminate = 0x00000001,
        CreateThread = 0x00000002,
        VMOperation = 0x00000008,
        VMRead = 0x00000010,
        VMWrite = 0x00000020,
        DupHandle = 0x00000040,
        SetInformation = 0x00000200,
        QueryInformation = 0x00000400,
        Synchronize = 0x00100000,
        ReadControl = 0x00020000,
        PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
    }

    [DllImport("kernel32.dll")]
    private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, int dwProcessId);

    [DllImport("kernel32.dll")]
    private static extern bool QueryFullProcessImageName(IntPtr hprocess, int dwFlags, StringBuilder lpExeName, out int size);

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr hHandle);

    private static Process GetProcessByHandle(IntPtr hwnd)
    {
        try
        {
            uint processID;
            GetWindowThreadProcessId(hwnd, out processID);
            return Process.GetProcessById((int)processID);
        }
        catch { return null; }
    }

    private static string GetExecutablePathAboveVista(int ProcessId)
    {
        var buffer = new StringBuilder(1024);
        IntPtr hprocess = OpenProcess(ProcessAccessFlags.PROCESS_QUERY_LIMITED_INFORMATION,
                                      false, ProcessId);
        if (hprocess != IntPtr.Zero)
        {
            try
            {
                int size = buffer.Capacity;
                if (QueryFullProcessImageName(hprocess, 0, buffer, out size))
                {
                    return buffer.ToString();
                }
            }
            finally
            {
                CloseHandle(hprocess);
            }
        }
        return null;
    }

    private static string GetWindowPath(IntPtr hwind)
    {
        try
        {
            Process currentProcess = GetProcessByHandle(hwind);

            if (Environment.OSVersion.Version.Major >= 6)
            {
                string newMethReturn = GetExecutablePathAboveVista(currentProcess.Id);
                if (!string.IsNullOrWhiteSpace(newMethReturn))
                    return newMethReturn;
            }


            if (currentProcess != null)

                return currentProcess.MainModule.FileName;
            else
                return null;
        }
        catch
        { return null; }
    }
查看更多
我想做一个坏孩纸
4楼-- · 2019-05-03 20:46

I'm not aware of a known problem with this, it smells environmental. The error is very low level, probably the wow64 emulation layer. I can only suggest you punt and use a different way to get the same info. You can use WMI, Win32_Process class. Run a select query on ProcessId, the ExecutablePath property gives you what you are looking for. Use the WMI Code Creator utility to experiment, it auto-generates the C# code you need. The odds are however not zero that this will fail the same way.

查看更多
小情绪 Triste *
5楼-- · 2019-05-03 20:57

It works well for me on a 64x machine. The only changes to the code are checking the values, as:

if (hwnd != IntPtr.Zero)
{
    if (pid != 0) 
    {
        var p = Process.GetProcessById((int) pid)
        if (p != null)
        {
            //...
        }
    }
}
查看更多
登录 后发表回答