在 Windows 上获取实际文件名(带有正确的大小写)
Windows 文件系统不区分大小写.如何,给定文件/文件夹名称(例如somefile"),我如何获得该文件/文件夹的实际名称(例如,如果资源管理器显示它,它应该返回SomeFile")?
Windows file system is case insensitive. How, given a file/folder name (e.g. "somefile"), I get the actual name of that file/folder (e.g. it should return "SomeFile" if Explorer displays it so)?
我所知道的一些方法,所有这些方法看起来都很落后:
Some ways I know, all of which seem quite backwards:
- 给定完整路径,搜索路径上的每个文件夹(通过 FindFirstFile).这给出了每个文件夹的正确大小写结果.在最后一步,搜索文件本身.
- 从句柄中获取文件名(如 MSDN 示例中所示)一>).这需要打开一个文件、创建文件映射、获取它的名称、解析设备名称等.相当复杂.它不适用于文件夹或零大小文件.
我是否遗漏了一些明显的 WinAPI 调用?最简单的,如 GetActualPathName() 或 GetFullPathName() 使用传入的大小写返回名称(例如,如果传入,则返回程序文件",即使它应该是程序文件").
Am I missing some obvious WinAPI call? The simplest ones, like GetActualPathName() or GetFullPathName() return the name using casing that was passed in (e.g. returns "program files" if that was passed in, even if it should be "Program Files").
我正在寻找本机解决方案(不是 .NET 解决方案).
I'm looking for a native solution (not .NET one).
推荐答案
我在此回答我自己的问题,基于 来自 cspirz 的原始答案.
And hereby I answer my own question, based on original answer from cspirz.
这是一个给定绝对路径、相对路径或网络路径的函数,它将返回大写/小写的路径,就像它在 Windows 上显示的那样.如果路径的某个组件不存在,它将返回从该点传入的路径.
Here's a function that given absolute, relative or network path, will return the path with upper/lower case as it would be displayed on Windows. If some component of the path does not exist, it will return the passed in path from that point.
它非常复杂,因为它试图处理网络路径和其他边缘情况.它对宽字符串进行操作并使用 std::wstring.是的,理论上 Unicode TCHAR 可能与 wchar_t 不同;这是读者的练习:)
It is quite involved because it tries to handle network paths and other edge cases. It operates on wide character strings and uses std::wstring. Yes, in theory Unicode TCHAR could be not the same as wchar_t; that is an exercise for the reader :)
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 (\servershareRestOfPath), getting the display
// name mangles it into unusable form (e.g. "\servershare" 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.
Again, thanks to cspirz for pointing me to SHGetFileInfo.
相关文章