First, many thanks to $Bill Luebkert who suggested using pack/unpack to solve the problem. "unpack" against the $FileInfo buffer did the trick.

Using Data::Dumper to look at the contents of the $FileInfo / WIN32_FIND_DATAW structure returned by the FindFirstFileW and FindNextFileW calls, I noticed that it included a "buffer" hash element that contained the filename that I wanted to retrieve (and all of the other data). So the solution was to unpack the buffer hash element, $FileInfo->{buffer} to retrieve the "cFileName" and "cAlternateFileName" elements in UTF-16LE format (which can then be passed to a Unicode::String object). The unpack template "x44A520A28" (skip first 44 bytes, copy 520 bytes, copy 28 bytes) very nearly does the trick. The one slight problem is that it cuts off the trailing null character when the last unicode character in the filename is something like "s\0" as it is with an Excel filename that ends in ".xls". Thinking about this though, the solution below of just blindly concatenating a null byte will not work if the filename ends in an extended two byte character as opposed to an ANSI character (which is in the first byte followed by a null byte). Well... fixing that is for another time ... after studying unpack in a lot more detail (which I've always tried to avoid).

One observation about Win32::API::Struct. As sizeof(WCHAR) is 2, I would think that "WCHAR cFileName[520];" would allocate 1,040 bytes. But it doesn't -- it allocates 520 bytes just like "TCHAR cFileName[520];" and sizeof(TCHAR) is 1 byte. Odd.

Anyway, for those who are interested, the updated sample code follows. And please beware, it has not been adequately tested. Many thanks to all who responded.


Regards,


... Dewey



use strict;
use Win32::API;
use Unicode::String;
#use Devel::Peek;
#use Data::Dumper; $Data::Dumper::Indent=1; $Data::Dumper::Sortkeys=1;
#use Encode qw(encode decode);
use English;

$OUTPUT_AUTOFLUSH=1;

$Win32::API::DEBUG = 0;

binmode(STDOUT, ":utf8");

use constant ERROR_NO_MORE_FILES  => 18;
use constant INVALID_HANDLE_VALUE => -1;

Win32::API::Struct-> typedef('FILETIME', qw(
  DWORD dwLowDateTime;
  DWORD dwHighDateTime;
));                             # 8 bytes

use constant FILE_ATTRIBUTE_READONLY =>  0x00000001;
use constant FILE_ATTRIBUTE_HIDDEN =>  0x00000002;
use constant FILE_ATTRIBUTE_SYSTEM =>  0x00000004;
use constant FILE_ATTRIBUTE_DIRECTORY =>  0x00000010;
use constant FILE_ATTRIBUTE_ARCHIVE =>  0x00000020;
use constant FILE_ATTRIBUTE_NORMAL =>  0x00000080;
use constant FILE_ATTRIBUTE_TEMPORARY =>  0x00000100;
use constant FILE_ATTRIBUTE_COMPRESSED =>  0x00000800;
use constant MAX_PATH =>  260;

Win32::API::Struct-> typedef('WIN32_FIND_DATAW', qw(
  DWORD dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD nFileSizeHigh;
  DWORD nFileSizeLow;
  DWORD dwReserved0;
  DWORD dwReserved1;
  WCHAR cFileName[520];
  WCHAR cAlternateFileName[28];
));             # 4 + 8 x 3 + 4 x 4 + 520 + 28 = 592 bytes

my $FindFirstFile = Win32::API->new('kernel32.dll', 'FindFirstFileW', 'PS', 'N') or die "FindFirstFile: $^E"; my $FindNextFile = Win32::API->new('kernel32.dll', 'FindNextFileW', 'NS', 'I') or die "FindNextFile $^E"; my $FindClose = Win32::API->new('kernel32.dll', 'FindClose', 'N', 'I') or die "FileClose $^E";

my $FileSpec = "//?/C:/My Documents/IBM/SACA/Tool/*.xls";

my $FileInfo = Win32::API::Struct-> new('WIN32_FIND_DATAW');

my $uFileSpec = Unicode::String->new;
$uFileSpec->utf8($FileSpec);
print "FileSpec = ", $uFileSpec->as_string, "\n";

my $handle = $FindFirstFile-> Call($uFileSpec->utf16le, $FileInfo);

if ($handle == INVALID_HANDLE_VALUE) {
        printf "Error is %d - %s\n", Win32::GetLastError (),
          Win32::FormatMessage (Win32::GetLastError ());
        exit(1);
} else {
        print "FindFirstFileW worked\n";
        
        my $count = 1;
        
        my $ufn = Unicode::String->new;
        my $ualtfn = Unicode::String->new;

my ($cFileName, $cAlternateFileName) = unpack( "x44A520A28", $FileInfo->{buffer} );
        
        $cFileName .= "\x00";
        $cAlternateFileName .= "\x00";
        
        $ufn->utf16le($cFileName);
        $ualtfn->utf16le($cAlternateFileName);
        
        print "($count) filename = ", $ufn->as_string, "\n";
        print "\talt = ", $ualtfn->as_string, "\n";
        
        while (my $result = $FindNextFile->Call($handle,$FileInfo)) {
                
                $count++;
                
($cFileName, $cAlternateFileName) = unpack( "x44A520A28", $FileInfo->{buffer} );
                
                $cFileName .= "\x00";
                $cAlternateFileName .= "\x00";
                
                $ufn->utf16le($cFileName);
                $ualtfn->utf16le($cAlternateFileName);
        
                print "($count) filename = ", $ufn->as_string, "\n";
                print "\talt = ", $ualtfn->as_string, "\n";
        }
}

$FindClose->Call($handle) or die "FindClose $^E";

Reply via email to