代码之家  ›  专栏  ›  技术社区  ›  NeARAZ

在Windows上获取实际文件名(大小写正确)

  •  16
  • NeARAZ  · 技术社区  · 16 年前

    Windows文件系统不区分大小写。如何,给定一个文件/文件夹名(例如“somefile”),我得到 该文件/文件夹的名称(例如,如果资源管理器显示“SomeFile”,则应返回“SomeFile”)?

    我知道的一些方法似乎都很落后:

    1. 给定完整路径,搜索路径上的每个文件夹(通过FindFirstFile)。这将为每个文件夹提供正确的大小写结果。在最后一步中,搜索文件本身。
    2. 从句柄获取文件名(如 MSDN example

    我是否错过了一些明显的WinAPI调用?最简单的方法,如GetActualPathName()或GetFullPathName()使用传入的大小写返回名称(例如,如果传入,则返回“程序文件”,即使它应该是“程序文件”)。

    我正在寻找本机解决方案(不是.NET解决方案)。

    9 回复  |  直到 16 年前
        1
  •  5
  •   Community Neeleshkumar S    7 年前

    original answer from cspirz .

    这里有一个函数,给定绝对路径、相对路径或网络路径,它将返回带有大写/小写字母的路径,正如它在Windows上显示的那样。如果路径的某个组件不存在,它将返回从该点传入的路径。

    std::wstring GetActualPathName( const wchar_t* path )
    {
        // This is quite involved, but the meat is SHGetFileInfo
    
        const wchar_t kSeparator = L'\\';
    
        // copy input string because we'll be temporary modifying it in place
        size_t length = wcslen(path);
        wchar_t buffer[MAX_PATH];
        memcpy( buffer, path, (length+1) * sizeof(path[0]) );
    
        size_t i = 0;
    
        std::wstring result;
    
        // for network paths (\\server\share\RestOfPath), getting the display
        // name mangles it into unusable form (e.g. "\\server\share" turns
        // into "share on server (server)"). So detect this case and just skip
        // up to two path components
        if( length >= 2 && buffer[0] == kSeparator && buffer[1] == kSeparator )
        {
            int skippedCount = 0;
            i = 2; // start after '\\'
            while( i < length && skippedCount < 2 )
            {
                if( buffer[i] == kSeparator )
                    ++skippedCount;
                ++i;
            }
    
            result.append( buffer, i );
        }
        // for drive names, just add it uppercased
        else if( length >= 2 && buffer[1] == L':' )
        {
            result += towupper(buffer[0]);
            result += L':';
            if( length >= 3 && buffer[2] == kSeparator )
            {
                result += kSeparator;
                i = 3; // start after drive, colon and separator
            }
            else
            {
                i = 2; // start after drive and colon
            }
        }
    
        size_t lastComponentStart = i;
        bool addSeparator = false;
    
        while( i < length )
        {
            // skip until path separator
            while( i < length && buffer[i] != kSeparator )
                ++i;
    
            if( addSeparator )
                result += kSeparator;
    
            // if we found path separator, get real filename of this
            // last path name component
            bool foundSeparator = (i < length);
            buffer[i] = 0;
            SHFILEINFOW info;
    
            // nuke the path separator so that we get real name of current path component
            info.szDisplayName[0] = 0;
            if( SHGetFileInfoW( buffer, 0, &info, sizeof(info), SHGFI_DISPLAYNAME ) )
            {
                result += info.szDisplayName;
            }
            else
            {
                // most likely file does not exist.
                // So just append original path name component.
                result.append( buffer + lastComponentStart, i - lastComponentStart );
            }
    
            // restore path separator that we might have nuked before
            if( foundSeparator )
                buffer[i] = kSeparator;
    
            ++i;
            lastComponentStart = i;
            addSeparator = true;
        }
    
        return result;
    }
    

    再次感谢cspirz为我指出SHGetFileInfo。

        2
  •  4
  •   cspirz    16 年前
        3
  •  3
  •   sergioko    13 年前

    还有另一个解决办法。首先调用GetShortPathName(),然后调用GetLongPathName()。猜猜接下来将使用什么字符大小写?;-)

        4
  •  2
  •   bugmagnet    16 年前

    好的,这是VBScript,但即使如此,我还是建议使用Scripting.FileSystemObject对象

    Dim fso
    Set fso = CreateObject("Scripting.FileSystemObject")
    Dim f
    Set f = fso.GetFile("C:\testfile.dat") 'actually named "testFILE.dAt"
    wscript.echo f.Name
    

    testFILE.dAt
    

    希望这至少为你指明了正确的方向。

        5
  •  1
  •   raymai97    6 年前

    刚发现 Scripting.FileSystemObject 十年前@bugmagnet提出的建议是一笔财富。与我以前的方法不同,它适用于绝对路径、相对路径、UNC路径和非常长的路径(路径比 MAX_PATH

    为了将来的参考,我想介绍这段代码,可以用C和C++模式编译。在C++模式下,代码将使用STL和ATL。在C模式下,您可以清楚地看到幕后的一切工作。

    #include <Windows.h>
    #include <objbase.h>
    #include <conio.h> // for _getch()
    
    #ifndef __cplusplus
    #   include <stdio.h>
    
    #define SafeFree(p, fn) \
        if (p) { fn(p); (p) = NULL; }
    
    #define SafeFreeCOM(p) \
        if (p) { (p)->lpVtbl->Release(p); (p) = NULL; }
    
    
    static HRESULT CorrectPathCasing2(
        LPCWSTR const pszSrc, LPWSTR *ppszDst)
    {
        DWORD const clsCtx = CLSCTX_INPROC_SERVER;
        LCID const lcid = LOCALE_USER_DEFAULT;
        LPCWSTR const pszProgId = L"Scripting.FileSystemObject";
        LPCWSTR const pszMethod = L"GetAbsolutePathName";
        HRESULT hr = 0;
        CLSID clsid = { 0 };
        IDispatch *pDisp = NULL;
        DISPID dispid = 0;
        VARIANT vtSrc = { VT_BSTR };
        VARIANT vtDst = { VT_BSTR };
        DISPPARAMS params = { 0 };
        SIZE_T cbDst = 0;
        LPWSTR pszDst = NULL;
    
        // CoCreateInstance<IDispatch>(pszProgId, &pDisp)
    
        hr = CLSIDFromProgID(pszProgId, &clsid);
        if (FAILED(hr)) goto eof;
    
        hr = CoCreateInstance(&clsid, NULL, clsCtx,
            &IID_IDispatch, (void**)&pDisp);
        if (FAILED(hr)) goto eof;
        if (!pDisp) {
            hr = E_UNEXPECTED; goto eof;
        }
    
        // Variant<BSTR> vtSrc(pszSrc), vtDst;
        // vtDst = pDisp->InvokeMethod( pDisp->GetIDOfName(pszMethod), vtSrc );
    
        hr = pDisp->lpVtbl->GetIDsOfNames(pDisp, NULL,
            (LPOLESTR*)&pszMethod, 1, lcid, &dispid);
        if (FAILED(hr)) goto eof;
    
        vtSrc.bstrVal = SysAllocString(pszSrc);
        if (!vtSrc.bstrVal) {
            hr = E_OUTOFMEMORY; goto eof;
        }
        params.rgvarg = &vtSrc;
        params.cArgs = 1;
        hr = pDisp->lpVtbl->Invoke(pDisp, dispid, NULL, lcid,
            DISPATCH_METHOD, &params, &vtDst, NULL, NULL);
        if (FAILED(hr)) goto eof;
        if (!vtDst.bstrVal) {
            hr = E_UNEXPECTED; goto eof;
        }
    
        // *ppszDst = AllocWStrCopyBStrFrom(vtDst.bstrVal);
    
        cbDst = SysStringByteLen(vtDst.bstrVal);
        pszDst = HeapAlloc(GetProcessHeap(),
            HEAP_ZERO_MEMORY, cbDst + sizeof(WCHAR));
        if (!pszDst) {
            hr = E_OUTOFMEMORY; goto eof;
        }
        CopyMemory(pszDst, vtDst.bstrVal, cbDst);
        *ppszDst = pszDst;
    
    eof:
        SafeFree(vtDst.bstrVal, SysFreeString);
        SafeFree(vtSrc.bstrVal, SysFreeString);
        SafeFreeCOM(pDisp);
        return hr;
    }
    
    static void Cout(char const *psz)
    {
        printf("%s", psz);
    }
    
    static void CoutErr(HRESULT hr)
    {
        printf("Error HRESULT 0x%.8X!\n", hr);
    }
    
    static void Test(LPCWSTR pszPath)
    {
        LPWSTR pszRet = NULL;
        HRESULT hr = CorrectPathCasing2(pszPath, &pszRet);
        if (FAILED(hr)) {
            wprintf(L"Input: <%s>\n", pszPath);
            CoutErr(hr);
        }
        else {
            wprintf(L"Was: <%s>\nNow: <%s>\n", pszPath, pszRet);
            HeapFree(GetProcessHeap(), 0, pszRet);
        }
    }
    
    
    #else // Use C++ STL and ATL
    #   include <iostream>
    #   include <iomanip>
    #   include <string>
    #   include <atlbase.h>
    
    static HRESULT CorrectPathCasing2(
        std::wstring const &srcPath,
        std::wstring &dstPath)
    {
        HRESULT hr = 0;
        CComPtr<IDispatch> disp;
        hr = disp.CoCreateInstance(L"Scripting.FileSystemObject");
        if (FAILED(hr)) return hr;
    
        CComVariant src(srcPath.c_str()), dst;
        hr = disp.Invoke1(L"GetAbsolutePathName", &src, &dst);
        if (FAILED(hr)) return hr;
    
        SIZE_T cch = SysStringLen(dst.bstrVal);
        dstPath = std::wstring(dst.bstrVal, cch);
        return hr;
    }
    
    static void Cout(char const *psz)
    {
        std::cout << psz;
    }
    
    static void CoutErr(HRESULT hr)
    {
        std::wcout
            << std::hex << std::setfill(L'0') << std::setw(8)
            << "Error HRESULT 0x" << hr << "\n";
    }
    
    static void Test(std::wstring const &path)
    {
        std::wstring output;
        HRESULT hr = CorrectPathCasing2(path, output);
        if (FAILED(hr)) {
            std::wcout << L"Input: <" << path << ">\n";
            CoutErr(hr);
        }
        else {
            std::wcout << L"Was: <" << path << ">\n"
                << "Now: <" << output << ">\n";
        }
    }
    
    #endif
    
    
    static void TestRoutine(void)
    {
        HRESULT hr = CoInitialize(NULL);
    
        if (FAILED(hr)) {
            Cout("CoInitialize failed!\n");
            CoutErr(hr);
            return;
        }
    
        Cout("\n[ Absolute Path ]\n");
        Test(L"c:\\uSers\\RayMai\\docuMENTs");
        Test(L"C:\\WINDOWS\\SYSTEM32");
    
        Cout("\n[ Relative Path ]\n");
        Test(L".");
        Test(L"..");
        Test(L"\\");
    
        Cout("\n[ UNC Path ]\n");
        Test(L"\\\\VMWARE-HOST\\SHARED FOLDERS\\D\\PROGRAMS INSTALLER");
    
        Cout("\n[ Very Long Path ]\n");
        Test(L"\\\\?\\C:\\VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME\\"
            L"VERYVERYVERYLOOOOOOOONGFOLDERNAME");
    
        Cout("\n!! Worth Nothing Behavior !!\n");
        Test(L"");
        Test(L"1234notexist");
        Test(L"C:\\bad\\PATH");
    
        CoUninitialize();
    }
    
    int main(void)
    {
        TestRoutine();
        _getch();
        return 0;
    }
    

    截图:

    screenshot2


    旧答案:

    FindFirstFile() 将在中返回正确的大小写文件名(路径的最后一部分) fd.cFileName . 如果我们通过 c:\winDOWs\exPLORER.exe FindFirstFile() 这个 fd.cFileName 会是 explorer.exe 这样地:

    prove

    如果我们将路径的最后一部分替换为 fd.cFileName ,我们将得到正确的最后一部分;这条路将变成 c:\winDOWs\explorer.exe

    假设路径始终是绝对路径(文本长度不变),我们可以将此“算法”应用于路径的每个部分(驱动器号部分除外)。

    Talk很便宜,下面是代码:

    #include <windows.h>
    #include <stdio.h>
    
    /*
        c:\windows\windowsupdate.log --> c:\windows\WindowsUpdate.log
    */
    static HRESULT MyProcessLastPart(LPTSTR szPath)
    {
        HRESULT hr = 0;
        HANDLE hFind = NULL;
        WIN32_FIND_DATA fd = {0};
        TCHAR *p = NULL, *q = NULL;
        /* thePart = GetCorrectCasingFileName(thePath); */
        hFind = FindFirstFile(szPath, &fd);
        if (hFind == INVALID_HANDLE_VALUE) {
            hr = HRESULT_FROM_WIN32(GetLastError());
            hFind = NULL; goto eof;
        }
        /* thePath = thePath.ReplaceLast(thePart); */
        for (p = szPath; *p; ++p);
        for (q = fd.cFileName; *q; ++q, --p);
        for (q = fd.cFileName; *p = *q; ++p, ++q);
    eof:
        if (hFind) { FindClose(hFind); }
        return hr;
    }
    
    /*
        Important! 'szPath' should be absolute path only.
        MUST NOT SPECIFY relative path or UNC or short file name.
    */
    EXTERN_C
    HRESULT __stdcall
    CorrectPathCasing(
        LPTSTR szPath)
    {
        HRESULT hr = 0;
        TCHAR *p = NULL;
        if (GetFileAttributes(szPath) == -1) {
            hr = HRESULT_FROM_WIN32(GetLastError()); goto eof;
        }
        for (p = szPath; *p; ++p)
        {
            if (*p == '\\' || *p == '/')
            {
                TCHAR slashChar = *p;
                if (p[-1] == ':') /* p[-2] is drive letter */
                {
                    p[-2] = toupper(p[-2]);
                    continue;
                }
                *p = '\0';
                hr = MyProcessLastPart(szPath);
                *p = slashChar;
                if (FAILED(hr)) goto eof;
            }
        }
        hr = MyProcessLastPart(szPath);
    eof:
        return hr;
    }
    
    int main()
    {
        TCHAR szPath[] = TEXT("c:\\windows\\EXPLORER.exe");
        HRESULT hr = CorrectPathCasing(szPath);
        if (SUCCEEDED(hr))
        {
            MessageBox(NULL, szPath, TEXT("Test"), MB_ICONINFORMATION);
        }
        return 0;
    }
    

    prove 2

    优势:

    • 该代码适用于自Windows95以来的所有Windows版本。
    • 可能的最高性能。 速度非常快,直接的缓冲区操作使速度更快。
    • 只有C和纯WinAPI。小的可执行文件大小。

    缺点:

    • 仅支持绝对路径,其他为未定义的行为。
    • 不确定它是否依赖于未记录的行为。
    • 代码可能太原始,对某些人来说太过DIY。可能会让你发火。

    代码样式背后的原因:

    我用 goto 后藤 对于C语言中的错误处理非常方便。我用 for 循环以执行以下功能: strcpy strchr 因为我想确定实际执行的是什么。

        6
  •  0
  •   Doub    9 年前

    FindFirstFileNameW 将有一些缺点:

    • 它在UNC路径上不起作用
    • 它会剥离驱动器号,因此您需要将其添加回去
    • 如果您的文件有多个硬链接,您需要确定正确的链接
        7
  •  -3
  •   Adam Rosenfield    16 年前

    经过快速测试, GetLongPathName()