10

First off, let me ask a rhetoric question -- Microsoft, why making us prepend paths with \\?\ to allow them to be 32,767 characters long? Why not just use them as-is and extend the size of the internal buffers on APIs? Sorry, I'm just venting my frustration...

OK, now my actual question, say if I have a path, how do I convert it to the format accepting 32,767 character length? Note that I don't know anything about that path -- it can be a relative path, an absolute local path, a network share, etc. In other words it can be any of those multitudes of path formats that Microsoft invented.

At first it seems like a straightforward proposition of adding \\?\ at the beginning, right? Well, what if that path is already converted to the extended format? I tried reading this and from the size of that page and the number of comments at the bottom, you can see that things aren't as simple as it seems.

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 2
    I commend you for both finding and *reading* the article you linked, as well as realizing the veritable barrage of forms that must be considered as a result. The only simple advice to your question, "how do I convert it to the format accepting 32,767 character length?" is this: *carefully*. You yourself have pointed out it is not so simple as to tack a `\\?\` on the front and call it good, and the debris of the article makes no such presumptions either, to be sure. Its not difficult, but it is tedious. – WhozCraig Sep 02 '13 at 21:31
  • @WhozCraig: At this point I'm inquiring if there's a system API that does this. And if not, as I'm suspecting, I'm asking for collaboration to write such a function. – c00000fd Sep 02 '13 at 21:37
  • 1
    I particularly liked the dysfunctional contradiction within the article. In one place, "The maximum path of 32,767 characters is approximate, because the "\\?\" prefix may be expanded to a longer string by the system at run time, and this expansion applies to the total length.". And yet later, "Because it turns off automatic expansion of the path string, the "\\?\" prefix..." *love that*. – WhozCraig Sep 02 '13 at 21:47
  • @WhozCraig: Yes, that article poses more questions than it answers. I was hoping to use `PathStripToRoot` API to help resolve my original question, but it seems to work differently for different OS. For instance, it seems to work OK since Vista, but on XP it doesn't seem to detect network share prefixes... If I come up with a working code I'll post it here for your review. – c00000fd Sep 02 '13 at 22:08
  • @WhozCraig: As you may know, the contradiction arises because the documentation is talking about two different things. The "\\?\" prefix disables any early manipulation of the path at the win32 level, but the path is always expanded by the kernel into an NT namespace path (e.g. "C:" is expanded to something like "\Device\HarddiskVolume2"). Thus the maximum usable path length depends on how the system has named the device you are accessing. – arx Apr 10 '14 at 13:59

3 Answers3

5

OK. This turns out not to be as simple as it sounds. The stumbling block (besides a fuzzy documentation and a myriad of path formats) is that some APIs don't even work as advertised on all versions of OS.

Anyway here's what I came up with, that seems to work on XP SP3, Vista, Windows 7 and 8. Somewhat large, also written for MFC, but that's only for string management. I don't have time to adjust it:

enum PATH_PREFIX_TYPE
{
    PPT_UNKNOWN,
    PPT_ABSOLUTE,           //Found absolute path that is none of the other types
    PPT_UNC,                //Found \\server\share\ prefix
    PPT_LONG_UNICODE,       //Found \\?\ prefix
    PPT_LONG_UNICODE_UNC,   //Found \\?\UNC\ prefix
};

CString MakeUnicodeLargePath(LPCTSTR pPath)
{
    //Convert path from 'pPath' into a larger Unicode path, that allows up to 32,767 character length
    //RETURN:
    //      = Resulting path
    CString strPath;

    if(pPath &&
        pPath[0] != 0)
    {
        //Determine the type of the existing prefix
        PATH_PREFIX_TYPE ppt;
        GetOffsetAfterPathRoot(pPath, &ppt);

        //Assume path to be without change
        strPath = pPath;

        switch(ppt)
        {
        case PPT_ABSOLUTE:
            {
                //First we need to check if its an absolute path relative to the root
                BOOL bOK2AddPrefix = TRUE;
                if(strPath.GetLength() >= 1 &&
                    (strPath[0] == L'\\' || strPath[0] == L'/'))
                {
                    bOK2AddPrefix = FALSE;

                    //Get current root path
                    TCHAR chDummy[1];
                    DWORD dwLnSz = GetCurrentDirectory(0, chDummy);
                    if(dwLnSz)
                    {
                        TCHAR* pBuff = new (std::nothrow) TCHAR[dwLnSz];
                        if(pBuff)
                        {
                            if(GetCurrentDirectory(dwLnSz, pBuff) == dwLnSz - 1)
                            {
                                int nIndFollowing = GetOffsetAfterPathRoot(pBuff);
                                if(nIndFollowing > 0)
                                {
                                    bOK2AddPrefix = TRUE;
                                    CString strRoot = pBuff;
                                    strPath = strRoot.Left(nIndFollowing) + strPath.Right(strPath.GetLength() - 1);
                                }
                            }

                            delete[] pBuff;
                            pBuff = NULL;
                        }
                    }
                }

                if(bOK2AddPrefix)
                {
                    //Add \\?\ prefix
                    strPath = L"\\\\?\\" + strPath;
                }
            }
            break;

        case PPT_UNC:
            {
                //First we need to remove the opening slashes for UNC share
                if(strPath.GetLength() >= 2 &&
                    (strPath[0] == L'\\' || strPath[0] == L'/') &&
                    (strPath[1] == L'\\' || strPath[1] == L'/')
                    )
                {
                    strPath = strPath.Right(strPath.GetLength() - 2);
                }

                //Add \\?\UNC\ prefix
                strPath = L"\\\\?\\UNC\\" + strPath;
            }
            break;
        }
    }

    return strPath;
}

LPCTSTR PathSkipRoot_CorrectedForMicrosoftStupidity(LPCTSTR pszPath)
{
    //Correction for PathSkipRoot API
    CString strPath = pszPath;

    //Replace all /'s with \'s because PathSkipRoot can't handle /'s
    strPath.Replace(L'/', L'\\');

    //Now call the API
    LPCTSTR pResBuff = PathSkipRoot(strPath.GetString());

    return pResBuff ? pszPath + (UINT)(pResBuff - strPath.GetString()) : NULL;
}

BOOL PathIsRelative_CorrectedForMicrosoftStupidity(LPCTSTR pszPath)
{
    //Correction for PathIsRelative API
    CString strPath = pszPath;

    //Replace all /'s with \'s because PathIsRelative can't handle /'s
    strPath.Replace(L'/', L'\\');

    //Now call the API
    return PathIsRelative(strPath);
}

int GetOffsetAfterPathRoot(LPCTSTR pPath, PATH_PREFIX_TYPE* pOutPrefixType)
{
    //Checks if 'pPath' begins with the drive, share, prefix, etc
    //EXAMPLES:
    //   Path                          Return:   Points at:                 PrefixType:
    //  Relative\Folder\File.txt        0         Relative\Folder\File.txt   PPT_UNKNOWN
    //  \RelativeToRoot\Folder          1         RelativeToRoot\Folder      PPT_ABSOLUTE
    //  C:\Windows\Folder               3         Windows\Folder             PPT_ABSOLUTE
    //  \\server\share\Desktop          15        Desktop                    PPT_UNC
    //  \\?\C:\Windows\Folder           7         Windows\Folder             PPT_LONG_UNICODE
    //  \\?\UNC\server\share\Desktop    21        Desktop                    PPT_LONG_UNICODE_UNC
    //RETURN:
    //      = Index in 'pPath' after the root, or
    //      = 0 if no root was found
    int nRetInd = 0;
    PATH_PREFIX_TYPE ppt = PPT_UNKNOWN;

    if(pPath &&
        pPath[0] != 0)
    {
        int nLen = lstrlen(pPath);

        //Determine version of Windows
        OSVERSIONINFO osi;
        osi.dwOSVersionInfoSize = sizeof(osi);
        BOOL bWinXPOnly = GetVersionEx(&osi) && osi.dwMajorVersion <= 5;

        //The PathSkipRoot() API doesn't work correctly on Windows XP
        if(!bWinXPOnly)
        {
            //Works since Vista and up, but still needs correction :)
            LPCTSTR pPath2 = PathSkipRoot_CorrectedForMicrosoftStupidity(pPath);
            if(pPath2 &&
                pPath2 >= pPath)
            {
                nRetInd = pPath2 - pPath;
            }
        }

        //Now determine the type of prefix
        int nIndCheckUNC = -1;

        if(nLen >= 8 &&
            (pPath[0] == L'\\' || pPath[0] == L'/') &&
            (pPath[1] == L'\\' || pPath[1] == L'/') &&
            pPath[2] == L'?' &&
            (pPath[3] == L'\\' || pPath[3] == L'/') &&
            (pPath[4] == L'U' || pPath[4] == L'u') &&
            (pPath[5] == L'N' || pPath[5] == L'n') &&
            (pPath[6] == L'C' || pPath[6] == L'c') &&
            (pPath[7] == L'\\' || pPath[7] == L'/')
            )
        {
            //Found \\?\UNC\ prefix
            ppt = PPT_LONG_UNICODE_UNC;

            if(bWinXPOnly)
            {
                //For older OS
                nRetInd += 8;
            }

            //Check for UNC share later
            nIndCheckUNC = 8;
        }
        else if(nLen >= 4 &&
            (pPath[0] == L'\\' || pPath[0] == L'/') &&
            (pPath[1] == L'\\' || pPath[1] == L'/') &&
            pPath[2] == L'?' &&
            (pPath[3] == L'\\' || pPath[3] == L'/')
            )
        {
            //Found \\?\ prefix
            ppt = PPT_LONG_UNICODE;

            if(bWinXPOnly)
            {
                //For older OS
                nRetInd += 4;
            }
        }
        else if(nLen >= 2 &&
            (pPath[0] == L'\\' || pPath[0] == L'/') &&
            (pPath[1] == L'\\' || pPath[1] == L'/')
            )
        {
            //Check for UNC share later
            nIndCheckUNC = 2;
        }

        if(nIndCheckUNC >= 0)
        {
            //Check for UNC, i.e. \\server\share\ part
            int i = nIndCheckUNC;
            for(int nSkipSlashes = 2; nSkipSlashes > 0; nSkipSlashes--)
            {
                for(; i < nLen; i++)
                {
                    TCHAR z = pPath[i];
                    if(z == L'\\' ||
                        z == L'/' ||
                        i + 1 >= nLen)
                    {
                        i++;
                        if(nSkipSlashes == 1)
                        {
                            if(ppt == PPT_UNKNOWN)
                                ppt = PPT_UNC;

                            if(bWinXPOnly)
                            {
                                //For older OS
                                nRetInd = i;
                            }
                        }

                        break;
                    }
                }
            }
        }

        if(bWinXPOnly)
        {
            //Only if we didn't determine any other type
            if(ppt == PPT_UNKNOWN)
            {
                if(!PathIsRelative_CorrectedForMicrosoftStupidity(pPath + nRetInd))
                {
                    ppt = PPT_ABSOLUTE;
                }
            }

            //For older OS only
            LPCTSTR pPath2 = PathSkipRoot_CorrectedForMicrosoftStupidity(pPath + nRetInd);
            if(pPath2 &&
                pPath2 >= pPath)
            {
                nRetInd = pPath2 - pPath;
            }

        }
        else
        {
            //Only if we didn't determine any other type
            if(ppt == PPT_UNKNOWN)
            {
                if(!PathIsRelative_CorrectedForMicrosoftStupidity(pPath))
                {
                    ppt = PPT_ABSOLUTE;
                }
            }
        }
    }

    if(pOutPrefixType)
        *pOutPrefixType = ppt;

    return nRetInd;
}

And here's how I test it:

_tprintf(MakeUnicodeLargePath(L""));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"C:\\Ba*d\\P|a?t<h>\\Windows\\Folder"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"Relative\\Folder\\File.txt"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"C:\\Windows\\Folder"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\?\\C:\\Windows\\Folder"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\?\\UNC\\server\\share\\Desktop"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\?\\unC\\server\\share\\Desktop\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\server\\share\\Desktop\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"C:\\Desktop\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\AbsoluteToRoot\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path\\Very Long path"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\server\\share\\Desktop"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\?\\UNC\\\\share\\folder\\Desktop"));
_tprintf(L"\n");
_tprintf(MakeUnicodeLargePath(L"\\\\server\\share"));
_tprintf(L"\n");

And this is the output I get:

\\?\C:\Ba*d\P|a?t<h>\Windows\Folder
Relative\Folder\File.txt
\\?\C:\Windows\Folder
\\?\C:\Windows\Folder
\\?\UNC\server\share\Desktop
\\?\unC\server\share\Desktop\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path
\\?\UNC\server\share\Desktop\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path
\\?\C:\Desktop\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path
\\?\C:\AbsoluteToRoot\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path
\\?\UNC\server\share\Desktop
\\?\UNC\\share\folder\Desktop
\\?\UNC\server\share
c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • If you need it to work on different a myrad of different Windows OS editions then take note that UNCs are only supported with Windows 7 and later editions,the UNC support on the Windows Server editions I'm not clear on but I suspect UNCs are not supported until Windows Server 2008. –  Sep 03 '13 at 14:40
2

Today is your lucky day because I was just working on the problem for my own program. The solution is simple: PathCchCanonicalizeEx function will prepend the "\?\" sign if needed. But this function have some minuses as it requires to use Windows 8 as a target platform which is not a deal. That's way I made my own function which accepts an "abnormal" path string as an input and output the normalized string ready to exceed the MAX_PATH limit. It uses the new define in the Win8 SDK:

// max # of characters we support using the "\\?\" syntax
// (0x7FFF + 1 for NULL terminator)
#define PATHCCH_MAX_CCH             0x8000

The function declarations is follows (not that it requires CString defined and it uses SAL annotations) :

//Normalize path and prepend "\\?\" if it exceeds the MAX_PATH limit
_Success_(return != false) //If the path was normalized and it exist (if bValidate == true) the return value is true and the normalized path is assigned to the sPath param
bool NormalizePath(_Inout_ CString & sPath, //Set to the abnormal path (relative, UNC, absolute), it is also used to store the normal path
    _In_ const CString sCurrentDir, //Set to the currend directory that will be used to resolve relative paths (used for security as GetCurrentDirectory function is thread-unsafe)
    _Out_opt_ bool *bDir = NULL, //Set to a bool pointer that will be set to true if the path points to a directory
    _In_ bool bValidate = false //If set the path existence is checked and the bDir argument is used
    );

And the definitions (without many comments):

// max # of characters we support using the "\\?\" syntax
// (0x7FFF + 1 for NULL terminator)
#define PATHCCH_MAX_CCH             0x8000

#define LONG_PATH_ID                L"\\\\?\\"
#define UNC_PREFIX                  "\\\\"
#define UNC_LONG_ID                 L"\\\\?\\UNC\\"
#define CUR_DIR_REL_PATH_ID         ".\\"

#define WILDCAR_CHAR_ASTER          '*'
#define WILDCAR_CHAR_QUEMARK        '?'

#define DIR_DOWN                    ".."
#define DIR_UP                      "."

//Verify that a path exists and set bDir to true if the path points to an directory
bool ValidatePath(_In_ const TCHAR *sPath, _Out_ bool & bDir)
{
    //If the path contains a wildcards search test only the parent directory

    const TCHAR * sLastElement = _tcsrchr(sPath, _T('\\')); //The last component in the path

    bool bFoundWildCard = false;

    if(sLastElement)
    {
        ++sLastElement;
        if(_tcsrchr(sLastElement, _T(WILDCAR_CHAR_ASTER)) || _tcsrchr(sLastElement, _T(WILDCAR_CHAR_QUEMARK))) //If wilcard characters are contained in the last path component
        {
            bFoundWildCard = true;
            --sLastElement;
            const_cast<TCHAR *>(sLastElement)[0] = _T('\0');
        }
    }

    DWORD dwPathAttr = GetFileAttributes(sPath);

    if(dwPathAttr == INVALID_FILE_ATTRIBUTES)
    {
        _com_error sErrorMsg(GetLastError());
        //CProgramError.Set(sErrorMsg.ErrorMessage());
        return false;
    }

    bDir = dwPathAttr & FILE_ATTRIBUTE_DIRECTORY;

    if(bFoundWildCard)
    {
        const_cast<TCHAR *>(sLastElement)[0] = _T('\\');
    }

    return true;
}

void RespondPathComponent(_In_ const TCHAR *pComponent, _Out_ std::vector<CString> & vPathComponents)
{
    const TCHAR *pComponentLimiterR = _tcschr(pComponent, _T('\\'));

    const TCHAR *pComponentLimiterL = _tcschr(pComponent, _T('/'));

    const TCHAR *pComponentLimiter = NULL;

    if(pComponentLimiterR && pComponentLimiterL)
        pComponentLimiter = (pComponentLimiterR > pComponentLimiterL ? pComponentLimiterL : pComponentLimiterR);
    else
        pComponentLimiter = (pComponentLimiterR ? pComponentLimiterR : pComponentLimiterL);

    if(pComponentLimiter)
    {
        size_t szComponent = pComponentLimiter - pComponent;
        if(szComponent)
        {
            CString sTemp;
            sTemp.SetString(pComponent, szComponent);
            vPathComponents.push_back(sTemp);
        }
        ++pComponentLimiter;
        RespondPathComponent(pComponentLimiter, vPathComponents);
    }
    else
    {
        size_t szLastComp = _tcslen(pComponent);
        if(szLastComp)
        {
            CString sTemp;
            sTemp.SetString(pComponent, szLastComp);
            vPathComponents.push_back(sTemp);
        }
    }
}

size_t FixUpPathComponents(_Out_ std::vector<CString> & vPathComponents, _In_ const TCHAR *pPathComponents)
{
    RespondPathComponent(pPathComponents, vPathComponents);

    size_t szNumComponents = vPathComponents.size();

    for(size_t i(0); i < szNumComponents; ++i) //Check path components for special meanings
    {
        if(vPathComponents[i] == _T(DIR_DOWN))
        {
            vPathComponents.erase(vPathComponents.begin() + i); //Remove the current component
            --szNumComponents;
            if(i > 0)
            {
                vPathComponents.erase(vPathComponents.begin() + i - 1);
                --szNumComponents;
            }
        }
        else if(vPathComponents[i] == _T(DIR_UP))
        {
            if( (i + 1) < szNumComponents )
            {
                vPathComponents.erase(vPathComponents.begin() + i + 1);
                --szNumComponents;
            }
            vPathComponents.erase(vPathComponents.begin() + i); //Remove the current component
            --szNumComponents;
        }
    }

    return szNumComponents;
}

//Note that sCurrentDir is appended to all relative paths (nomatter the drive letter) - it needs to be a full path, not ending with '\\'
bool ExpandAndFixUpPath(_Inout_ CString & sPath, _In_opt_ const CString sCurrentDir)
{
    const size_t InPathStrSz = sPath.GetLength();

    if(!InPathStrSz) //Invalid path
        return false;

    //sPath.LockBuffer(); //Lock character buffer

    const TCHAR *PathBuffer = sPath.GetString(); //Retrieve the buffer

    if(InPathStrSz > 1) //To suppose the path is full it needs to have at lease 2 characters
    {
        if(_tcsstr(PathBuffer, _T(UNC_PREFIX)) == PathBuffer) //If the path begin with UNC_PREFIX
        {
            std::vector<CString> vPathComponents;

            size_t nComponents;

            if((nComponents = FixUpPathComponents(vPathComponents, &PathBuffer[2])) < 2) //A UNC path needs at leas two elements
                return false;

            sPath = _T('\\');

            for(size_t i(0); i < nComponents; ++i)
            {
                sPath += _T('\\');
                sPath += vPathComponents[i];
            }

            return true;
        }
        else if(PathBuffer[1] == _T(':')) //If the path begin with a disk designator
        {
            std::vector<CString> vPathComponents;

            if(FixUpPathComponents(vPathComponents, &PathBuffer[2]))
            {
                if(PathBuffer[2] == _T('\\') || PathBuffer[2] == _T('/'))
                {
                    sPath.SetString(PathBuffer, 2);

                    for(size_t i(0); i < vPathComponents.size(); ++i)
                    {
                        sPath += _T('\\');
                        sPath += vPathComponents[i];
                    }
                }
                else
                {
                    sPath = sCurrentDir;

                    for(size_t i(0); i < vPathComponents.size(); ++i)
                    {
                        sPath += _T('\\');
                        sPath += vPathComponents[i];
                    }
                }
            }
            else
            {
                sPath.SetString(PathBuffer, 2);
                sPath += _T('\\');
            }
            return true;
        }
    }

    std::vector<CString> vPathComponents;

    const TCHAR *pComponentsBegin = (_tcsstr(PathBuffer, _T(CUR_DIR_REL_PATH_ID)) == PathBuffer ? &PathBuffer[((sizeof(_T(CUR_DIR_REL_PATH_ID)) / sizeof(TCHAR)) - 1)] : PathBuffer);

    FixUpPathComponents(vPathComponents, pComponentsBegin);

    sPath = sCurrentDir;

    for(size_t i(0); i < vPathComponents.size(); ++i)
    {
        sPath += _T('\\');
        sPath += vPathComponents[i];
    }

    return true;
}

bool NormalizePath(_Inout_ CString & sPath, _In_ const CString sCurrentDir, _Out_opt_ bool *bDir = NULL, _In_ bool bValidate = false)
{
    if(!ExpandAndFixUpPath(sPath, sCurrentDir)) //Extend the path to it's full form
        return false;

    size_t LongPathLen = sPath.GetLength();

    const TCHAR *pPathBuf = sPath.GetString();

#ifdef _UNICODE

    if(LongPathLen <= (MAX_PATH - 1)) //If the path is longer in the range of MAX_PATH return it directly
    {
        if(bValidate)
            if(!ValidatePath(pPathBuf, *bDir)) //Validate path before returning it
                return false;

        return true;
    }

    bool bIsUNCPath = _tcsstr(pPathBuf, _T(UNC_PREFIX)) == pPathBuf;

    if(!bIsUNCPath)
    {
        if(LongPathLen > (PATHCCH_MAX_CCH - 1 - ((sizeof(LONG_PATH_ID) / sizeof(WCHAR)) - 1))) //If have no space to store the prefix fail
        {
            //CProgramError.Set(_T("Path too long!"));
            return false;
        }


        CString sPathTmp = LONG_PATH_ID;

        sPathTmp += pPathBuf;

        if(bValidate)
            if(!ValidatePath(sPathTmp.GetString(), *bDir)) //Validate path before returning it
                return false;

        sPath = sPathTmp;

        return true;
    }
    else
    {
        if( LongPathLen > ( PATHCCH_MAX_CCH - 1 - ((sizeof(UNC_LONG_ID) / sizeof(WCHAR)) - 1) + ((sizeof(_T(UNC_PREFIX)) / sizeof(WCHAR)) - 1) ) ) //If have no space to store the prefix fail
        {
            //CProgramError.Set(_T("Path too long!"));
            return false;
        }


        CString sPathTmp = UNC_LONG_ID;

        sPathTmp += &pPathBuf[2];

        if(bValidate)
            if(!ValidatePath(sPathTmp.GetString(), *bDir)) //Validate path before returning it
                return false;

        sPath = sPathTmp;

        return true;
    }

#else

    if(bValidate)
        if(!ValidatePath(pPathBuf, *bDir)) //Validate path before returning it
            return false;

    return true;

#endif
}
  • 1
    Thanks. First off, what an "intuitive" way to name that new API, `PathCchCanonicalizeEx`, Microsoft :) Secondly, I wish you posted it a bit earlier. I ended up making my own work-around method (which I posted above.) I see that you're parsing the path all on your own, which kinda concerns me since my method relies on the existing Microsoft APIs to do most of the parsing. – c00000fd Sep 13 '13 at 16:48
  • I preferred using my own methods because the GetFullPathName function which I was using instead isn't thread safe. It is not the best implementation ofcourse. I'm really happy though that Microsoft added an new "MAX_PATH" value so we will be finally able to handle long paths. I also wish that the next Windows Explorer will support them too. –  Sep 13 '13 at 19:55
  • With the Shell being *the* reason for the `MAX_PATH` limit it would have never occured to me to look there for a solution. Great find. – IInspectable Sep 13 '13 at 19:56
0

Here is an example converted to untested C++ code from some of my old .NET code.

#include <iostream>
#include <string>

// For PathIsRelative() & PathCombine()
#include "Shlwapi.h"
#pragma comment(lib, "Shlwapi.lib") 

// For _wgetcwd()
#include <direct.h>

std::wstring ConvertToWin32Path(std::wstring &filepath);
std::wstring MakePathAbsolute(std::wstring &filepath);

int main()
{
    std::wstring filepath;
    std::wcout << L"Enter absolute file path (ex. \"c:\\temp\\myfile.txt\"):";
    std::wcin >> filepath;
    std::wcout << L"Converted file path: " << ConvertToWin32Path(filepath);
    return 0;
}

// If file path is disk file path then prepend it with \\?\
// if file path is UNC prepend it with \\?\UNC\ and remove \\ prefix in unc path.
std::wstring ConvertToWin32Path(std::wstring &filepath)
{
    if (filepath.compare(0, 2, L"\\\\"))
    {
        // If the path is relative then we need to convert it to a absolute path
        if (PathIsRelative(filepath.c_str()))
            filepath = MakePathAbsolute(filepath);

        filepath = std::wstring(L"\\\\?\\") + filepath;
    }
    else 
    {
        // Is the path already converted, if so then return the same filepath
        if (filepath.compare(0, 3, L"\\\\?"))
            filepath = std::wstring(L"\\\\?\\UNC\\") + filepath.substr(2, filepath.length() - 2);
    }

    return filepath;    
}

// Converts a relative path to a absolute one by adding the path of the executable
std::wstring MakePathAbsolute(std::wstring &filepath)
{
    // Convert from a relative to an absolute file path
    const size_t maxCurrDirLen = 256;
    wchar_t currentDir[maxCurrDirLen];
    _wgetcwd(currentDir, maxCurrDirLen - 1);
    const size_t maxPathLen = 32767;
    wchar_t fullPath[maxPathLen];
    PathCombine(fullPath, currentDir, filepath.c_str());
    return std::wstring(fullPath);
}

Update:

Did you even test my code before concluding that

"Thanks for trying, but sorry this is not that simple."

? I've added the output from my code with your new post's input paths; could you please state where my code fails? As far as I can see my code has a more accurate output than your (more complex code) does. The only problem I can see is that the UNC share path should have been \\share\folder\Desktop and not \\?\UNC\\share\folder\Desktop but that's an easy fix - but then again your own code has the same bug...

\\?\c:\dev\UNC_path_test
\\?\C:\Ba*d\P|a?t<h>\Windows\Folder
\\?\c:\dev\UNC_path_test\Relative\Folder\File.txt
\\?\C:\Windows\Folder
\\?\C:\Windows\Folder
\\?\UNC\server\share\Desktop
\\?\unC\server\share\Desktop\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path
\\?\UNC\server\share\Desktop\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\V
ery Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very L
ong path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long p
ath\Very Long path\Very Long path\Very Long path
\\?\C:\Desktop\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path
\\?\\AbsoluteToRoot\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long
path\Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\
Very Long path\Very Long path\Very Long path\Very Long path\Very Long path\Very
Long path\Very Long path\Very Long path
\\?\UNC\server\share\Desktop
\\?\UNC\\share\folder\Desktop
\\?\UNC\server\share
  • Thanks for trying, but sorry. This is not that simple. See my example in a different post. – c00000fd Sep 03 '13 at 01:29
  • @c00000fd Could you point out to me what kind of paths would not be supported with my above solution; I've looked at your new post but could not see the problem. –  Sep 03 '13 at 14:49
  • @Inge It does not work for relative paths. Relative paths were explicitly stated as a requirement. It will also fail for any future extension Microsoft decides to incorporate. – IInspectable Sep 13 '13 at 19:51
  • @IInspectable As one can see in my example the relative path `Relative\\Folder\\File.txt` is converted to `\\?\c:\dev\UNC_path_test\Relative\Folder\File.txt`, where `\\?\c:\dev\UNC_path_test` is the directory where my code ran from. Should I be expecting some other result? –  Sep 14 '13 at 00:34
  • @Inge You are calling `PathCombine` for relative paths. This API is limited to `MAX_PATH` characters, regardless of your real buffer size. You would have to use [`PathCchCombineEx`](http://msdn.microsoft.com/en-us/library/windows/desktop/hh707086.aspx) instead, which is Windows 8/Windows Server 2012 only. Similarly, `PathIsRelative` is limited to `MAX_PATH` characters. – IInspectable Sep 14 '13 at 12:59