Получение фактического имени файла (с правильным регистром) в Windows - PullRequest
16 голосов
/ 16 сентября 2008

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

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

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

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

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

Ответы [ 7 ]

5 голосов
/ 17 сентября 2008

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

4 голосов
/ 16 сентября 2008

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

3 голосов
/ 28 марта 2011

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

2 голосов
/ 16 сентября 2008

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

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

1 голос
/ 17 ноября 2017

Только что обнаружили, что 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 следующим образом:

prove

Если мы заменим последнюю часть пути на 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;
}

prove 2

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

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

Недостатки:

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

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

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

0 голосов
/ 01 октября 2015

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...