От новичка до гуру: Курсы программирования на CyberDuff

Получение фактического имени файла (с правильным регистром) в Windows

Файловая система Windows нечувствительна к регистру. Как, учитывая имя файла/папки (например, «somefile»), я получаю фактическое имя этого файла/папки (например, оно должно возвращать «SomeFile», если Explorer отображает его так)?

Некоторые способы, которые я знаю, все из которых кажутся довольно обратными:

  1. Учитывая полный путь, выполните поиск каждой папки на пути (через FindFirstFile). Это дает правильные результаты для каждой папки. На последнем шаге найдите сам файл.
  2. Получить имя файла из дескриптора (как в примере MSDN) . Для этого требуется открыть файл, создать сопоставление файлов, получить его имя, проанализировать имена устройств и т. д. Довольно запутанный. И это не работает для папок или файлов нулевого размера.

Я пропустил какой-то очевидный вызов WinAPI? Простейшие из них, такие как GetActualPathName() или GetFullPathName(), возвращают имя, используя регистр, который был передан (например, возвращает «программные файлы», если они были переданы, даже если это должны быть «Program Files»).

Я ищу собственное решение (не .NET).

16.09.2008

Ответы:


1

И настоящим я отвечаю на свой вопрос, основанный на оригинальном ответе от cspirz.

Вот функция, которая по заданному абсолютному, относительному или сетевому пути возвращает путь в верхнем/нижнем регистре, как это будет отображаться в Windows. Если какой-либо компонент пути не существует, он вернет переданный путь из этой точки.

Это довольно сложно, поскольку пытается обрабатывать сетевые пути и другие пограничные случаи. Он работает со строками широких символов и использует std::wstring. Да, теоретически Unicode TCHAR может отличаться от wchar_t; это упражнение для читателя :)

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.

17.09.2008
  • SHGetFileInfo: SHGFI_DISPLAYNAME: Получите отображаемое имя файла, которое отображается в проводнике Windows. [...] Обратите внимание, что на отображаемое имя могут влиять такие настройки, как отображение расширений. Вы уверены, что это то, что вы ищете? 13.07.2015
  • Это НЕ БУДЕТ работать для папки C:\Users\YourAccountName\Documents в Windows 8: вместо этого будет возвращено C:\Users\YourAccountName\My Documents, поскольку папка «Документы» отображается как «Мои документы» в проводнике Windows. 29.07.2015
  • У меня нет репутации, чтобы комментировать ответы, поэтому вот мое примечание для пользователей, которые используют GetAbsolutePathName: под капотом GetAbsolutePathName нарежьте путь и используйте FindFirstFile(W) в частях пути, чтобы узнать правильный регистр символов, вот доказательство: !введите здесь описание изображения 24.06.2018

  • 2

    Вы пытались использовать SHGetFileInfo?

    16.09.2008
  • Ура, это работает. Он возвращает только последний компонент имени пути (т. е. конечную папку или имя файла из всего пути), но это намного проще, чем использование FindFirstFile. 17.09.2008

  • 3

    Есть другое решение. Сначала вызовите GetShortPathName(), а затем GetLongPathName(). Угадайте, какой регистр символов будет использоваться тогда? ;-)

    27.03.2011
  • Это работает, если короткие имена файлов отключены в файловой системе? 18.04.2014
  • @HarryJohnston нет, это не работает в этом сценарии: 1) отключить короткое имя файла создание; 2) создать новый файл; 3) попытаться получить доступ к короткому имени этого файла. Это может по-прежнему работать, если файл был создан до отключения создания коротких имен файлов. 29.07.2015
  • Это не сработает, потому что путь может вообще не иметь короткого имени файла, например. C:\Пользователи\Общедоступные. 11.05.2016
  • @GlennMaynard Это работает. Компоненты, которые уже являются короткими, возвращаются GetLongPathName() в их реальном случае по какой-то причине. 14.02.2021

  • 4

    Хорошо, это 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
    

    Надеюсь, это хотя бы укажет вам правильное направление.

    16.09.2008

    5

    Только что обнаружил, что Scripting.FileSystemObject, предложенный @bugmagnet 10 лет назад, является сокровищем. В отличие от моего старого метода, он работает с абсолютным путем, относительным путем, путем 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;
    }
    

    Снимок экрана:

    скриншот2


    Старый ответ:

    Я обнаружил, что FindFirstFile() вернет правильное имя файла оболочки (последняя часть пути) в fd.cFileName. Если мы передаем c:\winDOWs\exPLORER.exe в качестве первого параметра в FindFirstFile(), fd.cFileName будет explorer.exe следующим образом:

    доказать

    Если мы заменим последнюю часть пути на fd.cFileName, мы получим последнюю часть правильно; путь станет c:\winDOWs\explorer.exe.

    Предполагая, что путь всегда является абсолютным путем (без изменения длины текста), мы можем просто применить этот «алгоритм» к каждой части пути (кроме части с буквой диска).

    Обсуждение дешево, вот код:

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

    доказать 2

    Преимущества:

    • Код работает на всех версиях Windows, начиная с Windows 95.
    • Базовая обработка ошибок.
    • Максимально возможная производительность. FindFirstFile() работает очень быстро, прямое управление буфером делает его еще быстрее.
    • Только C и чистый WinAPI. Небольшой размер исполняемого файла.

    Недостатки:

    • Поддерживается только абсолютный путь, остальные не определены.
    • Не уверен, что он полагается на недокументированное поведение.
    • Для некоторых людей код может быть слишком сырым и слишком самодельным. Может зажечь тебя.

    Причина стиля кода:

    Я использую goto для обработки ошибок, потому что привык к этому (goto очень удобен для обработки ошибок в C). Я использую цикл for для выполнения таких функций, как strcpy и strchr на лету, потому что хочу быть уверенным, что на самом деле было выполнено.

    17.11.2017
  • К сожалению, в рабочем коде вы абсолютно не можете предполагать, что длина пути не изменится. Такие случаи, как ввод файла с несколькими последовательными `` разделителями пути, полностью поддерживаются в ОС, но приведут к тому, что ваш код вернет неверный путь. 12.01.2018

  • 6

    FindFirstFileNameW будет работать с несколько недостатков:

    • это не работает с путями UNC
    • он удаляет букву диска, поэтому вам нужно добавить ее обратно
    • если есть более одной жесткой ссылки на ваш файл, вам нужно определить правильную
    01.10.2015

    7

    После быстрой проверки GetLongPathName() что вы хотите.

    16.09.2008
  • Не работает. GetLongPathName() возвращает любой регистр, который был передан (файлы c:\program вернут все это в нижнем регистре, то же самое для имен файлов). 17.09.2008
  • Новые материалы

    Представляем Narwhal Technologies (Nrwl)
    6 декабря 2016 г. Маунтин-Вью, Калифорния С тех пор, как Виктор Савкин и я (Джефф Кросс) присоединились к команде Angular в Google на заре Angular 1, Angular продемонстрировал феноменальный..

    Путь AWS  — «Изучение машинного обучения — 10 начинающих ИИ и машинного обучения на AWS».
    Универсальный ресурсный центр для изучения искусственного интеллекта и машинного обучения. НОЛЬ или ГЕРОЙ, начните свое путешествие здесь. Получите решения и пройдите обучение у экспертов AWS...

    5 простых концепций Python, ставших сложными
    #заранее извините 1) Переменные x = 4 y = 5 Переменная в Python — это символическое представление объекта. После присвоения некоторого объекта переменной Python мы приобретаем..

    «Освоение вероятности: изучение совместной, предельной, условной вероятности и теоремы Байеса —…
    Виды вероятности: Совместная вероятность Предельная вероятность Условная вероятность Диаграмма Венна в вероятностях: В “Set Theory” мы создаем диаграмму Венна...

    Основы Spring: Bean-компоненты, контейнер и внедрение зависимостей
    Как лего может помочь нашему пониманию Когда мы начинаем использовать Spring, нам бросают много терминов, и может быть трудно понять, что они все означают. Итак, мы разберем основы и будем..

    Отслеживание состояния с течением времени с дифференцированием снимков
    Время от времени что-то происходит и революционизирует часть моего рабочего процесса разработки. Что-то более забавное вместо типичного утомительного и утомительного процесса разработки. В..

    Я предполагаю, что вы имеете в виду методы обработки категориальных данных.
    Я предполагаю, что вы имеете в виду методы обработки категориальных данных. Пожалуйста, проверьте мой пост Инструментарий специалиста по данным для кодирования категориальных переменных в..