【问题标题】:Handling wide char values returned by Win32::API处理 Win32::API 返回的宽字符值
【发布时间】:2017-11-13 07:23:08
【问题描述】:

wide char and win32::api 中提供的答案适用于 utf-16 传递给 Win API。但是如何转换 Win API 返回的 utf16 字符串? (我正在尝试使用GetCommandLineW)。

我尝试了Unicode::StringEncode::decode 都没有成功。我猜可能需要先打包或解包数据,但是如何?

之后,下一个问题是如何处理像CommandLineToArgvW返回的那种指向utf16的指针。

感谢您的帮助。

【问题讨论】:

  • 请发布您尝试过的代码、您的预期以及您得到的代码。
  • @andlabs,我理解这个问题,而且我知道 OP 可以提供的东西真的不多。我正在写答案。
  • “我已经尝试过Unicode::StringEncode::decode 请包含代码以准确显示您尝试过的内容并描述您遇到的问题。它将帮助我们写出更准确的答案,并且您的问题的主要价值是对许多其他可能正在寻找类似问题的解决方案的人。 “没有成功”并不是一个问题陈述,而且无法判断您的情况是否与此相符。

标签: perl winapi


【解决方案1】:

当您指定返回值为字符串时,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 位版本都是正确的。
  • 非常感谢您提供如此清晰而有用的实现。它很好地展示了有效使用 Win32::API 所需的概念。我写了一个 decode_LPCWSTR() 的替代品,对于大多数用途来说可能足够有效:code sub decode_LPCWSTR { state $lstrlenW = Win32::API->new('kernel32', 'lstrlenW', PTR_WIN32API_TYPE, 'N') 或死($^E);我的 ($ptr) = @_;如果 !$ptr; 则返回 undef我的 $nchars = $lstrlenW->Call($ptr);返回 '​​' 如果 $nchars == 0;我的 $sW = ReadMemory($ptr, $nchars * 2);返回解码('UTF-16le',$sw); }
  • 确实如此。我将用那个替换我的答案!
  • (有人可以格式化我的回复然后删除它吗?我无法让“code”工作。谢谢。)
  • 1) 我们无法编辑 cmets,2) 无法格式化 cmets 中的代码。 3) 它已被纳入我的答案,因此它在 cmets 中的可读性没有实际意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 2021-08-09
  • 2011-05-08
  • 2013-09-30
  • 1970-01-01
相关资源
最近更新 更多