处理Win32::API返回的宽字符值

5
wide char and win32::api中提供的答案适用于将utf-16传递给Win API。但是,如何转换Win API返回的utf16字符串?(我正在尝试使用GetCommandLineW)。
我尝试过Unicode::StringEncode::decode,但都没有成功。我猜可能需要先打包或解包数据,但是怎么做呢?
之后,下一个问题是如何处理指向指向utf16的指针,例如由CommandLineToArgvW返回的指针。
感谢任何帮助。

1
请发布您尝试过的代码,期望的结果以及实际得到的结果。 - andlabs
@andlabs,我理解这个问题,也知道原帖提供的信息已经很少了。我正在撰写答案。 - ikegami
我已经尝试过Unicode::StringEncode::decode。以下是我尝试的代码,并描述了我遇到的问题。这将有助于我们编写更准确的答案,而您的问题的主要价值在于可能正在寻找类似问题的许多其他人。use Unicode::String qw(utf8); my $string = "你好"; my $unicode_string = utf8($string);我尝试使用Unicode::String将字符串转换为Unicode格式,但它没有按预期工作。然后我尝试使用Encode::decode,但也没有成功。 - Borodin
1个回答

6

当您指定返回值为字符串时,Win32::API 假定它以值为 0 的字节终止,但是在 UTF-16le 文本中,具有该值的字节很常见。

如同 Win32::API 建议的那样,您应该使用 N 类型(或 64 位构建上的 Q)将指针作为数字返回,然后自己读取指向的内存。Win32::API 提供了 ReadMemory 来读取内存,但它需要知道要读取多少内存。对于以 NUL 终止的字符串和宽 NUL 终止的字符串,这没有用。

对于宽 NUL 终止的字符串,Win32::API 提供了 SafeReadWideCString。但是 SafeReadWideCString 可能会在出错时返回与输入无关的字符串,因此我会使用自己的 decode_LPCWSTR

use strict;
use warnings;
use feature qw( say state );

use open ':std', ':encoding('.do { require Win32; "cp".Win32::GetConsoleOutputCP() }.')';

use Config     qw( %Config );
use Encode     qw( decode encode );
use Win32::API qw( ReadMemory );

use constant PTR_SIZE => $Config{ptrsize};

use constant PTR_PACK_FORMAT =>
     PTR_SIZE == 8 ? 'Q'
   : PTR_SIZE == 4 ? 'L'
   : die("Unrecognized ptrsize\n");

use constant PTR_WIN32API_TYPE =>
     PTR_SIZE == 8 ? 'Q'
   : PTR_SIZE == 4 ? 'N'
   : die("Unrecognized ptrsize\n");

    
sub lstrlenW {
   my ($ptr) = @_;

   state $lstrlenW = Win32::API->new('kernel32', 'lstrlenW', PTR_WIN32API_TYPE, 'i')
      or die($^E);

   return $lstrlenW->Call($ptr);
}


sub decode_LPCWSTR {
   my ($ptr) = @_;
   return undef if !$ptr;

   my $num_chars = lstrlenW($ptr)
      or return '';

   return decode('UTF-16le', ReadMemory($ptr, $num_chars * 2));
}


# Returns true on success. Returns false and sets $^E on error.
sub LocalFree {
   my ($ptr) = @_;

   state $LocalFree = Win32::API->new('kernel32', 'LocalFree', PTR_WIN32API_TYPE, PTR_WIN32API_TYPE)
      or die($^E);

   return $LocalFree->Call($ptr) == 0;
}


sub GetCommandLine {
   state $GetCommandLine = Win32::API->new('kernel32', 'GetCommandLineW', '', PTR_WIN32API_TYPE)
      or die($^E);

   return decode_LPCWSTR($GetCommandLine->Call());
}


# Returns a reference to an array on success. Returns undef and sets $^E on error.
sub CommandLineToArgv {
   my ($cmd_line) = @_;

   state $CommandLineToArgv = Win32::API->new('shell32', 'CommandLineToArgvW', 'PP', PTR_WIN32API_TYPE)
      or die($^E);

   my $cmd_line_encoded = encode('UTF-16le', $cmd_line."\0");
   my $num_args_buf = pack('i', 0);  # Allocate space for an "int".

   my $arg_ptrs_ptr = $CommandLineToArgv->Call($cmd_line_encoded, $num_args_buf)
      or return undef;

   my $num_args = unpack('i', $num_args_buf);
   my @args =
      map { decode_LPCWSTR($_) }
         unpack PTR_PACK_FORMAT.'*',
            ReadMemory($arg_ptrs_ptr, PTR_SIZE * $num_args);

   LocalFree($arg_ptrs_ptr);
   return \@args;
}


{
   my $cmd_line = GetCommandLine();

   say $cmd_line;

   my $args = CommandLineToArgv($cmd_line)
      or die("CommandLineToArgv: $^E\n");

   for my $arg (@$args) {
      say "<$arg>";
   }
}

修复了在 Perl 的 32 位和 64 位版本中都正确的问题。 - ikegami
非常感谢您提供如此清晰有用的代码,它很好地展示了使用Win32::API的相关概念。我编写了一个decode_LPCWSTR()的替代函数,可能对大多数目的来说已经足够高效了: code sub decode_LPCWSTR { state $lstrlenW = Win32::API->new('kernel32', 'lstrlenW', PTR_WIN32API_TYPE, 'N') or die($^E); my ($ptr) = @_; return undef if !$ptr; my $nchars = $lstrlenW->Call($ptr); return '' if $nchars == 0; my $sW = ReadMemory($ptr, $nchars * 2); return decode('UTF-16le', $sW);
}
- Freon Sandoz
好的,我会用那个来替换我的回答中的那个! - ikegami
1
以下是与编程有关的内容,请将其从英语翻译成中文。请仅返回已翻译的文本,不要进行解释。 - Freon Sandoz
  1. 我们无法编辑评论。
  2. 无法在评论中格式化代码。
  3. 它已经被纳入我的答案中,因此在评论中的可读性是无关紧要的。
- ikegami

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接