3
\$\begingroup\$

I'm building a small Delphi application where a user can input a file or directory manually by typing it or by selecting it from a dialog . Since it can be input manually, I want to ensure that I save that path as it is on the file system. For example: the user input c:\program files, the path is updated to C:\Program Files since it's how it's stored in the file system.

Here are the my requirements for the function:

  • The input can be a regular path, an UNC path or a \\?\ path.
  • If an invalid (or non-existent) path is provided, the function returns an empty string.
  • The function supports Unicode paths.
  • The function supports paths longer than MAX_PATH (yes it's possible).
  • The function returns "regular" paths (non \\?\ paths).

I'm coming back to Windows development under Delphi after a decade long pause (I've been working on Web services for a while now) so I'm out of touch with the Windows API and Delphi a bit.

Here is my current (working) function. Do you see anything wrong with it? Especially with GetFinalPathNameByHandle (lpszFilePath, cchFilePath and dwFlags). I also got a suggestion to do a double call to avoid using 64K for FilePath but I don't see how I could optimize memory usage for that part.

function GetCaseSensitivePath(const APath: String): String;

const
    EXT_MAX_PATH = 32767;
    UNC_PREFIX = '\\';
    UNIFIED_PREFIX = '\\?\';
    UNIFIED_UNC_PREFIX = UNIFIED_PREFIX + 'UNC\';

var
    UnifiedPath: PWideChar;
    LinkHandle: THandle;
    FilePath: array [0..EXT_MAX_PATH] of WideChar;

begin
    if APath.StartsWith(UNIFIED_PREFIX) then
        UnifiedPath := PWideChar(APath)
    else
        UnifiedPath := PWideChar(IfThen(APath.StartsWith(UNC_PREFIX), UNIFIED_UNC_PREFIX + APath.Substring(Length(UNC_PREFIX)), UNIFIED_PREFIX + APath));

    LinkHandle := CreateFile(UnifiedPath, 0, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
    try
        Win32Check(LinkHandle <> INVALID_HANDLE_VALUE);
        try
            if GetFinalPathNameByHandle(LinkHandle, FilePath, Length(FilePath), VOLUME_NAME_DOS) > 0 then
            begin
                Result := FilePath;
                if UpperCase(Result.Substring(0, Length(UNIFIED_UNC_PREFIX))) = UNIFIED_UNC_PREFIX then
                    Result := UNC_PREFIX + Result.Substring(Length(UNIFIED_UNC_PREFIX))
                else if Result.StartsWith(UNIFIED_PREFIX) then
                    Result := Result.Substring(Length(UNIFIED_PREFIX));
            end
            else
                RaiseLastOSError;
        finally
            CloseHandle(LinkHandle);
        end;
    except
        on EOSError do
            Result := '';
    end;
end;
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

As many Windows API functions work since 90's - you can call a function with zero size buffer first - function will fail, but will return needed buffer size, then you create your buffer with required size and call the function again with real buffer. In your case - you can make FilePath dynamic array and resize it between two calls of GetFinalPathNameByHandle.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.