Только что обнаружили, что 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, ¶ms, &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;
}
Скриншот:
Старый ответ:
Я обнаружил, что FindFirstFile()
вернет правильное имя файла оболочки (последняя часть пути) в fd.cFileName
. Если мы передадим c:\winDOWs\exPLORER.exe
в качестве первого параметра FindFirstFile()
, fd.cFileName
будет explorer.exe
следующим образом:
Если мы заменим последнюю часть пути на 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;
}
Преимущества:
- Код работает на всех версиях Windows начиная с Windows 95.
- Базовая обработка ошибок.
- Максимально возможная производительность.
FindFirstFile()
очень быстро, прямое управление буфером делает его еще быстрее.
- Просто C и чистый WinAPI. Маленький исполняемый размер.
Недостатки:
- Поддерживается только абсолютный путь, другие - неопределенное поведение.
- Не уверен, полагается ли это на недокументированное поведение.
- Для некоторых людей код может быть слишком необработанным. Возможно, тебя зажгут.
Причина стиля кода:
Я использую goto
для обработки ошибок, потому что я к этому привык (goto
очень удобен для обработки ошибок в C). Я использую цикл for
для выполнения таких функций, как strcpy
и strchr
на лету, потому что я хочу быть уверенным в том, что на самом деле было выполнено.