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";