如何获得与打开HANDLE相关联的名称

时间:2008-09-15 17:58:21

标签: c windows winapi

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

7 个答案:

答案 0 :(得分:15)

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

  1. 它不应该用于网络共享,因为MountPointManager可能会挂起很长时间。
  2. 它使用未记录的API(IOCTL_MOUNTMGR_QUERY_DOS_VOLUME_PATH)我不太喜欢
  3. 它不支持创建虚拟COM端口的USB设备(我需要在我的项目中使用)
  4. 我还研究过其他方法,例如GetFileInformationByHandleEx()GetFinalPathNameByHandle(),但这些方法没用,因为它们只返回Path + Filename但没有驱动器。此外,GetFinalPathNameByHandle()也有悬挂错误。

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

    1. 仅适用于真实文件
    2. 文件大小不得为零字节
    3. 不支持目录,网络和COM端口
    4. 代码笨拙
    5. 所以我写了自己的代码。我在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;
      }
      

      HEADER FILE:

      #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);   
      

答案 1 :(得分:13)

在Windows XP 上有一个正确的(虽然没有记录)方法,这也适用于目录 - 相同的方法GetFinalPathNameByHandle在Windows Vista及更高版本上使用。

以下是加入的声明。其中一些已经在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;
}

答案 2 :(得分:4)

编辑感谢您对Vista或Server 2008的评论。我在页面中错过了。我猜我应该阅读整个文章;)

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

您可能希望执行以下操作:

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

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

干杯,

泰勒

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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

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

- = - 詹姆斯。

答案 6 :(得分:0)

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

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

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

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

找到Mehrdad的解决方案