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

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

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

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

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

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

7 ответов

Решение

И тем самым я отвечаю на свой вопрос, основываясь на оригинальном ответе 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.

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

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

Просто обнаружил, что 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;
}

Скриншот:

screenshot2


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

я нашел это 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. Маленький исполняемый размер.

Недостатки:

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

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

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

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

Надеюсь, что это, по крайней мере, указывает вам в правильном направлении.

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

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

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

Насколько я знаю, свойство Name класса System.IO.FileInfo вернет вам фактическое имя из Windows.

Это делает трюк:

win32file.FindFilesW('somefile')[0][-2]

возвращает SomeFile.

РЕДАКТИРОВАТЬ: глупо, я искал то же самое в Python. Так что игнорируйте это для C/C++...

Другие вопросы по тегам