C#如何通过句柄(hWnd)获取路径/文件名(32位和64位)

5
我使用以下代码通过句柄获取路径/文件名:
    [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();
    }

在32位系统上运行良好,但在64位系统上出现错误:“只有部分ReadProcessMemory或WriteProcessMemory请求已完成。”项目编译为x86(平台目标x86)。

我该如何解决?

~谢谢Ron


哼...错误在哪一行? - user541686
返回 proc.MainModule.FileName.ToString(); - Ron
尝试以管理员身份运行您的程序。 - Darin Dimitrov
@Darin Dimitrov,我已经以管理员身份运行它了。这不是问题。@Cody Gray,我不知道。我没有构建这个函数,但即使我将其更改为IntPtr,它也无法工作。 [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetWindowThreadProcessId(IntPtr handle, out IntPtr processId);public static string GetProcessPath(IntPtr hwnd) { IntPtr pid = IntPtr.Zero; GetWindowThreadProcessId(hwnd, out pid); Process proc = Process.GetProcessById(pid.ToInt32()); return proc.MainModule.FileName.ToString(); } - Ron
4个回答

3
从您的问题中可以看出,您目前将程序编译为32位应用程序。然而,您正在尝试查询的进程(假设您正在运行64位的Windows版本)无疑是64位的不允许这种情况发生。虽然您可以在64位Windows版本上运行32位应用程序,但它们在专用的Windows on Windows (WOW64)子系统下运行。这就是为什么会收到“只完成了ReadProcessMemory或WriteProcessMemory请求的部分内容”的Win32Exception错误信息。如果您不知道Windows如何管理32位和64位进程,我同意这并不是最具描述性的错误消息,但掌握了这些知识后,它至少更加合理。
解决方案是将应用程序编译为64位应用程序(x64),或者选择"任何CPU(Any CPU)"。之后一切都应该正常工作。如果可能的话,建议使用"任何CPU",这将允许应用程序在32位操作系统上以32位模式运行,在64位操作系统上以64位模式运行。这真的是理想的情况,假设:
  1. 您已经正确编写了P/Invoke定义(即在适当的情况下使用IntPtr,而不是Integer)。
  2. 您没有依赖于第三方DLL(其源代码不可用),这些DLL编译为32位。

我认为这不是问题所在。我刚刚使用这段代码编写了一个WinForms示例应用程序,并针对x86进行了编译,在我的Windows 7 x64上完美地工作。 - Darin Dimitrov
@Cody Gray,我不依赖第三方DLL,但我依赖于为32位编写的第三方类,因此我无法使用“任何CPU”选项。你有其他解决方案吗? - Ron
1
@Ron:那个第三方类是由卫星DLL提供的,还是你放置在项目中的代码?如果你有源代码,重新编译成64位也很简单。除非它进行了大量的P/Invoke(即使是这样,这些问题也是可以解决的),否则编译为“任何CPU”几乎不可能出现任何兼容性问题。在.NET中编写的普通托管代码非常可移植。但是,我不知道其他的解决方案。 - Cody Gray
@Cody Gray,抱歉,我没有注意到。我正在依赖第三方DLL > Skybound.VisualStyles.dll和AMS.Profile.dll。无论如何,感谢您的尝试。 - Ron
@Cody Gray,这是对之前评论的编辑。我不能编辑因为超过了5分钟。我删除了Skybound.VisualStyles dll并编译为“Any CPU”,它可以工作了。谢谢。 - Ron
显示剩余7条评论

1

在我的64位机器上运行良好。代码的唯一更改是检查值,如下:

if (hwnd != IntPtr.Zero)
{
    if (pid != 0) 
    {
        var p = Process.GetProcessById((int) pid)
        if (p != null)
        {
            //...
        }
    }
}

1

我不知道这个问题是否已经被确认,但它似乎是环境相关的。这个错误非常低级,可能是wow64仿真层引起的。我只能建议您放弃并使用另一种获取相同信息的方法。您可以使用WMI,Win32_Process类。在ProcessId上运行选择查询,ExecutablePath属性会给您所需的内容。使用WMI Code Creator实用程序进行实验,它会自动生成您需要的C#代码。然而,这种方法失败的可能性并不为零。


WMI是一种密集的查找,应尽可能避免使用。 - Wobbles

1
这件事情对于一个32位应用程序来说,在检查64位进程时绝对是可能的,虽然不像64位编译的应用程序那样直接:
    [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; }
    }

虽然晚了三年,但还是想说谢谢。我一直在苦苦寻找一种优雅地停止我正在包装的进程的方法,这证明是拼图中的最后一块,因为我需要一种区分具有相同标题但不可执行路径的两个窗口的方法。 - Dealman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接