문제

Windows 파일 시스템은 대소문자를 구분하지 않습니다.파일/폴더 이름이 지정된 경우(예:"somefile"), 나는 실제 해당 파일/폴더의 이름(예:탐색기에 "SomeFile"이 표시되면 "SomeFile"을 반환해야 합니까?

내가 아는 몇 가지 방법은 모두 꽤 거꾸로 보이는 것 같습니다.

  1. 전체 경로가 주어지면 FindFirstFile을 통해 경로에서 각 폴더를 검색합니다.이렇게 하면 각 폴더에 대해 적절한 결과가 제공됩니다.마지막 단계에서 파일 자체를 검색하십시오.
  2. 핸들에서 파일 이름을 가져옵니다(예: MSDN 예).이를 위해서는 파일 열기, 파일 매핑 생성, 이름 가져오기, 장치 이름 구문 분석 등이 필요합니다.꽤 복잡합니다.그리고 폴더나 크기가 0인 파일에서는 작동하지 않습니다.

명백한 WinAPI 호출이 누락되었나요?GetActualPathName() 또는 GetFullPathName()과 같은 가장 간단한 것들은 전달된 대소문자를 사용하여 이름을 반환합니다(예:전달된 경우 "프로그램 파일"이어야 하는 경우에도 "프로그램 파일"을 반환합니다.

.NET 솔루션이 아닌 기본 솔루션을 찾고 있습니다.

도움이 되었습니까?

해결책

그리고 이로써 나는 내 자신의 질문에 대답합니다. 원래 답변 cspirz.

다음은 절대, 상대 또는 네트워크 경로가 주어지면 Windows에 표시되는 것처럼 대문자/소문자로 경로를 반환하는 함수입니다.경로의 일부 구성 요소가 존재하지 않으면 해당 지점에서 전달된 경로를 반환합니다.

네트워크 경로 및 기타 극단적인 경우를 처리하려고 하기 때문에 상당히 복잡합니다.이는 와이드 문자열에서 작동하며 std::wstring을 사용합니다.예, 이론적으로 유니코드 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;
}

SHGetFileInfo를 알려준 cspirz에게 다시 한 번 감사드립니다.

다른 팁

사용해 보셨나요? SH파일정보 가져오기?

또 다른 해결책이 있습니다.먼저 GetShortPathName()을 호출한 다음 GetLongPathName()을 호출합니다.그러면 어떤 문자 대소문자가 사용될지 추측해 보세요.;-)

좋아, 이것은 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

적어도 올바른 방향을 제시해주기를 바랍니다.

방금 발견한 것은 Scripting.FileSystemObject 10년 전 @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

path의 마지막 부분을 다음으로 바꾸면 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

장점:

  • 이 코드는 Windows 95 이후 모든 Windows 버전에서 작동합니다.
  • 기본 오류 처리.
  • 가능한 최고의 성능. FindFirstFile() 매우 빠르며 직접 버퍼 조작을 통해 더욱 빨라집니다.
  • C와 순수 WinAPI만 있으면 됩니다.작은 실행 파일 크기.

단점:

  • 절대 경로만 지원되며 다른 경로는 정의되지 않은 동작입니다.
  • 문서화되지 않은 동작에 의존하고 있는지 확실하지 않습니다.
  • 어떤 사람들에게는 코드가 너무 원시적일 수도 있고 DIY가 너무 많을 수도 있습니다.당신을 화나게 할 수도 있습니다.

코드 스타일의 이유:

나는 사용한다 goto 나는 그것에 익숙했기 때문에 오류 처리를 위해 (goto C의 오류 처리에 매우 편리합니다.)나는 사용한다 for 다음과 같은 기능을 수행하는 루프 strcpy 그리고 strchr 실제로 무엇이 실행되었는지 확인하고 싶기 때문입니다.

FindFirstFileNameW 몇 가지 단점이 있지만 작동합니다.

  • UNC 경로에서는 작동하지 않습니다
  • 드라이브 문자가 제거되므로 다시 추가해야 합니다.
  • 파일에 대한 하드 링크가 두 개 이상 있는 경우 올바른 링크를 식별해야 합니다.

간단한 테스트를 거쳐, GetLongPathName() 당신이 원하는 것을합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top