【问题标题】:What's the correct way to assign PAnsiChar to a (unicode-) string?将 PAnsiChar 分配给(unicode-)字符串的正确方法是什么?
【发布时间】:2015-03-07 14:12:31
【问题描述】:

我有一个 DLL 函数,它返回一个指向 ANSI 文本 (PAnsiChar) 的指针。我想将其分配给(unicode-)字符串(这是Delphi XE2。)。以下编译但我收到警告 “W1057 隐式字符串从 'AnsiChar' 转换为 'string'”:

function TProj4.pj_strerrno(_ErrorCode: Integer): string;
var
  Err: PAnsiChar;
begin
  Err := Fpj_strerrno(_ErrorCode);
  Result := Err;
end;

编辑:有问题的文本是英文的错误消息,所以这里不太可能有任何转换问题。

我现在很想将 Err 显式类型转换为这样的字符串 ...

Result := String(Err);

.. 摆脱警告。这会出错吗?我应该改用临时 AnsiString 变量吗?

var
  s: AnsiString;
[...]
s := Err;
Result := String(s);

如果是,为什么?

或者我应该明确表示,代码首先将 PAnsiChar 转换为 AnsiString,然后将 AnsiString 转换为 String?

Result := String(AnsiString(Err));

当然我可以把它变成一个函数:

function PAnsicharToString(_a: PAnsiChar): string;
begin
   // one of the above conversion codes goes here
end;

所有这些选项都可以编译,但它们会起作用吗?这里的最佳做法是什么?

加分项:理想情况下,代码应该能够在 Delphi 2007 和更新版本中编译和工作。

【问题讨论】:

  • 您需要知道用于对 8 位文本进行编码的代码页。除非你知道你不能指望取得进步。
  • 本案例中的文字是英文的错误信息,所以不太可能引起任何问题。
  • 那么,文本是如何编码的呢?哪个代码页?你知道吗?如果您对“不太可能”感到满意,那您为什么想了解最佳实践?
  • 我猜@dummzeuch 试图说文本是纯 ASCII,因此不太可能包含任何不可转换的项目,无论代码页如何。但我们都知道土耳其语 EBCDIC (CP 1026) 与 ASCII-7 不兼容。

标签: delphi unicode delphi-xe2 delphi-2007 pchar


【解决方案1】:

我认为一般的解决方案是将c char指针分配给RawByteString,然后将其代码页设置为对应于c以null结尾的字符串编码。

var
  bys :TBytes;
  rbstr :RawByteString;
  ustr :string;
  pastr :PAnsiChar;
begin

  SetLength(bys,5);
  bys[0] := $ca;
  bys[1] := $e9;
  bys[2] := $d2;
  bys[3] := $b5;
  bys[4] := 0;

  pastr := @bys[0]; // just simulate char* returned by c api


  rbstr := pastr; // assign PAnsiChar to RawByteString
  // assume text encoded as codepage 936
  // Note here: set 3rd param to false!
  SetCodePage(rbstr,936,false);

  ustr := string(rbstr);
  ShowMessage(ustr);
end;

另外一个跨平台解决方案是(vcl,fmx,fmx with mobile platform)

function CString2TBytes(ptr :{$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}) :TBytes;
var
  pby :PByte;
  len :Integer;
begin
  pby := PByte(ptr);
  while pby^<>0 do Inc(pby);
  len := pby - ptr;
  SetLength(Result,len);
  if len>0 then Move(ptr^,Result[0],len);
end;

procedure TForm5.Button1Click(Sender: TObject);
var
  bys, cbys: TBytes;
  ustr: string;
  // PAnsiChar is undefined in mobile platform
  // remap param foo(outSting:PAnsiString) => foo(outString:MarshaledAString)
  ptr: {$IFDEF NEXTGEN} MarshaledAString {$ELSE} PAnsiChar {$ENDIF}; //
  encoding : TEncoding;
begin

  SetLength(bys, 5);
  bys[0] := $CA;
  bys[1] := $E9;
  bys[2] := $D2;
  bys[3] := $B5;
  bys[4] := 0;

  ptr := @bys[0]; // just simulate char* returned by c api

  cbys := CString2TBytes(ptr);

  // assume text encoded as codepage 936
  encoding := TEncoding.GetEncoding(936);
  try
    ustr := encoding.GetString(cbys);
    ShowMessage(ustr);
  finally
    encoding.Free;
  end;

end;

【讨论】:

  • 对于移动设备,您可以使用MarshaledAString 而不是Pointer。此外,TEncoding.GetEncoding() 返回一个新对象,该对象在桌面平台上必须是Free'd。而且您不能将普通的 PAnsiChar/Pointer 类型转换为 TBytes 并期望它正常工作,动态数组具有普通指针所没有的额外元数据,而 TEncoding.GetString() 使用该元数据(在特别是数组长度)。因此,您必须为TBytes 分配内存并将ptr 数据复制到其中。或者使用 Delphi 的 marshaling API 分配一个 DLL 可以写入的缓冲区。
  • @RemyLebeau 感谢您的评论。根据你所关心的,我只是修改了移动部分,使其更加健壮。使用“try finally free”块让编译器处理 ARC/Non-ARC。关于指向 TBytes 的指针,我没有找到任何读取的跨平台函数来计算它的长度,所以我创建了它。如果有人知道delphi中的任何已读函数,请告诉我。谢谢。移动部分在 XE 和 XE7 中进行了测试。
  • 我不明白这里为什么这么复杂
  • @DavidHeffernan 好吧,如果代码页与调用者 env 相同,并且可以使用 AnsiString,则可以非常简单。当然,UnicodeFromLocaleChars 应该可以正常工作,但是 IMO 它的参数很复杂,而且我很容易出错。只是另一种方式。 :)
  • UFLC 并不难。请记住,您的代码一开始就有错误。
【解决方案2】:

如果文本是在用户当前的语言环境中编码的,那么我想说这是最简单的写法:

var
  p: PAnsiChar;
  str: string;
....
str := string(p);

否则,如果您希望从特定代码页转换为 Unicode 字符串,则应使用 UnicodeFromLocaleChars

【讨论】:

  • 嗯,是的。我不知道代码页,因为 dll 上根本没有太多文档。我假设是 Windows-1252,因为这是不关心 Unicode 的人最广泛使用的。所以,我可能会按照你的建议只使用一个类型转换。
  • 这听起来不对。类型转换将使用可能不是 1252 的当前代码页。库在固定代码页中为您提供文本是不寻常的。更有可能的是文本是 ASCII 或在当前代码页中编码。无论哪种方式,简单的演员阵容都可以。我的猜测是,如果库没有声明代码页,那么文本会在当前代码页中编码。
  • 实际上如果是 US-ASCII 可能与当前的代码页不兼容。例如,我刚刚了解到代码页 1026(IBM EBCDIC 土耳其拉丁语 5)与 US-ASCII 不兼容。
  • 它在 MSDN 上的代码页标识符列表中:msdn.microsoft.com/en-us/library/windows/desktop/dd317756.aspx
  • @Asbjørn 是的,但是 65001 也是如此,你不能把它作为你的 ANSI 代码页。据我了解,所有 ANSI 代码页都包含 ASCII。
猜你喜欢
  • 2022-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 2020-07-31
  • 2020-08-07
相关资源
最近更新 更多