关于C#:如何获取与打开的HANDLE相关的名称

关于C#:如何获取与打开的HANDLE相关的名称

How to get name associated with open HANDLE

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


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

  • 它不应用于网络共享,因为MountPointManager可能会挂起很长时间。
  • 它使用未记录的API(IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH),我不太喜欢
  • 它不支持创建虚拟COM端口的USB设备(我在项目中需要)
  • 我还研究了其他方法,例如GetFileInformationByHandleEx()GetFinalPathNameByHandle(),但是这些方法无用,因为它们仅返回路径文件名,而没有驱动器。另外,GetFinalPathNameByHandle()也具有挂起的bug。

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

  • 它仅适用于真实文件
  • 文件大小不能为零字节
  • 不支持目录,网络和COM端口
  • 代码很笨拙
  • 所以我写了自己的代码。我在Win XP和Win 7、8和10上对其进行了测试。它可以完美运行。

    注意:您不需要任何其他LIB文件即可编译此代码!

    CPP文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    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.webp"          (Harddisk File)
    //"\\Device\\Harddisk1\\DP(1)0-0+6\\foto.webp"                  (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\
    amedPipe\\Pipename"                             (named pipe)

    //"\\BaseNamedObjects\\Objectname"                           (named mutex, named event, named semaphore)
    //"\
    EGISTRY\\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.webp"          ->"E:\\Temp\\transparent.webp"
    //"\\Device\\Harddisk1\\DP(1)0-0+6\\foto.webp"                  ->"I:\\foto.webp"
    //"\\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;
    }

    头文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #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上有一种正确的方法(尽管未记录),该方法也可用于目录-与Windows Vista及更高版本上的GetFinalPathNameByHandle使用的方法相同。

    以下是简化的声明。其中一些已经在WInternl.hMountMgr.h中,但是我还是把它们放在这里:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #include"stdafx.h"
    #include <Windows.h>
    #include

    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];
    };

    这是核心功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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); }
    }

    这是用法示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    int _tmain(int argc, _TCHAR* argv[])
    {
        HANDLE hFile = CreateFile(_T("\\\\\\\\.\\\\C:\\\\Windows\\\
    otepad.exe"
    ),
            0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
        assert(hFile != NULL && hFile != INVALID_HANDLE_VALUE);
        __try
        {
            wprintf(L"%s\
    "
    , GetFilePath(hFile));
            //  Prints:
            //  C:\\Windows\
    otepad.exe

        }
        __finally { CloseHandle(hFile); }
        return 0;
    }

    edit感谢您对此的评论,仅适用于Vista或Server 2008。我在页面上错过了。猜猜我应该已经读了整篇文章;)

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

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

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

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

    干杯,

    泰勒


    FWIW,这是Prakash在Python中使用奇妙的ctypes建议的MSDN文章中的相同解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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()

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

    对于Windows XP,我更喜欢Mehrdad的解决方案。

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


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

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

    -=-詹姆斯。


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

    因此,您也许可以使用其他答案在Win32上执行此操作,但是如果您需要将应用程序移植到Unix环境中,那将会很不幸。我对您的建议是,如果可能的话,请重构程序,这样您就不需要操作系统就能维护与文件名连接的开放资源。


    推荐阅读