【问题标题】:How to get name associated with open HANDLE如何获取与打开 HANDLE 关联的名称
【发布时间】:2010-09-09 01:46:15
【问题描述】:

在 Win32 中获取与打开的 HANDLE 关联的文件名的最简单方法是什么?

【问题讨论】:

    标签: c windows winapi


    【解决方案1】:

    我尝试了 Mehrdad 在这里发布的代码。它有效,但有局限性:

    1. 不应将其用于网络共享,因为 MountPointManager 可能会挂起很长时间。
    2. 它使用未记录的 API (IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH) 我不太喜欢这样
    3. 它不支持创建虚拟 COM 端口的 USB 设备(我的项目中需要它)

    我还研究了其他方法,例如 GetFileInformationByHandleEx()GetFinalPathNameByHandle(),但这些方法没有用,因为它们只返回路径 + 文件名,但没有驱动器。另外GetFinalPathNameByHandle()也有挂机bug。

    MSDN 中的GetMappedFileName() 方法(由 Max 在此处发布)也非常有限:

    1. 它只适用于真实文件
    2. 文件大小不能为零字节
    3. 不支持目录、网络和 COM 端口
    4. 代码很笨拙

    所以我编写了自己的代码。我在 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);   
    

    【讨论】:

    • 首先,这段代码至少需要kernel32.lib和advapi32.lib才能编译。似乎 Elmue “想说”这段代码不需要额外的库(例如:ntdll.lib)来编译。 :) 其次,此代码并不适合所有网络重定向器(UNC 提供程序),例如,在 VirtualBox(主机)共享的情况下(Device\VBoxMiniRdr 作为 (DosDevices\)VBoxMiniRdrDN 符号链接的目标)。 :)
    • 好答案。这基本上是 Process Hacker 所做的(可能也是 Sysinternals 的 Process Explorer)并且适用于几乎所有句柄类型,而不仅仅是文件。
    • 嗨@Elmue,这工作非常出色!我想知道您是否有机会使用 Win8、8.1 或 10 进行测试?
    【解决方案2】:

    在 Windows XP 上有一种正确的(尽管没有记录)方法也适用于目录——与GetFinalPathNameByHandle 在 Windows Vista 及更高版本上使用的方法相同。

    这里是 eneded 声明。其中一些已经在WInternl.hMountMgr.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;
    }
    

    【讨论】:

    • UPPERCASE / lowercase == 比例太大
    • @pmg:我同意,但我没有选择名称……Windows API 已经是这样了,我只是在这里复制/粘贴了我需要的内容。
    • @ybungalobill:你真的需要 DDK 吗?我想我将 SDK 之外的所有内容都复制到了这里...
    • 虽然值得一提的是 GetProcAddress 会绕过对 DDK 的需求。
    • 此代码有效,但有一些限制。有关更多详细信息,请参阅我的帖子。
    【解决方案3】:

    edit 感谢 cmets 关于这仅适用于 Vista 或 Server 2008。我在页面上错过了。我想我应该阅读整篇文章;)

    看来您可以使用GetFileInformationByHandleEx() 来获取此信息。

    您可能想要执行以下操作:

    GetFileInformationByHandleEx( fileHandle, FILE_NAME_INFO, lpFileInformation, sizeof(FILE_NAME_INFO));
    

    仔细检查 MSDN 页面,确保我没有误导你太严重:)

    干杯,

    泰勒

    【讨论】:

    • 此解决方案的唯一问题是 GetFileInformationByHandleEx 需要 Windows Vista 或 Server 2008(或更高版本)。
    • 喜欢它的简单性,但我使用的是 XP :-(
    • 看起来很简单但是只返回路径+文件名,但是驱动器丢失了,所以用处不大。
    • FILE_NAME_INFO 是一个可变长度的数据结构,第一个字符只有一个条目。因此,我认为您希望将 dwBufferSize 计算为 sizeof(DWORD) + MAX_PATH * sizeof(WCHAR),而不是 sizeof(FILE_NAME_INFO)。
    • 我很抱歉我之前的评论,因为原来的帖子只要求一个文件名。因此,GetFileInformationByHandleEx() 不报告任何驱动器信息可能是可以的。无论如何,我更喜欢在 Windows Vista 及更高版本中使用 GetFinalPathNameByHandle()。
    【解决方案4】:

    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
    

    【讨论】:

    • 此代码有效,但有一些限制。在这里查看我的帖子
    【解决方案5】:

    对于 Windows Vista 及更高版本,我更喜欢使用 GetFinalPathNameByHandle()

    char buf[MAX_PATH];
    GetFinalPathNameByHandleA(fileHandle, buf, sizeof(buf), VOLUME_NAME_DOS)
    

    对于 Windows XP,我更喜欢solution by Mehrdad

    所以我通过 GetProcAddress() 动态加载 GetFinalPathNameByHandle(),如果失败(因为它是 Windows XP),我会使用 Mehrdad 的 NtQueryObject() 解决方案

    【讨论】:

      【解决方案6】:

      如果您需要在 Win32 pre-Vista 或 Server 2008 上执行此操作,请查看 GetMappedFileName(...) 函数,这是 Win32 中保存最完好的秘密之一。使用一点C/C++-fu,您可以内存映射相关文件的一小部分,然后将该句柄传递给此函数。

      此外,在 Win32 上,您不能真正删除打开的文件(另一个答案中提到的打开/取消链接问题) - 您可以在关闭时将其标记为删除,但它仍然会一直挂起,直到它的最后一个打开句柄是关闭。不知道在这种情况下映射(通过mmap(...))文件是否会有所帮助,因为它必须指向物理文件...

      -=- 詹姆斯。

      【讨论】:

        【解决方案7】:

        在 unix 上没有可靠的方法来做到这一点。在具有传统 unix 文件系统的 unix 中,您可以打开文件然后取消链接(从目录中删除其条目)并使用它,此时名称不会存储在任何地方。此外,由于一个文件可能有多个指向文件系统的硬链接,每个名称都是等效的,因此一旦您获得了打开句柄,就不清楚应该映射回哪个文件名。

        因此,您可以使用其他答案在 Win32 上执行此操作,但如果您需要将应用程序移植到 unix 环境,那么您将不走运。我对你的建议是,如果可能的话,重构你的程序,这样你就不需要操作系统来维护一个开放资源到文件名的连接。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-07-12
          • 1970-01-01
          • 2021-11-01
          • 2021-08-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多