根据句柄获得文件名

发布于 2021-09-09


我们可以通过GetFinalPathNameByHandle这个 api 直接从一个句柄中获得文件的路径。使用代码如下:

    WCHAR path_name[MAX_PATH] = {0};
    BOOL result =
        GetFinalPathNameByHandle(test_file_handle, path_name, MAX_PATH,
                                 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
    if (result) {
      std::cout << "FILE_NAME_NORMALIZED | VOLUME_NAME_DOS" << std::endl;
      std::wcout << path_name << std::endl << std::endl;
    }

    result = GetFinalPathNameByHandle(test_file_handle, path_name, MAX_PATH,
                                      FILE_NAME_NORMALIZED | VOLUME_NAME_NT);
    if (result) {
      std::cout << "FILE_NAME_NORMALIZED | VOLUME_NAME_NT" << std::endl;
      std::wcout << path_name << std::endl << std::endl;
    }

    result = GetFinalPathNameByHandle(test_file_handle, path_name, MAX_PATH,
                                      FILE_NAME_NORMALIZED | VOLUME_NAME_NONE);
    if (result) {
      std::cout << "FILE_NAME_NORMALIZED | VOLUME_NAME_NONE" << std::endl;
      std::wcout << path_name << std::endl << std::endl;
    }

输出结果为:
FILE_NAME_NORMALIZED | VOLUME_NAME_DOS
\\?\C:\Users\xxx\source\repos\ConsoleApplication10\ConsoleApplication10\test.txt

FILE_NAME_NORMALIZED | VOLUME_NAME_NT
\Device\HarddiskVolume5\Users\xxx\source\repos\ConsoleApplication10\ConsoleApplication10\test.txt

FILE_NAME_NORMALIZED | VOLUME_NAME_NONE
\Users\xxx\source\repos\ConsoleApplication10\ConsoleApplication10\test.txt

获得的路径前面添加了一些前缀,这跟我们正常的路径还是有一些差别。

但是这个 api 最低只支持vista系统。如果要在 XP 等低版本 Windows 系统上获得文件名,可以使用其他办法。

BOOL GetFileNameFromHandle(HANDLE hFile) {
  BOOL bSuccess = FALSE;
  TCHAR pszFilename[MAX_PATH + 1] = {0};
  HANDLE hFileMap;

  // Create a file mapping object.
  hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 1, NULL);

  if (hFileMap) {
    // Create a file mapping to get the file name.
    void *pMem = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 1);

    if (pMem) {
      if (GetMappedFileName(GetCurrentProcess(), pMem, pszFilename, MAX_PATH)) {

        // Translate path with device name to drive letters.
        TCHAR szTemp[BUFSIZE];
        szTemp[0] = '\0';

        if (GetLogicalDriveStrings(BUFSIZE - 1, szTemp)) {
          TCHAR szName[MAX_PATH];
          TCHAR szDrive[3] = TEXT(" :");
          BOOL bFound = FALSE;
          TCHAR *p = szTemp;

          do {
            // Copy the drive letter to the template string
            *szDrive = *p;

            // Look up each device name
            if (QueryDosDevice(szDrive, szName, MAX_PATH)) {
              size_t uNameLen = wcslen(szName);

              if (uNameLen < MAX_PATH) {
                bFound = _wcsnicmp(pszFilename, szName, uNameLen) == 0 &&
                         *(pszFilename + uNameLen) == L'\\';

                if (bFound) {
                  // Reconstruct pszFilename using szTempFile
                  // Replace device path with DOS path
                  TCHAR szTempFile[MAX_PATH];
                  StringCchPrintf(szTempFile, MAX_PATH, TEXT("%s%s"), szDrive,
                                  pszFilename + uNameLen);
                  StringCchCopyN(pszFilename, MAX_PATH + 1, szTempFile,
                                 wcslen(szTempFile));
                }
              }
            }

            // Go to the next NULL character.
            while (*p++)
              ;
          } while (!bFound && *p); // end of string
        }
      }
      bSuccess = TRUE;
      UnmapViewOfFile(pMem);
    }

    CloseHandle(hFileMap);
  }
  std::wcout << L"File name is " << pszFilename << std::endl;
  return (bSuccess);
}

输出结果:
C:\Users\xxx\source\repos\ConsoleApplication10\ConsoleApplication10\test.txt

明显繁琐很多。

如果是dll或者模块的句柄,使用GetModuleFileName很简单

参考

Obtaining a File Name From a File Handle GetFinalPathNameByHandle