如果我只有一个窗口句柄(hWnd),如何获取GetModuleFileName()?

11

我正在尝试获取在C# 2.0应用程序之外的窗口可执行文件的名称。我的应用程序当前使用来自"user32.dll"的GetForegroundWindow()调用获取窗口句柄(hWnd)。

通过我的研究,我认为我想要使用GetModuleFileNameEx()函数(来自PSAPI)来获取名称,但是GetModuleFileNameEx()需要一个进程句柄,而不是窗口句柄。

是否有可能从窗口句柄获取进程句柄?(我需要首先获取窗口的线程句柄吗?)

编辑了第一句话以使我正在尝试做什么更清晰。

更新! 这是我找到适用于我的C#代码。唯一的警告是偶尔会返回驱动器字母为“?”而不是实际的驱动器字母(如“C”)的文件/路径。 - 尚未弄清原因。

[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(UInt32 dwDesiredAccess, Int32 bInheritHandle, UInt32 dwProcessId);

[DllImport("psapi.dll")]
static extern uint GetModuleFileNameEx(IntPtr hProcess, IntPtr hModule, [Out] StringBuilder lpBaseName, [In] [MarshalAs(UnmanagedType.U4)] int nSize);

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool CloseHandle(IntPtr hObject);

private string GetWindowModuleFileName(IntPtr hWnd)
{
    uint processId = 0;
    const int nChars = 1024;
    StringBuilder filename = new StringBuilder(nChars);
    GetWindowThreadProcessId(hWnd, out processId);
    IntPtr hProcess = OpenProcess(1040, 0, processId);
    GetModuleFileNameEx(hProcess,IntPtr.Zero,filename,nChars);
    CloseHandle(hProcess);
    return (filename.ToString());
}

1
对我来说运行得还好。在OpenProcess()之后需要加上if ((int)hProcess != 0)。 - TByte
5个回答

8

我已经为同一个问题苦苦挣扎了一个小时,使用GetModuleFileNameEx替换了第一个字母?。 最终使用System.Diagnostics.Process类得出了这个解决方案。

[DllImport("user32.dll")]
public static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

void GetProcessPathFromWindowHandle(IntPtr hwnd)
{
   uint pid = 0;
   Win32.GetWindowThreadProcessId(hwnd, out pid);
   Process p = Process.GetProcessById((int)pid);
   return p.MainModule.FileName;
}

2
我认为这是迄今为止在这里提供的最佳解决方案。没有奇怪的字符,最重要的是不依赖于不一致的GetModuleFileNameEx函数,在Windows 7上该函数位于另一个DLL中(不是psapi而是kernel32)。在这种情况下,使用.Net类绝对是首选。完美地工作。 - Axonn
2
我意识到这个线程已经过时了,但是对于这个建议的解决方案有一个小备注。如果您尝试从32位进程(或反之)获取64位进程的文件名,这种方法将不起作用。 - Geoffrey
不适用于 Windows 10 上的 Microsoft Store 应用程序,调用不同应用程序窗口时会不断返回“C:\WINDOWS\system32\ApplicationFrameHost.exe”。 - fuweichin

6

嘿,欢迎来到 Stack Overflow - 我一直是你的忠实读者。 - 1800 INFORMATION

2

如果您正在运行64位Windows平台,则可能需要使用QueryFullProcessImageName。这将返回用户样式路径,而GetProcessImageFileName返回系统样式路径,需要使用NtQuerySymbolicLinkObject或ZwQuerySymbolicLinkObject进行转换。

一个巨大的示例函数-建议将其拆分为可重用的部分。

typedef DWORD (__stdcall *PfnQueryFullProcessImageName)(HANDLE hProcess, DWORD dwFlags, LPTSTR lpImageFileName, PDWORD nSize);
typedef DWORD (__stdcall *PfnGetModuleFileNameEx)(HANDLE hProcess, HMODULE hModule, LPTSTR lpImageFileName, DWORD nSize);

std::wstring GetExeName( HWND hWnd ){
// Convert from Window to Process ID
DWORD dwProcessID = 0;
::GetWindowThreadProcessId(hWnd, &dwProcessID);

// Get a handle to the process from the Process ID
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessID);

// Get the process name
if (NULL != hProcess) {
    TCHAR szEXEName[MAX_PATH*2] = {L'\0'};
    DWORD nExeName = sizeof(szEXEName)/sizeof(TCHAR);

    //  the QueryFullProcessImageNameW does not exist on W2K
    HINSTANCE hKernal32dll = LoadLibrary(L"kernel32.dll");
    PfnQueryFullProcessImageName pfnQueryFullProcessImageName = NULL;
    if(hKernal32dll != NULL) {
        pfnQueryFullProcessImageName = (PfnQueryFullProcessImageName)GetProcAddress(hKernal32dll, "QueryFullProcessImageNameW");
        if (pfnQueryFullProcessImageName != NULL) 
            pfnQueryFullProcessImageName(hProcess, 0, szEXEName, &nExeName);
        ::FreeLibrary(hKernal32dll);
    } 

    // The following was not working from 32 querying of 64 bit processes
    // Use as backup for when function above is not available 
    if( pfnQueryFullProcessImageName == NULL ){ 
        HINSTANCE hPsapidll = LoadLibrary(L"Psapi.dll");
        PfnGetModuleFileNameEx pfnGetModuleFileNameEx = (PfnGetModuleFileNameEx)GetProcAddress(hPsapidll, "GetModuleFileNameExW");
        if( pfnGetModuleFileNameEx != NULL )    
            pfnGetModuleFileNameEx(hProcess, NULL, szEXEName, sizeof(szEXEName)/sizeof(TCHAR));
        ::FreeLibrary(hPsapidll);
    }

    ::CloseHandle(hProcess);

    return( szEXEName );
} 
return std::wstring();
}

1
你到底想做什么?你可以使用GetWindowThreadProcessId()获取创建窗口的进程ID,然后使用OpenProcess()获取进程句柄。但这种方法似乎很笨拙,我觉得有更优雅的方式来实现你想要做的事情。

也许这对你来说似乎很丑陋,但是一个声望值为1的人在你之前只提供了完全相同的答案,而我不认为他的声望会很快保持在1。 - Windows programmer
是的,这对我来说也感觉很笨拙。我正在尝试跟踪哪个窗口具有焦点。自然地,窗口句柄和标题栏名称会发生变化,但应用程序 exe 名称不会改变。 - Pretzel
好的,但无论如何,Larry Osterman给出了相同的答案,所以它可能比所有其他替代方案都更少。而且他的声誉不再是1了。 - Windows programmer
Larry Osterman在Windows编程领域的声誉可以和Raymond Chen媲美。 - 1800 INFORMATION
Larry Osterman的回答刚被接受,所以他的声望现在是46。他在SO之前的生涯不计入其中。 - Windows programmer

0

尝试使用以下代码获取可执行文件的文件名:

C#:

string file = System.Windows.Forms.Application.ExecutablePath;

制造


1
这是一个有点老的问题了,但当时我正在尝试根据进程ID获取另一个进程的EXE名称。(不是正在运行的那个。)因此,在这种情况下,这不是有用的答案。 - Pretzel

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