关于C#:在Windows上获取实际文件名(带有正确的大小写)

Getting actual file name (with proper casing) on Windows

Windows文件系统不区分大小写。在给定文件/文件夹名称(例如" somefile ")的情况下,如何获取该文件/文件夹的实际名称(例如,如果资源管理器显示该文件/文件夹的名称,则它应返回" SomeFile ")?

我知道一些方式,所有这些方式似乎都非常落后:

  • 给定完整路径,请搜索路径上的每个文件夹(通过FindFirstFile)。这样可以为每个文件夹提供适当的大小写结果。在最后一步,搜索文件本身。
  • 从句柄获取文件名(如MSDN示例)。这需要打开文件,创建文件映射,获取文件名,解析设备名称等。非常复杂。它不适用于文件夹或大小为零的文件。
  • 我缺少一些明显的WinAPI调用吗?最简单的名称(如GetActualPathName()或GetFullPathName())使用传入的大小写返回名称(例如,如果传入,则返回" program files ",即使它应为" Program Files ")。铅>

    我正在寻找一种本机解决方案(不是.NET)。


    在此,我根据cspirz的原始答案回答我自己的问题。

    这是一个给定绝对,相对或网络路径的函数,该路径将返回大小写的路径,就像在Windows上显示的那样。如果路径的某些组件不存在,它将从该点返回传入的路径。

    它非常复杂,因为它试图处理网络路径和其他边缘情况。它对宽字符串进行操作,并使用std :: wstring。是的,理论上Unicode TCHAR可能与wchar_t不同。这是读者的练习:)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    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\
    estOfPath), 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()。猜猜将使用哪种字符大小写? ;-)


    好的,这是VBScript,但是即使如此,我还是建议使用Scripting.FileSystemObject对象

    1
    2
    3
    4
    5
    Dim fso
    Set fso = CreateObject("Scripting.FileSystemObject")
    Dim f
    Set f = fso.GetFile("C:\\testfile.dat") 'actually named"testFILE.dAt"
    wscript.echo f.Name

    我从此代码段得到的响应是

    1
    testFILE.dAt

    希望至少可以将您指向正确的方向。


    刚刚发现@bugmagnet在10年前提出的Scripting.FileSystemObject是宝藏。与我的旧方法不同,它适用于绝对路径,相对路径,UNC路径和超长路径(比MAX_PATH长的路径)。对我很遗憾,因为没有更早测试他的方法。

    为便于将来参考,我想介绍一下可以在C和C模式下进行编译的代码。在C模式下,代码将使用STL和ATL。在C模式下,您可以清楚地看到一切在幕后如何进行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    #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!\
    "
    , hr);
    }

    static void Test(LPCWSTR pszPath)
    {
        LPWSTR pszRet = NULL;
        HRESULT hr = CorrectPathCasing2(pszPath, &pszRet);
        if (FAILED(hr)) {
            wprintf(L"Input: <%s>\
    "
    , pszPath);
            CoutErr(hr);
        }
        else {
            wprintf(L"Was: <%s>\
    Now: <%s>\
    "
    , pszPath, pszRet);
            HeapFree(GetProcessHeap(), 0, pszRet);
        }
    }


    #else // Use C++ STL and ATL
    #   include <iostream>
    #   include <iomanip>
    #   include <string>
    #   include

    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 <<"\
    "
    ;
    }

    static void Test(std::wstring const &path)
    {
        std::wstring output;
        HRESULT hr = CorrectPathCasing2(path, output);
        if (FAILED(hr)) {
            std::wcout << L"Input: <" << path <<">\
    "
    ;
            CoutErr(hr);
        }
        else {
            std::wcout << L"Was: <" << path <<">\
    "

                <<"Now: <" << output <<">\
    "
    ;
        }
    }

    #endif


    static void TestRoutine(void)
    {
        HRESULT hr = CoInitialize(NULL);

        if (FAILED(hr)) {
            Cout("CoInitialize failed!\
    "
    );
            CoutErr(hr);
            return;
        }

        Cout("\
    [ Absolute Path ]\
    "
    );
        Test(L"c:\\\\uSers\\\
    ayMai\\\\docuMENTs"
    );
        Test(L"C:\\\\WINDOWS\\\\SYSTEM32");

        Cout("\
    [ Relative Path ]\
    "
    );
        Test(L".");
        Test(L"..");
        Test(L"\");

        Cout("
    \
    [ UNC Path ]\
    ");
        Test(L"
    \\\\\\\\VMWARE-HOST\\\\SHARED FOLDERS\\\\D\\\\PROGRAMS INSTALLER");

        Cout("
    \
    [ Very Long Path ]\
    ");
        Test(L"
    \\\\\\\\?\\\\C:\\\\VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME\"
            L"
    VERYVERYVERYLOOOOOOOONGFOLDERNAME");

        Cout("
    \
    !! Worth Nothing Behavior !!\
    ");
        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

    假设路径始终是绝对路径(文本长度不变),我们可以将此"算法"应用于路径的每个部分(驱动器号部分除外)。

    通话很便宜,这是代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    #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。可执行文件较小。

    缺点:

    • 仅支持绝对路径,其他是未定义的行为。
    • 不知道它是否依赖未记录的行为。
    • 对于某些人来说,该代码可能太原始,太多了。可能会让您失望。

    代码风格背后的原因:

    我使用goto进行错误处理,因为我已经习惯了(goto对于C语言中的错误处理非常方便)。我使用for循环来即时执行strcpystrchr之类的功能,因为我想确定实际执行的内容。


    FindFirstFileNameW将具有一些缺点:

    • 在UNC路径上不起作用
    • 它会剥去驱动器号,因此您需要重新添加驱动器号
    • 如果您的文件有多个硬链接,则需要确定正确的链接

    快速测试后,GetLongPathName()完成了您想要的操作。


    推荐阅读

      linux文件异或命令?

      linux文件异或命令?,数字,系统,工作,管理,命令,数据,网络,文件,第一,单位,基

      linux文件复制的命令?

      linux文件复制的命令?,系统,文件,命令,目录,源文件,基本知识,位置,目标,选

      linux复制命令文件?

      linux复制命令文件?,系统,文件,命令,目录,基本知识,源文件,目标,文件夹,路

      linux下文件均分命令?

      linux下文件均分命令?,管理,情况,系统,工作,信息,地址,命令,目录,单位,设备,L

      linux查文件数量命令?

      linux查文件数量命令?,系统,数据,电脑,命令,文件,信息,代码,对比,软件,第三,l

      linux命令去重文件?

      linux命令去重文件?,系统,工作,命令,信息,数据,环境,代码,文件,目录,操作,Lin

      linux匹配文件名命令?

      linux匹配文件名命令?,系统,时间,发行,位置,工具,软件,名称,盘后,电脑,盘中,l

      改文件名linux命令?

      改文件名linux命令?,名字,软件,文件,命令,位置,系统,文件名,目录,指令,方面,l

      linux命令文件加锁?

      linux命令文件加锁?,数据,密码,系统,设备,代码,地址,名单,信息,数字,统一,请

      linux拼接文件命令?

      linux拼接文件命令?,文件,数据,命令,代码,时间,信息,系统,情况,管理,标准,Lin

      linux文件常用命令?

      linux文件常用命令?,工作,地址,信息,系统,命令,目录,标准,情况,管理,常用命

      文件写入linux命令?

      文件写入linux命令?,文件,命令,状态,系统,名称,时间,首次,数据,数字,内容,在l

      linux命令写满文件?

      linux命令写满文件?,地址,工作,命令,系统,管理,文件,目录,标准,电脑,信息,Lin

      文件夹排序linux命令?

      文件夹排序linux命令?,系统,数字,信息,工作,时间,命令,管理,设备,单位,工具,

      linux打开文件夹命令?

      linux打开文件夹命令?,工作,系统,信息,命令,图片,文件,管理,发行,名字,名称,

      linux上清空文件命令?

      linux上清空文件命令?,系统,命令,文件夹,名字,档案,文件,目录,方法,终端,指

      linux中历史命令文件?

      linux中历史命令文件?,系统,地址,信息,数字,时间,命令,数据,环境,历史,文件,l

      linux命令拷贝文件?

      linux命令拷贝文件?,系统,文件,命令,目录,情况,源文件,目标,文件夹,选项,语

      linux比对文件命令?

      linux比对文件命令?,系统,对比,第一,管理,工作,命令,文件,网络,名称,标准,lin

      linux命令分大小写吗?

      linux命令分大小写吗?,系统,名称,命令,大小写,设备,目录,文件名,变量,语言,