19

Windowsファイルシステムでは大文字と小文字は区別されません。ファイル/フォルダ名(「somefile」など)を指定すると、そのファイル/フォルダの実際の名前を取得できます(たとえば、Explorerに「SomeFile」と表示された場合は「SomeFile」が返されます)。

私が知っているいくつかの方法、それらはすべてかなり逆に見えます:

  1. フルパスを指定して、パス上の各フォルダーを検索します(FindFirstFileを使用)。これにより、各フォルダーの適切なケースの結果が得られます。最後のステップで、ファイル自体を検索します。
  2. ハンドルからファイル名を取得します(MSDNの例のように)。これには、ファイルを開く、ファイルマッピングを作成する、ファイル名を取得する、デバイス名を解析するなどが必要です。かなり複雑です。また、フォルダやサイズがゼロのファイルでは機能しません。

明らかなWinAPI呼び出しがありませんか?GetActualPathName()やGetFullPathName()のような最も単純なものは、渡された大文字小文字を使用して名前を返します(たとえば、「プログラムファイル」である必要がある場合でも、渡された場合は「プログラムファイル」を返します)。

ネイティブソリューション(.NETソリューションではない)を探しています。

4

7 に答える 7

5

これにより、 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;
}

繰り返しになりますが、SHGetFileInfo を教えてくれた cspirz に感謝します。

于 2008-09-17T09:35:02.750 に答える
4

SHGetFileInfoを試してみましたか?

于 2008-09-16T17:04:32.523 に答える
3

別の解決策があります。最初に GetShortPathName() を呼び出し、次に GetLongPathName() を呼び出します。その場合、どの文字ケースが使用されると思いますか? ;-)

于 2011-03-27T21:36:14.183 に答える
2

これは 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

少なくともあなたを正しい方向に向けてくれることを願っています。

于 2008-09-16T17:01:44.747 に答える
1

Scripting.FileSystemObject10 年前に @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;
}

スクリーンショット:

スクリーンショット2


古い答え:

FindFirstFile()で適切なケーシング ファイル名 (パスの最後の部分) を返すことがわかりましたfd.cFileNamec:\winDOWs\exPLORER.exeに最初のパラメーターとして渡すとFindFirstFile()、次のようfd.cFileNameになります。explorer.exe

証明

path の最後の部分を に置き換えると、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 95 以降のすべてのバージョンの Windows で動作します。
  • 基本的なエラー処理。
  • 可能な限り最高のパフォーマンス。FindFirstFile()は非常に高速ですが、バッファを直接操作するとさらに高速になります。
  • C と純粋な WinAPI だけです。実行可能サイズが小さい。

短所:

  • 絶対パスのみがサポートされ、その他は未定義の動作です。
  • 文書化されていない動作に依存しているかどうかはわかりません。
  • 一部の人にとっては、コードがあまりにも生々しく、あまりにも DIY である可能性があります。あなたを炎上させるかもしれません。

コード スタイルの背後にある理由:

私はgotoそれに慣れていたので、エラー処理に使用します ( gotoC でのエラー処理には非常に便利です)。for実際に何が実行されたかを確認したいので、ループを使用してオンザフライなどの機能を実行しますstrcpystrchr

于 2017-11-17T14:47:30.170 に答える
-3

簡単なテストの後、GetLongPathName()は希望どおりに動作します。

于 2008-09-16T17:04:38.670 に答える