在 Win32 中获取与打开的 HANDLE 关联的文件名的最简单方法是什么?
7 回答
我尝试了 Mehrdad 在这里发布的代码。它有效,但有局限性:
- 它不应该用于网络共享,因为 MountPointManager 可能会挂起很长时间。
- 它使用未记录的 API (IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH) 我不太喜欢
- 它不支持创建虚拟 COM 端口的 USB 设备(我的项目中需要它)
我还研究了其他方法,例如GetFileInformationByHandleEx()
and GetFinalPathNameByHandle()
,但这些方法没有用,因为它们只返回 Path + Filename 但没有驱动器。另外GetFinalPathNameByHandle()
还有挂机bug。
MSDN 中的GetMappedFileName()
方法(由 Max 在此处发布)也非常有限:
- 它仅适用于真实文件
- 文件大小不得为零字节
- 不支持目录、网络和 COM 端口
- 代码很笨拙
所以我写了自己的代码。我在 Win XP 和 Win 7、8 和 10 上对其进行了测试。它运行良好。
注意:您不需要任何额外的 LIB 文件来编译此代码!
CPP文件:
t_NtQueryObject NtQueryObject()
{
static t_NtQueryObject f_NtQueryObject = NULL;
if (!f_NtQueryObject)
{
HMODULE h_NtDll = GetModuleHandle(L"Ntdll.dll"); // Ntdll is loaded into EVERY process!
f_NtQueryObject = (t_NtQueryObject)GetProcAddress(h_NtDll, "NtQueryObject");
}
return f_NtQueryObject;
}
// returns
// "\Device\HarddiskVolume3" (Harddisk Drive)
// "\Device\HarddiskVolume3\Temp" (Harddisk Directory)
// "\Device\HarddiskVolume3\Temp\transparent.jpeg" (Harddisk File)
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" (USB stick)
// "\Device\TrueCryptVolumeP\Data\Passwords.txt" (Truecrypt Volume)
// "\Device\Floppy0\Autoexec.bat" (Floppy disk)
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" (DVD drive)
// "\Device\Serial1" (real COM port)
// "\Device\USBSER000" (virtual COM port)
// "\Device\Mup\ComputerName\C$\Boot.ini" (network drive share, Windows 7)
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" (network drive share, Windwos XP)
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" (network folder share, Windwos XP)
// "\Device\Afd" (internet socket)
// "\Device\Console000F" (unique name for any Console handle)
// "\Device\NamedPipe\Pipename" (named pipe)
// "\BaseNamedObjects\Objectname" (named mutex, named event, named semaphore)
// "\REGISTRY\MACHINE\SOFTWARE\Classes\.txt" (HKEY_CLASSES_ROOT\.txt)
DWORD GetNtPathFromHandle(HANDLE h_File, CString* ps_NTPath)
{
if (h_File == 0 || h_File == INVALID_HANDLE_VALUE)
return ERROR_INVALID_HANDLE;
// NtQueryObject() returns STATUS_INVALID_HANDLE for Console handles
if (IsConsoleHandle(h_File))
{
ps_NTPath->Format(L"\\Device\\Console%04X", (DWORD)(DWORD_PTR)h_File);
return 0;
}
BYTE u8_Buffer[2000];
DWORD u32_ReqLength = 0;
UNICODE_STRING* pk_Info = &((OBJECT_NAME_INFORMATION*)u8_Buffer)->Name;
pk_Info->Buffer = 0;
pk_Info->Length = 0;
// IMPORTANT: The return value from NtQueryObject is bullshit! (driver bug?)
// - The function may return STATUS_NOT_SUPPORTED although it has successfully written to the buffer.
// - The function returns STATUS_SUCCESS although h_File == 0xFFFFFFFF
NtQueryObject()(h_File, ObjectNameInformation, u8_Buffer, sizeof(u8_Buffer), &u32_ReqLength);
// On error pk_Info->Buffer is NULL
if (!pk_Info->Buffer || !pk_Info->Length)
return ERROR_FILE_NOT_FOUND;
pk_Info->Buffer[pk_Info->Length /2] = 0; // Length in Bytes!
*ps_NTPath = pk_Info->Buffer;
return 0;
}
// converts
// "\Device\HarddiskVolume3" -> "E:"
// "\Device\HarddiskVolume3\Temp" -> "E:\Temp"
// "\Device\HarddiskVolume3\Temp\transparent.jpeg" -> "E:\Temp\transparent.jpeg"
// "\Device\Harddisk1\DP(1)0-0+6\foto.jpg" -> "I:\foto.jpg"
// "\Device\TrueCryptVolumeP\Data\Passwords.txt" -> "P:\Data\Passwords.txt"
// "\Device\Floppy0\Autoexec.bat" -> "A:\Autoexec.bat"
// "\Device\CdRom1\VIDEO_TS\VTS_01_0.VOB" -> "H:\VIDEO_TS\VTS_01_0.VOB"
// "\Device\Serial1" -> "COM1"
// "\Device\USBSER000" -> "COM4"
// "\Device\Mup\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\C$\Boot.ini" -> "\\ComputerName\C$\Boot.ini"
// "\Device\LanmanRedirector\ComputerName\Shares\Dance.m3u" -> "\\ComputerName\Shares\Dance.m3u"
// returns an error for any other device type
DWORD GetDosPathFromNtPath(const WCHAR* u16_NTPath, CString* ps_DosPath)
{
DWORD u32_Error;
if (wcsnicmp(u16_NTPath, L"\\Device\\Serial", 14) == 0 || // e.g. "Serial1"
wcsnicmp(u16_NTPath, L"\\Device\\UsbSer", 14) == 0) // e.g. "USBSER000"
{
HKEY h_Key;
if (u32_Error = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"Hardware\\DeviceMap\\SerialComm", 0, KEY_QUERY_VALUE, &h_Key))
return u32_Error;
WCHAR u16_ComPort[50];
DWORD u32_Type;
DWORD u32_Size = sizeof(u16_ComPort);
if (u32_Error = RegQueryValueEx(h_Key, u16_NTPath, 0, &u32_Type, (BYTE*)u16_ComPort, &u32_Size))
{
RegCloseKey(h_Key);
return ERROR_UNKNOWN_PORT;
}
*ps_DosPath = u16_ComPort;
RegCloseKey(h_Key);
return 0;
}
if (wcsnicmp(u16_NTPath, L"\\Device\\LanmanRedirector\\", 25) == 0) // Win XP
{
*ps_DosPath = L"\\\\";
*ps_DosPath += (u16_NTPath + 25);
return 0;
}
if (wcsnicmp(u16_NTPath, L"\\Device\\Mup\\", 12) == 0) // Win 7
{
*ps_DosPath = L"\\\\";
*ps_DosPath += (u16_NTPath + 12);
return 0;
}
WCHAR u16_Drives[300];
if (!GetLogicalDriveStrings(300, u16_Drives))
return GetLastError();
WCHAR* u16_Drv = u16_Drives;
while (u16_Drv[0])
{
WCHAR* u16_Next = u16_Drv +wcslen(u16_Drv) +1;
u16_Drv[2] = 0; // the backslash is not allowed for QueryDosDevice()
WCHAR u16_NtVolume[1000];
u16_NtVolume[0] = 0;
// may return multiple strings!
// returns very weird strings for network shares
if (!QueryDosDevice(u16_Drv, u16_NtVolume, sizeof(u16_NtVolume) /2))
return GetLastError();
int s32_Len = (int)wcslen(u16_NtVolume);
if (s32_Len > 0 && wcsnicmp(u16_NTPath, u16_NtVolume, s32_Len) == 0)
{
*ps_DosPath = u16_Drv;
*ps_DosPath += (u16_NTPath + s32_Len);
return 0;
}
u16_Drv = u16_Next;
}
return ERROR_BAD_PATHNAME;
}
头文件:
#pragma warning(disable: 4996) // wcsnicmp deprecated
#include <winternl.h>
// This makro assures that INVALID_HANDLE_VALUE (0xFFFFFFFF) returns FALSE
#define IsConsoleHandle(h) (((((ULONG_PTR)h) & 0x10000003) == 0x3) ? TRUE : FALSE)
enum OBJECT_INFORMATION_CLASS
{
ObjectBasicInformation,
ObjectNameInformation,
ObjectTypeInformation,
ObjectAllInformation,
ObjectDataInformation
};
struct OBJECT_NAME_INFORMATION
{
UNICODE_STRING Name; // defined in winternl.h
WCHAR NameBuffer;
};
typedef NTSTATUS (NTAPI* t_NtQueryObject)(HANDLE Handle, OBJECT_INFORMATION_CLASS Info, PVOID Buffer, ULONG BufferSize, PULONG ReturnLength);
在 Windows XP 上,有一种正确的(尽管没有记录)方法,它也适用于目录——与GetFinalPathNameByHandle在 Windows Vista 及更高版本上使用的方法相同。
这是 eneded 的声明。其中一些已经在里面WInternl.h
,MountMgr.h
但我还是把它们放在这里:
#include "stdafx.h"
#include <Windows.h>
#include <assert.h>
enum OBJECT_INFORMATION_CLASS { ObjectNameInformation = 1 };
enum FILE_INFORMATION_CLASS { FileNameInformation = 9 };
struct FILE_NAME_INFORMATION { ULONG FileNameLength; WCHAR FileName[1]; };
struct IO_STATUS_BLOCK { PVOID Dummy; ULONG_PTR Information; };
struct UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; };
struct MOUNTMGR_TARGET_NAME { USHORT DeviceNameLength; WCHAR DeviceName[1]; };
struct MOUNTMGR_VOLUME_PATHS { ULONG MultiSzLength; WCHAR MultiSz[1]; };
extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryObject(IN HANDLE Handle OPTIONAL,
IN OBJECT_INFORMATION_CLASS ObjectInformationClass,
OUT PVOID ObjectInformation OPTIONAL, IN ULONG ObjectInformationLength,
OUT PULONG ReturnLength OPTIONAL);
extern "C" NTSYSAPI NTSTATUS NTAPI NtQueryInformationFile(IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID FileInformation,
IN ULONG Length, IN FILE_INFORMATION_CLASS FileInformationClass);
#define MOUNTMGRCONTROLTYPE ((ULONG) 'm')
#define IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH \
CTL_CODE(MOUNTMGRCONTROLTYPE, 12, METHOD_BUFFERED, FILE_ANY_ACCESS)
union ANY_BUFFER {
MOUNTMGR_TARGET_NAME TargetName;
MOUNTMGR_VOLUME_PATHS TargetPaths;
FILE_NAME_INFORMATION NameInfo;
UNICODE_STRING UnicodeString;
WCHAR Buffer[USHRT_MAX];
};
这是核心功能:
LPWSTR GetFilePath(HANDLE hFile)
{
static ANY_BUFFER nameFull, nameRel, nameMnt;
ULONG returnedLength; IO_STATUS_BLOCK iosb; NTSTATUS status;
status = NtQueryObject(hFile, ObjectNameInformation,
nameFull.Buffer, sizeof(nameFull.Buffer), &returnedLength);
assert(status == 0);
status = NtQueryInformationFile(hFile, &iosb, nameRel.Buffer,
sizeof(nameRel.Buffer), FileNameInformation);
assert(status == 0);
//I'm not sure how this works with network paths...
assert(nameFull.UnicodeString.Length >= nameRel.NameInfo.FileNameLength);
nameMnt.TargetName.DeviceNameLength = (USHORT)(
nameFull.UnicodeString.Length - nameRel.NameInfo.FileNameLength);
wcsncpy(nameMnt.TargetName.DeviceName, nameFull.UnicodeString.Buffer,
nameMnt.TargetName.DeviceNameLength / sizeof(WCHAR));
HANDLE hMountPointMgr = CreateFile(_T("\\\\.\\MountPointManager"),
0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, 0, NULL);
__try
{
DWORD bytesReturned;
BOOL success = DeviceIoControl(hMountPointMgr,
IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH, &nameMnt,
sizeof(nameMnt), &nameMnt, sizeof(nameMnt),
&bytesReturned, NULL);
assert(success && nameMnt.TargetPaths.MultiSzLength > 0);
wcsncat(nameMnt.TargetPaths.MultiSz, nameRel.NameInfo.FileName,
nameRel.NameInfo.FileNameLength / sizeof(WCHAR));
return nameMnt.TargetPaths.MultiSz;
}
__finally { CloseHandle(hMountPointMgr); }
}
这是一个示例用法:
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE hFile = CreateFile(_T("\\\\.\\C:\\Windows\\Notepad.exe"),
0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
assert(hFile != NULL && hFile != INVALID_HANDLE_VALUE);
__try
{
wprintf(L"%s\n", GetFilePath(hFile));
// Prints:
// C:\Windows\notepad.exe
}
__finally { CloseHandle(hFile); }
return 0;
}
编辑感谢您对此仅是 Vista 或 Server 2008 的评论。我在页面上错过了。我想我应该阅读整篇文章;)
看起来您可以使用GetFileInformationByHandleEx()来获取此信息。
您可能想要执行以下操作:
GetFileInformationByHandleEx( fileHandle, FILE_NAME_INFO, lpFileInformation, sizeof(FILE_NAME_INFO));
仔细检查 MSDN 页面,确保我没有误导你太严重:)
干杯,
泰勒
FWIW,这是 Prakash 在 Python 中使用美妙的ctypes建议的 MSDN 文章中的相同解决方案:
from ctypes import *
# get handle to c:\boot.ini to test
handle = windll.kernel32.CreateFileA("c:\\boot.ini", 0x80000000, 3, 0, 3, 0x80, 0)
hfilemap = windll.kernel32.CreateFileMappingA(handle, 0, 2, 0, 1, 0)
pmem = windll.kernel32.MapViewOfFile(hfilemap, 4, 0, 0, 1)
name = create_string_buffer(1024)
windll.psapi.GetMappedFileNameA(windll.kernel32.GetCurrentProcess(), pmem, name, 1024)
print "The name for the handle 0x%08x is %s" % (handle, name.value)
# convert device name to drive letter
buf = create_string_buffer(512)
size = windll.kernel32.GetLogicalDriveStringsA(511, buf)
names = buf.raw[0:size-1].split("\0")
for drive in names:
windll.kernel32.QueryDosDeviceA(drive[0:2], buf, 512)
if name.value.startswith(buf.value):
print "%s%s" % (drive[0:2], name.value[len(buf.value):])
break
对于 Windows Vista 及更高版本,我更喜欢使用 GetFinalPathNameByHandle()
char buf[MAX_PATH];
GetFinalPathNameByHandleA(fileHandle, buf, sizeof(buf), VOLUME_NAME_DOS)
对于 Windows XP,我更喜欢Mehrdad 的解决方案。
因此,我通过 GetProcAddress() 动态加载 GetFinalPathNameByHandle(),如果失败(因为它是 Windows XP),我会使用 Mehrdad 的 NtQueryObject() 解决方案
如果您需要在 Win32 pre-Vista 或 Server 2008 上执行此操作,请查看该GetMappedFileName(...)
功能,这是 Win32 中保存最好的秘密之一。只需一点C/C++-
fu,您就可以对相关文件的一小部分进行内存映射,然后将该句柄传递给该函数。
此外,在 Win32 上,您不能真正删除打开的文件(另一个答案中提到的打开/取消链接问题) - 您可以在关闭时将其标记为删除,但它仍然会挂起,直到其最后打开的句柄关闭。不知道在这种情况下映射(通过mmap(...)
)文件是否会有所帮助,因为它必须指向物理文件......
-=- 詹姆斯。
在 Unix 上,没有真正可靠的方法来做到这一点。在具有传统 unix 文件系统的 unix 中,您可以打开文件然后取消链接(从目录中删除其条目)并使用它,此时名称不会存储在任何地方。此外,因为一个文件可能有多个到文件系统的硬链接,每个名称都是等效的,所以一旦你得到了打开的句柄,就不清楚你应该映射回哪个文件名。
因此,您可以使用其他答案在 Win32 上执行此操作,但如果您需要将应用程序移植到 unix 环境,那么您将不走运。我对您的建议是,如果可能的话,重构您的程序,这样您就不需要操作系统来维护与文件名连接的开放资源。