AfxFilenameOriginalCase

Started by Paul Squires, January 05, 2026, 06:23:29 PM

Previous topic - Next topic

Paul Squires

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
Paul Squires
PlanetSquires Software

José Roca

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
' ========================================================================================

Paul Squires

Excellent - thanks José, appreciate it!
Paul Squires
PlanetSquires Software