#------------------------------------------------------------------------------ # Rebuild a path as an absolute long path to be usable in Windows system calls # Inputs: 0) ExifTool ref, 1) path string # Returns: normalized long path # Note: this should only be called for Windows systems # References: # - https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats # - https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation # # GetFullPathName supported by Windows XP and later. It handles: # full path names EG: c:\foto\sub\abc.jpg # relative EG: .\abc.jpg, ..\abc.jpg # full UNC paths EG: \\server\share\abc.jpg # relative UNC paths EG: .\abc.jpg, ..\abc.jpg # Dos device paths EG: \\.\c:\fotoabc.jpg # relative path on other drives EG: z:abc.jpg (working dir on z: z:\foto called from c:\foto) # Wide chars EG: Chars that need UTF8. # # FrankB comment: Dont know exactly how Win32::API::More->new works, but I would imagine it # does a LoadLibrary and a GetProcAddress. So I only want to do that once. The variable # $GetFullPathName should by defined globally to achieve this. my $GetFullPathName; sub WindowsLongPath($$) { my ($self, $path) = @_; my $debug = $$self{OPTIONS}{Debug}; my $out = $$self{OPTIONS}{TextOut}; unless (eval { require Win32::API }) { $self->WarnOnce('Install Win32::API to use WindowsLongPath option'); return $path; } $debug and print $out "WindowsLongPath input : $path\n"; $path =~ tr(/)(\\); # convert slashes to backslashes if ($path =~ /^\\\\\?\\/) { # already a device path in the format we want $debug and print $out "WindowsLongPath return: (unchanged -- already prefixed)\n"; return $path; } unless (defined $GetFullPathName) { # Need to import (once) GetFullPathName? $GetFullPathName = Win32::API->new('KERNEL32', 'GetFullPathNameW', 'PNPP', 'I'); $debug and print $out "GetFullPathName loaded\n" if defined $GetFullPathName; } my $enc = $$self{OPTIONS}{CharsetFileName}; my $encPath = $self->Encode($path, 'UTF16', 'II', $enc); # Need to encode to UTF16 my $lenReq = $GetFullPathName->Call($encPath, 0, 0, 0) + 1; # first pass gets length required. Add +1 for safety, Needs Null terminator? my $fullPath = "\0" x $lenReq x 2; # create buffer to hold Full Path $GetFullPathName->Call($encPath, $lenReq, $fullPath, 0); # fullPath is UTF16 now $path = $self->Decode($fullPath, 'UTF16', 'II', $enc); # Decode if ($path =~ /^\\\\/) { $path = '\\\\?\UNC' . substr($path, 1) unless length($path) <= 247; } else { $path = '\\\\?\\' . $path unless length($path) <= 247; } $debug and print $out "WindowsLongPath return: $path\n"; return $path; }