GetFinalPathNameByHandleW
が使いたかったけど、XP には無かった…
…だけならわざわざ作りはしなかったけれども、c:\tmp の ディレクトリハンドルの GUID を取得した際に FindFirst/NextVolume で列挙したボリューム郡を指していない GUID を返してきたため、しぶしぶ作った。
static HRESULT GetVolumeNameForVolumeSerialNumberW(
const DWORD dwVolumeSerialNumber,
CStringW &result);
static HRESULT GetVolumePathNamesForVolumeNameW(
LPCWSTR szVolumeName,
std::vector<CStringW> &VolumePaths);
HRESULT GetFinalPathNameByHandleW2(HANDLE hFile, DWORD dwFlags, CStringW &result) {
BYTE data[sizeof(DWORD) + sizeof(WCHAR) * 32767]; // 32767 is a NTFS maximum filename length
const DWORD fileNameMask = FILE_NAME_NORMALIZED | FILE_NAME_OPENED;
const DWORD volumeNameMask = VOLUME_NAME_DOS | VOLUME_NAME_GUID | VOLUME_NAME_NT | VOLUME_NAME_NONE;
CStringW trail;
CStringW foundVolume;
BOOL rc;
HRESULT hr;
DWORD dwVolumeSerialNumber;
rc = GetFileInformationByHandleEx(hFile, FileNameInfo, &data, sizeof(data));
if (rc == FALSE) {
return HRESULT_FROM_WIN32(GetLastError());
}
{
FILE_NAME_INFO &fiName = (FILE_NAME_INFO &)data;
trail = CStringW(fiName.FileName, fiName.FileNameLength / sizeof(WCHAR));
if (trail[0] == L'\\') {
trail.Delete(0, 1);
}
switch (dwFlags & fileNameMask) {
case FILE_NAME_NORMALIZED:
/// ?('_.)? need change?
break;
case FILE_NAME_OPENED:
/// ?('_.)? need change?
break;
}
}
rc = GetVolumeInformationByHandleW(hFile,
NULL, 0,
&dwVolumeSerialNumber,
NULL,
NULL,
NULL, 0);
if (rc == FALSE) {
return HRESULT_FROM_WIN32(GetLastError());
}
hr = GetVolumeNameForVolumeSerialNumber(dwVolumeSerialNumber, foundVolume);
if (hr == S_OK) {
switch (dwFlags & volumeNameMask) {
case VOLUME_NAME_DOS:
// \\?\c:\ type.
{
std::vector<CStringW> VolumePaths;
GetVolumePathNamesForVolumeNameW(foundVolume, VolumePaths);
if (VolumePaths.size() == 0) {
// ?('_.)? no change?
break;
}
CStringW shortest;
DWORD lenShortest = (DWORD)(-1);
for (int i = 0; i < VolumePaths.size(); i++) {
size_t len = VolumePaths[i].GetLength();
if (len < lenShortest) {
lenShortest = len;
shortest = VolumePaths[i];
}
}
foundVolume = CStringW(L"\\\\?\\") + shortest;
}
break;
case VOLUME_NAME_GUID:
// \\?\Volume{guid}\ type. no change.
break;
case VOLUME_NAME_NT:
/// don't know format. 調べるのタルい.
throw "not impremented.";
break;
case VOLUME_NAME_NONE:
/// don't know format. 調べるのタルい.
throw "not impremented.";
break;
}
hr = S_OK;
}
else {
// maybe network file handle.
switch (dwFlags & volumeNameMask) {
case VOLUME_NAME_DOS:
// \\?\UNC\server\share\ type
hr = S_OK;
throw "not impremented.";
break;
case VOLUME_NAME_GUID:
/// error.
hr = E_FAIL;
break;
case VOLUME_NAME_NT:
/// don't know format. 調べるのタルい.
hr = S_OK;
throw "not impremented.";
break;
case VOLUME_NAME_NONE:
/// don't know format. 調べるのタルい.
hr = S_OK;
throw "not impremented.";
break;
}
}
if (SUCCEEDED(hr)) {
result = foundVolume;
if (result[result.GetLength() - 1] != L'\\') {
result += L"\\";
}
result += trail;
}
return hr;
}
static HRESULT GetVolumeNameForVolumeSerialNumber(const DWORD dwVolumeSerialNumber, CStringW &result) {
WCHAR VolumeNameBuffer[1024];
HANDLE hFindVolume = FindFirstVolume(VolumeNameBuffer, _countof(VolumeNameBuffer));
if (hFindVolume == NULL || hFindVolume == INVALID_HANDLE_VALUE) {
return HRESULT_FROM_WIN32(GetLastError());
}
DWORD err;
do {
DWORD vsn;
GetVolumeInformationW(VolumeNameBuffer,
NULL, 0,
&vsn,
NULL,
NULL,
NULL, 0);
if (vsn != dwVolumeSerialNumber) {
continue;
}
assert(GetLastError() == ERROR_MORE_DATA);
result = VolumeNameBuffer;
break;
}
while (FindNextVolumeW(hFindVolume, VolumeNameBuffer, _countof(VolumeNameBuffer)));
err = GetLastError();
FindVolumeClose(hFindVolume);
return
err == ERROR_NO_MORE_FILES ? HRESULT_FROM_WIN32(ERROR_NOT_FOUND) :
err == ERROR_MORE_DATA ? S_OK :
HRESULT_FROM_WIN32(err);
}
static HRESULT GetVolumePathNamesForVolumeNameW(
LPCWSTR szVolumeName,
std::vector<CStringW> &VolumePaths
) {
CHeapPtr<WCHAR> heapNames;
DWORD count;
SetLastError(ERROR_SUCCESS);
::GetVolumePathNamesForVolumeNameW(szVolumeName, NULL, 0, &count);
DWORD lastError = GetLastError();
if (lastError != ERROR_MORE_DATA) {
return HRESULT_FROM_WIN32(lastError);
}
if (heapNames.Allocate(count) == false) {
return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
}
SetLastError(ERROR_SUCCESS);
BOOL
rc = ::GetVolumePathNamesForVolumeNameW(szVolumeName, heapNames.m_pData, count, &count);
if (rc == FALSE) {
DWORD lastError = GetLastError();
return HRESULT_FROM_WIN32(lastError);
}
for (WCHAR *NameIdx = heapNames.m_pData;
NameIdx[0] != L'\0';
NameIdx += wcslen(NameIdx) + 1) {
VolumePaths.push_back(CStringW(NameIdx));
}
return S_OK;
}
###known issue.
\\localpc\share\dir\subdir\tatoeba.bmp
を開いた HANDLE を食わせてやると、本家では \\?\UNC\localpc\share\dir\subdir\tatoeba.bmp
を返してくるけれど、この関数では \\C:\test\sharedir\localpc\share\dir\subdir\tatoeba.bmp
を返してくる。
###follow link.
C++ - GetFinalPathNameByHandleW が 2000 でも使いたい