如何获取与打开的句柄关联的名称
在Win32下打开HANDLE的文件名最简单的方法是什么?
Windows XP上有一个正确的(虽然没有logging)的方法, 它也可以在目录中使用 – 在Windows Vista和更高版本上, GetFinalPathNameByHandle使用相同的方法。
这是支持的声明。 其中一些已经在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]; };
核心function如下:
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; }
我在这里尝试了Mehrdad发布的代码。 它工作,但有局限性:
- 它不应该用于networking共享,因为MountPointManager可能会挂起很长时间。
- 它使用未公开的API(IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH)我不太喜欢那个
- 它不支持创build虚拟COM端口的USB设备(我需要在我的项目中)
我还研究了其他方法,如GetFileInformationByHandleEx()
和GetFinalPathNameByHandle()
,但是这些都没用,因为它们只返回Path + Filename,但没有驱动器。 另外GetFinalPathNameByHandle()
也有挂起的bug。
MSDN中的GetMappedFileName()
方法(由Max在此处发布)也非常有限:
- 它只适用于真实的文件
- 文件大小不能是零字节
- 不支持目录,networking和COM端口
- 代码笨拙
所以我写了自己的代码。 我在Win XP和Win 7,8和10上进行了testing。它完美地工作。
注意:您不需要任何额外的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 || // eg "Serial1" wcsnicmp(u16_NTPath, L"\\Device\\UsbSer", 14) == 0) // eg "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);
编辑感谢您对Vista或Server 2008的评论。 我错过了在页面中。 猜猜我应该读完整篇文章;)
看起来你可以使用GetFileInformationByHandleEx()来获取这些信息。
你可能会想要做这样的事情:
GetFileInformationByHandleEx( fileHandle, FILE_NAME_INFO, lpFileInformation, sizeof(FILE_NAME_INFO));
仔细检查MSDN页面,以确保我没有误导你太糟糕:)
干杯,
泰勒
FWIW,这里是从Python使用美妙的ctypes的 Prakashbuild议MSDN文章相同的解决scheme:
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
如果您需要在Win32之前的Vista或Server 2008上执行此操作,请查看GetMappedFileName(...)
函数,这是Win32中保存最好的秘密之一。 用一点C/C++-
fu ,你可以映射一小部分有问题的文件,然后把这个句柄传给这个函数。
此外,在Win32上,你不能真正删除一个打开的文件(在另一个答案中提到的打开/解除链接问题) – 你可以将它标记为在closures时删除,但是它仍然会在最后打开的句柄closures之前四处停留。 不知道是否映射(通过mmap(...)
)在这种情况下文件将有所帮助,因为它必须指向一个物理文件…
– = – 詹姆斯。
在unixes没有真正的方法可靠地做到这一点。 在unix中使用传统的unix文件系统,你可以打开一个文件,然后取消它的链接(从目录中删除它的条目)并使用它,此时这个名字不会被存储在任何地方。 另外,由于一个文件可能有多个硬链接到文件系统中,每个名称都是相同的,所以一旦你打开了句柄,就不清楚应该映射回哪个文件名。
所以,你也许可以在Win32上使用其他答案来做到这一点,但是如果你需要将应用程序移植到unix环境中,那么你将会运气不佳。 我的build议是重构你的程序,如果可能的话,这样你就不需要操作系统能够维护一个开放的资源文件名连接。
对于Windows Vista和更高版本,我更喜欢使用GetFinalPathNameByHandle()
char buf[MAX_PATH]; GetFinalPathNameByHandleA(fileHandle, buf, sizeof(buf), VOLUME_NAME_DOS)
对于Windows XP,我更喜欢Mehrdad的解决scheme 。
所以我通过GetProcAddress()dynamic加载GetFinalPathNameByHandle(),如果失败(因为它是Windows XP),我用Mentrdad的解决scheme与NtQueryObject()