Hi José,
I am sharing a new function (code below) that I wrote the other day. There are times when you have a full path filename in some type of letter casing but you want to interact with that filename in its original casing per the way it is stored in Windows.
For example, I am integrating the GDB Debugger into Tiko and GDB returns filenames back to Tiko in UPPERCASE format. Sure, I could use that uppercase file and display it in uppercase but visually it looks much more pleasing for the filename to display within the Tiko Editor using the original case.
A filename like:
C:\DEV\TIKO_EDITOR_DEBUGGER\DEBUG-EXAMPLE\TESTFILE2.BAS
Gets converted to:
C:\dev\tiko_editor_debugger\Debug-Example\TestFile2.bas
This code might be interesting to add to AfxNova if you also find it worthwhile.
' ========================================================================================
' Get the original case filename as stored by Windows
' ========================================================================================
function AfxFilenameOriginalCase( byval filename as DWSTRING ) as DWSTRING
dim as long bufSize = 1024
dim as DWSTRING buffer = wspace(bufSize \ 2)
dim as HANDLE hfile = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, _
null, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, null)
if hfile = INVALID_HANDLE_VALUE then exit function
dim as DWORD result = GetFinalPathNameByHandleW( hfile, *buffer, bufSize, FILE_NAME_NORMALIZED )
if (result > 0) andalso (result < bufSize) then
' Good data returned but still need to remove the mapping characters
if left(buffer, 4) = "\\?\" then buffer = mid(buffer, 5)
else
' error: could not retrieve path.
end if
' clean up
CloseHandle(hfile)
function = buffer
end function
Hi Paul,
I didn't forget about the function you proposed earlier.
The reason I didn't implement it immediately is that something important was missing, and I needed to study the official documentation for GetFinalPathNameByHandleW first.
According to Microsoft's documentation , the function's return value is the length of the string in TCHARs, excluding the terminating null character. This detail affects how the buffer must be allocated and how the result must be trimmed. Without understanding this behavior precisely, the function could return incorrect or partially truncated paths, especially when dealing with long filenames or UNC paths.
Once I reviewed the documentation carefully, I was able to implement a correct and robust version:
- dynamic buffer resizing based on the exact size required
- proper handling of the \\?\ and \\?\UNC\ prefixes
- correct trimming of the returned string
- support for both files and directories
and a variant that accepts a HANDLE directly
So the delay wasn't because I forgot your suggestion — I simply needed to confirm how the API behaves internally before finalizing the implementation.
Thanks again for the original idea. It was a good starting point, and now the function works reliably in all cases.
I have added them to fbxWin.inc, using USTRING and DEFER.
' ========================================================================================
' Returns the original-case filename as stored by Windows.
' Uses GetFinalPathNameByHandleW with dynamic buffer, UNC handling,
' directory support, and automatic cleanup via DEFER.
' ========================================================================================
FUNCTION fbxGetFilenameOriginalCase OVERLOAD (BYREF wszFileName AS WSTRING) AS USTRING
DIM INITIAL_SIZE AS LONG = 1024
DIM hFile AS HANDLE
DIM dwNeeded AS DWORD
DIM dwFlags AS DWORD = FILE_NAME_OPENED
DIM ustrResult AS USTRING = WSPACE(INITIAL_SIZE \ 2)
' Open file or directory
hFile = CreateFileW(wszFileName, GENERIC_READ, _
FILE_SHARE_READ OR FILE_SHARE_WRITE OR FILE_SHARE_DELETE, _
NULL, OPEN_EXISTING, _
FILE_ATTRIBUTE_NORMAL OR FILE_FLAG_BACKUP_SEMANTICS, _
NULL)
IF hFile = INVALID_HANDLE_VALUE THEN RETURN ""
' Automatic cleanup
DEFER CloseHandle(hFile)
' First call: get required size
dwNeeded = GetFinalPathNameByHandleW(hFile, STRPTR(ustrResult), INITIAL_SIZE, dwFlags)
IF dwNeeded = 0 THEN RETURN ""
' Resize buffer if needed
IF dwNeeded >= INITIAL_SIZE THEN
ustrResult = WSPACE(dwNeeded + 1)
dwNeeded = GetFinalPathNameByHandleW(hFile, STRPTR(ustrResult), dwNeeded + 1, dwFlags)
IF dwNeeded = 0 THEN RETURN ""
END IF
' Trim to actual length (dwNeeded does not include the null terminator)
ustrResult = LEFT(ustrResult, dwNeeded)
' Remove \\?\ prefix
IF LEFT(ustrResult, 4) = $"\\?\" THEN
ustrResult = MID(ustrResult, 5)
ELSEIF LEFT(ustrResult, 7) = $"\\?\UNC\" THEN
ustrResult = $"\\" & MID(ustrResult, 7)
END IF
RETURN ustrResult
END FUNCTION
' ========================================================================================
' ========================================================================================
' Returns the original-case filename associated with a HANDLE.
' The HANDLE must refer to a file or directory opened with sufficient access rights.
' ========================================================================================
FUNCTION fbxGetFilenameOriginalCaseFromHandle OVERLOAD (BYVAL hFile AS HANDLE) AS USTRING
DIM INITIAL_SIZE AS LONG = 1024
DIM dwNeeded AS DWORD
DIM dwFlags AS DWORD = FILE_NAME_OPENED
DIM ustrResult AS USTRING = WSPACE(INITIAL_SIZE \ 2)
' Validate handle
IF hFile = INVALID_HANDLE_VALUE OR hFile = NULL THEN RETURN ""
' First call: get required size
dwNeeded = GetFinalPathNameByHandleW(hFile, STRPTR(ustrResult), INITIAL_SIZE, dwFlags)
IF dwNeeded = 0 THEN RETURN ""
' Resize buffer if needed
IF dwNeeded >= INITIAL_SIZE THEN
' dwNeeded = number of characters without the terminating null
ustrResult = WSPACE(dwNeeded + 1)
dwNeeded = GetFinalPathNameByHandleW(hFile, STRPTR(ustrResult), dwNeeded + 1, dwFlags)
IF dwNeeded = 0 THEN RETURN ""
END IF
' Trim to actual length (dwNeeded does not include the null terminator)
ustrResult = LEFT(ustrResult, dwNeeded)
' Remove \\?\ prefix
IF LEFT(ustrResult, 4) = $"\\?\" THEN
ustrResult = MID(ustrResult, 5)
ELSEIF LEFT(ustrResult, 7) = $"\\?\UNC\" THEN
ustrResult = $"\\" & MID(ustrResult, 7)
END IF
RETURN ustrResult
END FUNCTION
' ========================================================================================
Excellent - thanks José, appreciate it!