GetFinalPathNameByHandle() result without prepended '\\?\'
Asked Answered
B

4

6

Here is my code snippet:

char existingTarget[MAX_PATH]; 
HANDLE hFile = CreateFile(linkPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
    GetFinalPathNameByHandle(hFile, existingTarget, MAX_PATH, FILE_NAME_OPENED);
    CloseHandle(hFile);
}

However, existingTarget is coming out to be \\?\C:\mydir\etc. How can I get it to return just C:\mydir\etc?

Note: I don't want to check for the string \\?\ and just memmove it, it's a bit too hackish of a solution for this program.

Buccaneer answered 15/7, 2015 at 19:8 Comment(1)
Some targets may not be valid or representable without the prefix.Anastos
C
6

how can I get it to return just C:\mydir\etc

You cannot. VOLUME_NAME_DOS and VOLUME_NAME_GUID always use that format, and is documented as such:

The string that is returned by this function uses the \\?\ syntax.

See the example results in the Community Additions section of the documentation.

Note: I don't want to check for the string \\?\ and just memmove it, its a bit too hackish of a solution for this program.

That is the easiest solution. Otherwise, you have to use other APIs to translate the returned path into a more human-readable path. Such as using the result of VOLUME_NAME_NT with QueryDosDevice(), or using the result of VOLUME_NAME_GUID with GetVolumePathNamesForVolumeName().

Candace answered 15/7, 2015 at 19:21 Comment(5)
Do you know if the \\?\ is always prepended? Or does it vary from system to system?Buccaneer
I addressed that in my answer.Candace
@amza: \\?\ is the prefix that changes CreateFile to point at the NT namespace rather than the NT namespace.Pentecostal
@BillyONeal that makes no sense. Read what you wrote.Candace
I don't see how you can use QueryDosDevice since it only use the lpDeviceName (ex: "C:"), not the full VOLUME_NAME_NT. A code example would be appreciatedHardhearted
I
1

I know, you don't want to check for \\?\ and remove it, but as Remy explains in his answer, that's the easiest solution. However, you should be careful, because local paths and network paths have different prefixes. As you know, a local path starts with \\?\, but a network path starts with \\?\UNC\ (as described here), for example:

\\?\UNC\My server\My share\Directory

As a result, based on your code, my solution to remove the prefixes is as follows:

char existingTarget[MAX_PATH];
HANDLE hFile = CreateFileA(linkPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
    DWORD ret = GetFinalPathNameByHandleA(hFile, existingTarget, MAX_PATH, FILE_NAME_OPENED);
    CloseHandle(hFile);

    // Check whether existingTarget is large enough to hold the final path.
    if (ret < MAX_PATH)
    {
        // The local path prefix is also a prefix of the network path prefix.
        // Therefore, look for the network path prefix first.
        // Please note that backslashes have to be escaped.
        std::string targetPath(existingTarget);
        if (targetPath.substr(0, 8).compare("\\\\?\\UNC\\") == 0)
        {
            // In case of a network path, replace `\\?\UNC\` with `\\`.
            targetPath = "\\" + targetPath.substr(7);
        }
        else if (targetPath.substr(0, 4).compare("\\\\?\\") == 0)
        {
            // In case of a local path, crop `\\?\`.
            targetPath = targetPath.substr(4);
        }
    }
}

If needed, you can still use memmove() to copy targetPath into another variable.

Iconoduly answered 17/10, 2018 at 18:19 Comment(0)
R
-1

There's a few other approaches if you don't want to do parsing stuff:

  • std::filesystem can handle it (at least with MSVC, didn't check GCC on MinGW or Cygwin), if you don't mind having an std::string:

    #include <filesystem>
    #include <string>
    ...
    std::string cleanerPath = std::filesystem::canonical(existingTarget).string();
    // use weakly_canonical instead if the file might not exist.
    
  • If you pass VOLUME_NAME_NONE to GetFinalPathNameByHandle it will strip that prefix and the drive letter, and it'll end up giving you a driveless path starting with \, which may or may not be useful depending on the situation, however...

  • If you pass VOLUME_NAME_NONE but you also know that the file is on the same drive as the current working directory, both std::filesystem and GetFullPathName can re-complete the path for you:

    ...
    // result will not include drive letter component
    GetFinalPathNameByHandle(hFile, existingTarget, MAX_PATH, 
                             FILE_NAME_OPENED | VOLUME_NAME_NONE);
    ...
    {
      // with std::filesystem into an std::string: 
      std::string cleanerPath = std::filesystem::absolute(existingTarget).string();
    }
    ...
    {
      // or with win api: 
      char cleanerPath[MAX_PATH];
      GetFullPathNameA(existingTarget, sizeof(cleanerPath), cleanerPath, nullptr);
    }
    
  • You could also use GetFileInformationByHandleEx instead of GetFinalPathNameByHandle, at the expense of a weirder API (and there's no ANSI version). (todo: example; no more time right now)

And, of course, you could leave the prefix, which is a valid path on Windows, even if it is a bit uncommon.

Restharrow answered 6/5, 2021 at 17:22 Comment(0)
H
-1

Since Windows 8, you can simply call PathCchStripPrefix to remove the \\?\ and the \\?\UNC prefix.

Hardhearted answered 26/3 at 23:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.