刚刚发现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
这样的:
如果我们用 替换 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;
}
好处:
- 该代码适用于自 Windows 95 以来的所有 Windows 版本。
- 基本错误处理。
- 可能的最高性能。
FindFirstFile()
非常快,直接缓冲区操作使其更快。
- 只是 C 和纯 WinAPI。小可执行文件大小。
缺点:
- 仅支持绝对路径,其他是未定义的行为。
- 不确定它是否依赖于无证行为。
- 对于某些人来说,代码可能太原始了太多 DIY。可能会让你火上浇油。
代码风格背后的原因:
我goto
用于错误处理是因为我已经习惯了(goto
对于 C 中的错误处理非常方便)。我使用for
循环来执行类似strcpy
和strchr
即时的功能,因为我想确定实际执行了什么。