如何在Delphi XE2中使用新的字符串类型?

4
请注意,sarnold已经大幅编辑了这个问题;原始问题全部保留在问题的评论中。如果我某些地方表述不清楚,或许原始帖子会有所帮助。(我将其作为评论留下,以便未来的编辑者不需要总是参考问题的编辑历史。)
我正在使用Delphi Xe2,并需要帮助理解如何正确地使用ANSI字符串、Unicode字符串和宽字符字符串,特别是在编写一个旨在与其他语言(如VB、C++或C#)一起使用的DLL时。
我需要使用Delphi Xe2编写一个DLL来执行Unicode字符串上的简单字符串操作。这个DLL需要在内存管理器SimpleShareMemShareMem存在或不存在的情况下工作。这个DLL需要被从VB、C++和C#等外部语言调用。
默认情况下,字符串应该是Unicode字符串。我们应该使用Embarcadero来处理这些字符串吗?
字符串可以是:(a)不支持Unicode的单字节字符,或(b)每个字符需要两个字节的宽字符串。它们支持Unicode,但不是UTF-8字符串。
有两种指针类型可用:PAnsiCharPWideChar(没有PUnicodeChar指针可用)。PCharPWideChar的别名--这是否意味着我们总是需要为这些字符串分配2 * length的内存?(同样地,我们需要将内存除以2来获得这些字符串的长度吗?)
对于字符串常量,我们是否需要在源代码中标注字符串类型?例如:
Const MyCo = 'test';

或者

Const MyCo = WideString('test');

当我们在字符串变量之间执行赋值操作时,会出现什么情况呢?
s := st;

这段文字是否需要重写:

s := WideString(st);

我们的字符串中应该包含Unicode字节顺序标记吗?我们应该如何在字符串中加入BOM?
我们应该如何处理不同Windows代码页中的ANSI字符串?如果我们收到一个代码页为1200的ANSI字符串,我们应该重新编码字符串还是按原样处理?
我们应该如何使用TEncoding类在Unicode、UTF-8、WideString和AnsiString类之间进行转换?
使用宽字符或Unicode字符串会有严重的性能损失吗?
当使用通用内存管理器时,我们应该将接口写成仅需要使用WideString变量吗?
我们应该为PChar、PAnsiChar和PWideChar参数类型的长度参数编写接口要求吗?
我们如何编写接口来确定文件是以Unicode、UTF-8、ANSI还是Wide Characters存储的?在写回文件时,我们应该如何确定使用哪种格式?
我们应该只使用过程吗?或者函数也可以工作吗?
谢谢,新年快乐。

5
BO? 应为 字节顺序标记。我建议在 Stack Overflow 的问题中提出更少的问题 - 这里有很多,但我认为问题归结为几个小问题:在国际化和本地化程序中应该使用什么类型的字符串?与系统上的其他程序交互时应如何使用这些字符串?但是我不够自信来编辑您的问题。(抱歉。)(哦,对了,祝你新年快乐。 :)) - sarnold
谢谢你的回答! BOM! :) 你是对的(我记得那是从“BO”开始的)。 关于代码行,我只是担心会丢失问题线程(而且我认为,对于其他搜索者来说,如果我设置这么多单独的小问题,那将更好),我已经在这里尽可能地写得简短,我非常不想把它们分开。如果可能的话,请至少纠正一部分我已经理解的问题,我会请朋友完成其余的部分。我的问题已经挂了多长时间(包括假期),没有进行任何编辑? - Gu.
1
在我看来,这个问题无法挽救。请提出一个单一的问题。 - David Heffernan
1
@David:我知道你的意思,但我有一种感觉,答案会比问题简单:Gu.似乎在询问如何处理新环境提供的“新”字符串的最佳实践,这听起来并不可怕。关于Delphi内存管理的部分问题严重困扰了我重新撰写问题的努力,但希望熟悉Delphi的人能理解其含义... - sarnold
1
谷,这个网站的设置是为了让你在提出简短、简明的问题时获得最佳答案。最好将问题分开提出。这样,人们可以跳过已经被其他人充分回答的问题,如果有多种观点,你可能会得到多个答案。此外,人们可以因回答多个问题而获得更大的奖励。我认为现在不会有很多人回答你所有的问题。 - Wouter van Nifterick
显示剩余8条评论
3个回答

5
我了解到Gu正在从Delphi 7迁移到Unicode启用版本(D2009+),并正在寻求如何处理新字符串的建议。Cary Jensen的白皮书 Delphi Unicode Migration for Mere Mortals,解决了提出问题的几乎所有问题。
我通常会将此内容放在评论中,但评论列表已经很长了,我觉得链接(可能不仅有助于Gu,还有助于更多人)更容易在答案中找到。

1
  • ShareMem和SimpleShareMem可用于仅使用字符串参数在使用相同单元的Delphi应用程序之间共享DLL。它们确保应用程序和DLL使用相同的内存管理器。你不能在Delphi以外使用它们,因为VB、C++和C#将分别使用它们自己的内存管理器。Delphi字符串类型的内存布局与其他语言兼容(反之亦然),但必须使用相同的内存管理器分配和释放字符串。
  • Delphi Unicode字符串是UTF-16字符串,因为这是“本地”的Windows字符串类型。它可以支持多种8位编码,包括UTF-8,使用AnsiString类型。
  • UTF-16并不总是每个“字符”使用2个字节。一些“字符”可能会使用4个字节,尽管这通常只出现在进行一些“奇特”的文本处理(音乐符号、死语言等)时。
  • 将字符串转换为Unicode是无损的。否则,您必须确保每个AnsiString使用正确的代码页以避免转换损失。请注意,许多例程通过Unicode转换,因此最好将任何非Unicode字符串转换为Unicode,如果没有特定于编码的处理需要执行,则最后再转换回来。
  • 由于Unicode字符串在Delphi和Windows中都是默认类型,因此性能应该更好,因为不需要来回转换。但是,对UTF-16字符串进行操作的自定义代码可能会更慢,因为处理更加复杂(UTF-8可能更加复杂,因为某些MBCS编码)。
  • 在文本文件中通常使用BOM,而不是在内存中的字符串中。从外部获取的数据应在读取/接收时转换为本机内存格式。否则,每次将字符串传递给期望它的函数时都需要转换为本机格式。输出形式取决于您的应用程序,您需要有能够确定它或询问用户的代码。

-1

AS,你最好把这些问题放在rsdn.ru论坛上,我觉得他们对新手更加宽容,而不是DelphiMasters。

Gu,基本上,你只需要阅读Delphi XE2的手册。所有的东西都在那里,只要用思考和注意力去阅读。

1)你根本不应该制作DLL。你应该制作BPL。 DLL旨在与纯C接口(如Win32 API)协作。 这些是最原始的类型,没有任何副作用。 C被称为“机器无关”的汇编语言,因为它的原始性,而DLL接口就是其中一种。

DLL可以用于与其他语言的兼容性,因为它们使用与Delphi不兼容的另一种副作用。然后,DLL强制执行最原始的协作,从接口中删除所有副作用。通过将互通级别降低到最简单的类型,DLL接口强制实现兼容性。

但是,当您需要与Delphi交互时,最好让Delphi关心所有兼容性问题。这就是Delphi 3中BPL的用途。 对于此类情况,使用DLL没有任何理由。

当然,如果你愿意的话,你可以自己给自己脚射击。 https://forums.embarcadero.com/thread.jspa?threadID=64114

2) 在XE2中,你不应该使用WideString,而应该使用UnicodeString。 字符串手册特别强调了这一点。所以看起来你只是没有阅读手册。

3) 字符串常量是Unicode的。(但字符常量是随机的ANSI或Unicode,这会导致意外的行为错误)。 并且没有必要询问和相信任何人的话 - 只需在任何查看器中打开你的exe文件并搜索这些常量,你会发现它们以2字节的UCS-2(也称为WideChar)编码存在。

4) Unicode BOM用于确定CPU的字节顺序,是Intel还是Motorola风格。 当你开发Windows时,你只能有Intel字节顺序,所以不需要BOM。

5) 你所写的关于长度和内存大小的整个段落非常模糊。你所说的长度是什么意思,以哪种单位衡量,从哪里获取?

我假设你所说的长度是指不包括任何“幕后”服务结构/字符的字符数。这就是内置函数System.Length(字符串或数组)返回的内容。 但是,如果这个假设是错误的,那么下面的答案也就错了。

而问题是你是否应该乘以2只是糟糕代码的一个标志。 你应该总是乘以某些东西,几年前你已经应该乘以。 乘以...什么?通过SizeOf(使用的char变量)或SizeOf(使用的char类型)。 然后t将是Delphi自动确定需要多少内存。 并且在处理C字符串时,您应该使用的不是length,而是length+1-不要忘记#0终止符。

6)我们应该如何处理不同Windows代码页中的ANSI字符串?如果我们收到一个代码页为1200的ANSI字符串,我们应该重新编码字符串还是按原样处理?

RTFM!!! 只需声明带有代码页1200的AnsiString类型。 或使用RawByteStrnig和SetCodePage。 请阅读下面的真实代码。

再次强调,RTFM - 所有这些都在内置帮助中描述。 在Delphi XE2的内置帮助中阅读所有这些只需要2个小时。

7) 如何使用TEncoding类在Unicode、UTF-8、WideString和AnsiString类之间进行转换? TEncoding是为TStringList或类似对象设计的。 为什么要这样做?有UTF8String类型 - 直接使用即可。

var as: AnsiString; ucs2s: string; utf8string: UTF8String; ... as := ucs2; utf8s := as; ....

8) 使用宽字符串或Unicode字符串是否会有严重的性能损失? 这取决于你指的是哪种“Unicode”,UCS-2还是UTF8。以及你想使用哪些操作。 只需编写一个长循环并测量时间即可。

9) 我们应该将接口编写成需要PChar、PAnsiChar和PWideChar参数类型的长度参数吗? 由你决定,随意处理。 通常PChar是以#0结尾的C字符串。这就是StrLen函数的工作方式。 如果您忽略这个约定并像不带类型的指针一样使用它 - 那么请单独传递长度。

所有这些问题都已经在帮助文件中得到了回答!!!请阅读它。


function CDF_File_Buffer.GetStringNoBounds(const ofs, len: integer): string;
// Распознать кодировку, не проверять адреса
var cntDOS, cntWin, cntWeird, i : Cardinal;
    sBuf: RawByteString; cp: word;
    ptr, ptr_i: PAnsiChar;
const rusDOS: set of AnsiChar = [#$80..#$AF, #$E0..#$F1];
      rusWin: set of AnsiChar = [#$C0..#$FF];
begin
   ptr := Pointer(Header);  Inc(ptr, ofs);

   case textCharset of
     tcsGuess:
       begin
           ptr_i := ptr;  cntDOS :=0; cntWin :=0; cntWeird:=0;
           for i := 1 to len do begin
               if ptr_i^ in rusDOS then Inc(cntDOS);
               if ptr_i^ in rusWin then Inc(cntWin);
               if (ptr_i^ < #32) or ((ptr_i^ >= #127) and not(ptr_i^ in rusWin) and not(ptr_i^ in rusDOS))
                   then inc(cntWeird);

               Inc(ptr_i);
           end;
           if (cntWin > cntDOS) or (cntWeird > cntDOS) then cp := 1251 else cp := 866;
       end;
     tcsWin: cp := 1251;
     tcsDOS: cp := 866;
     else cp := 0; // быть не должно такого
   end;

   SetString(sBuf,ptr,len); 

   for i := 1 to Length(sBuf) do  if sBuf[i] = #0 then sBuf[i] := #7;
   // Не совсем корректно заменять нули, но
   // 1) неизвестно, что вообще в этом поле может быть
   // 2) файлы создаются в программе на C++, где нулей в строках не может быть
   // 3) семантику надо выводить на экран, а Windows написана на C++

   SetCodePage(sBuf, cp, false);

   Result := string(sBuf);
end;

function CDF_File_Buffer.GetString(const ofs, len: integer; const min, max: integer): string;
begin
  if (ofs <= 0) or (len <= 0) then
     Exit( '--- нет текста ---');

  if Cardinal(ofs + (len-1)) >= TotalSize then //меньше нуля уже не будет, убираем Warning
     Exit('--- За пределами файла ---');

  Result := '--- За пределами допустимой области: слишком близко к ';
  if ofs < min then Exit(Result + 'началу ---');
  if ofs + (len-1) > max then Exit(Result + 'концу ---');

  Result := GetStringNoBounds(ofs, len);
end;

function CDF_File_Buffer.GetString(const ofs, len: integer): string;
begin
  Result := GetString(ofs, len, 0, TotalSize-1);
end;

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