الحصول على اسم الملف الفعلي (مع الغلاف المناسب) على نظام التشغيل Windows

StackOverflow https://stackoverflow.com/questions/74451

  •  09-06-2019
  •  | 
  •  

سؤال

نظام ملفات Windows حساس لحالة الأحرف.كيف، مع إعطاء اسم ملف/مجلد (على سبيل المثال:"somefile")، أحصل على فِعلي اسم هذا الملف/المجلد (على سبيل المثال.يجب أن يُرجع "SomeFile" إذا عرضه Explorer بهذا الشكل)؟

أعرف بعض الطرق، وكلها تبدو متخلفة تمامًا:

  1. بالنظر إلى المسار الكامل، ابحث عن كل مجلد على المسار (عبر FindFirstFile).وهذا يعطي نتائج مناسبة لكل مجلد.في الخطوة الأخيرة، ابحث عن الملف نفسه.
  2. احصل على اسم الملف من المقبض (كما في مثال MSDN).يتطلب ذلك فتح ملف وإنشاء تعيين للملف والحصول على اسمه وتحليل أسماء الأجهزة وما إلى ذلك.معقدة جدا.ولا يعمل مع المجلدات أو الملفات ذات الحجم الصفري.

هل أفتقد بعض استدعاءات WinAPI الواضحة؟أبسطها، مثل GetActualPathName() أو GetFullPathName() تقوم بإرجاع الاسم باستخدام حالة الأحرف التي تم تمريرها (على سبيل المثال.تقوم بإرجاع "ملفات البرنامج" إذا تم تمريرها، حتى لو كان من المفترض أن تكون "ملفات البرنامج").

أنا أبحث عن حل أصلي (وليس حل .NET).

هل كانت مفيدة؟

المحلول

وبهذا أجيب على سؤالي الخاص، بناءً على ذلك الجواب الأصلي من com.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.

نصائح أخرى

هل حاولت استخدام شجيتفيلينفو؟

هناك حل آخر.قم أولاً باستدعاء 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 الذي اقترحه @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 سريعًا لأنني أريد التأكد مما تم تنفيذه بالفعل.

FindFirstFileNameW سوف تعمل مع بعض العيوب:

  • لا يعمل على مسارات UNC
  • يقوم بتجريد حرف محرك الأقراص لذا تحتاج إلى إضافته مرة أخرى
  • إذا كان هناك أكثر من رابط ثابت لملفك، فأنت بحاجة إلى تحديد الرابط الصحيح

بعد اختبار سريع GetLongPathName() يفعل ما تريد.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top