使用 Delphi 6/7,如何将不同 CharSet 中的 AnsiString 转换为十六进制字符串 UTF-8?

With Delphi 6/7, how can I convert an AnsiString in a different CharSet, to hex String UTF-8?

我需要用 Delphi 6/7 绘制条形码 (QR)。该程序可以在各种 windows 语言环境中 运行,数据来自输入框。

在此输入框中,用户可以选择字符集,并输入自己的语言。这很好用。输入数据仅来自同一代码页。示例配置可以是:

我需要将 Shift-JIS 转换为条形码。最健壮的方法是使用十六进制编码。

所以我的问题是:如果代码页与 Windows 区域设置不同,我该如何从 Shift-JIS 转换为 UTF-8 编码的十六进制字符串?

例如:我有字符串 能ラ。这需要根据 UTF-8 转换为 E883BDE383A9。我试过这个但结果不同且毫无意义:

String2Hex(UTF8Encode(ftext))

不幸的是,我不能只有 WideStrings 的输入框。但是如果我能找到一种方法将 ANSI 文本转换为 WideString,条形码模块也可以使用 Unicode 字符串。

如果相关:我正在使用 TEC-IT TBarcode DLL。

创建和访问 Unicode 文本控件

这比您想象的要容易,我过去曾使用全新的 Windows 2000 这样做过,而 Tnt Delphi Unicode Controls 等方便的组件并不存在可用的。了解如何在不使用 Delphi 的 VCL 的情况下创建 Windows GUI 程序并手动创建所有内容的背景知识会有所帮助- 否则这也是它的介绍。

  1. 首先在您的表单中添加一个属性,以便我们稍后可以轻松访问新控件:

    type
      TForm1= class(TForm)
    ...
      private
        hEdit: THandle;  // Our new Unicode control
      end;
    
  2. 现在只需在您最喜欢的活动中创建它 - 我选择 FormCreate:

      // Creating a child control, type "edit"
      self.hEdit:= CreateWindowW( PWideChar(WideString('edit')), PWideChar(WideString('myinput')), WS_CHILD or WS_VISIBLE, 10, 10, 200, 25, Handle, 0, HINSTANCE, nil );
      if self.hEdit= 0 then begin  // Failed. Get error code so we know why it failed.
        //GetLastError();
        exit;
      end;
    
      // Add a sunken 3D edge (well, historically speaking)
      if SetWindowLong( self.hEdit, GWL_EXSTYLE, WS_EX_CLIENTEDGE )= 0 then begin
        //GetLastError();
        exit;
      end;
    
      // Applying new extended style: the control's frame has changed
      if not SetWindowPos( self.hEdit, 0, 0, 0, 0, 0, SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOZORDER or SWP_NOSIZE ) then begin
        //GetLastError();
        exit;
      end;
    
      // The system's default font is no help, let's use this form's font (hopefully Tahoma)
      SendMessage( self.hEdit, WM_SETFONT, self.Font.Handle, 1 );
    
  3. 有时您想获取编辑的内容。再说一次:如果没有 Delphi 的 VCL,而是直接使用 WinAPI,这是如何完成的?这次我使用了按钮的 Click 事件:

    var
      sText: WideString;
      iLen, iError: Integer;
    begin
      // How many CHARACTERS to copy?
      iLen:= GetWindowTextLengthW( self.hEdit );
      if iLen= 0 then iError:= GetLastError() else iError:= 0;  // Could be empty, could be an error
      if iError<> 0 then begin
        exit;
      end;
    
      Inc( iLen );  // For a potential trailing #0
      SetLength( sText, iLen );  // Reserve space
      if GetWindowTextW( self.hEdit, @sText[1], iLen )= 0 then begin  // Copy text
        //GetLastError();
        exit;
      end;
    
      // Demonstrate that non-ANSI text was copied out of a non-ANSI control
      MessageBoxW( Handle, PWideChar(sText), nil, 0 );
    end;
    

还有一些细节问题,比如无法通过 Tab 访问这个新控件,但我们基本上已经 re-inventing Delphi的VCL,所以这些都是其他时候需要注意的细节。

正在转换代码页

WinAPI 处理 codepages (Strings) or in UTF-16 LE (WideStrings)。由于历史原因(UCS-2 及更高版本)UTF-16 LE 适合所有内容,因此这始终是来自代码页时要实现的隐含目标:

// Converting an ANSI charset (String) to UTF-16 LE (Widestring)
function StringToWideString( s: AnsiString; iSrcCodePage: DWord ): WideString;
var
  iLenDest, iLenSrc: Integer;
begin
  iLenSrc:= Length( s );
  iLenDest:= MultiByteToWideChar( iSrcCodePage, 0, PChar(s), iLenSrc, nil, 0 );  // How much CHARACTERS are needed?
  SetLength( result, iLenDest );
  if iLenDest> 0 then begin  // Otherwise we get the error ERROR_INVALID_PARAMETER
    if MultiByteToWideChar( iSrcCodePage, 0, PChar(s), iLenSrc, PWideChar(result), iLenDest )= 0 then begin
      //GetLastError();
      result:= '';
    end;
  end;
end;

源代码页由您决定:也许

  • 1252 for "Windows-1252" = ANSI Latin 1 多语言(西欧)
  • 932 for "Shift-JIS X-0208" = IBM-PC 日本 MIX (DOS/V) (DBCS) (897 + 301)
  • 28595 对于“ISO 8859-5”= 西里尔文
  • 65001 表示“UTF-8”

但是,如果你想从一个代码页转换到另一个代码页,并且源和目标都不能是 UTF-16 LE,那么你必须来回:

  1. 从 ANSI 转换为 WIDE
  2. 从 WIDE 转换为不同的 ANSI
// Converting UTF-16 LE (Widestring) to an ANSI charset (String, hopefully you want 65001=UTF-8)
function WideStringToString( s: WideString; iDestCodePage: DWord= CP_UTF8 ): AnsiString;
var
  iLenDest, iLenSrc: Integer;
begin
  iLenSrc:= Length( s );
  iLenDest:= WideCharToMultiByte( iDestCodePage, 0, PWideChar(s), iLenSrc, nil, 0, nil, nil );
  SetLength( result, iLenDest );
  if iLenDest> 0 then begin  // Otherwise we get the error ERROR_INVALID_PARAMETER
    if WideCharToMultiByte( iDestCodePage, 0, PWideChar(s), iLenSrc, PChar(result), iLenDest, nil, nil )= 0 then begin
      //GetLastError();
      result:= '';
    end;
  end;
end;

根据每个 Windows 安装,并非每个代码页都受支持,或者支持不同的代码页,因此转换尝试可能会失败。立即瞄准 Unicode 程序会更稳健,因为这是每个 Windows 安装肯定支持的(除非你还是处理 Windows 95, Windows 98 或者 Windows ME).

结合一切

现在你已经拥有了组装所需的一切:

  • 你可以有一个 Unicode 文本控件直接在 UTF-16 LE
  • 中获取它
  • 您可以使用 ANSI 文本控件将输入转换为 UTF-16 LE
  • 您可以将 UTF-16 LE (WIDE) 转换为 UTF-8 (ANSI)

尺寸

UTF-8 大多是最佳选择,但当您的目标受众是亚洲人时,UTF-16 可能需要更少的总字节数:在 UTF-8 中, 都需要每个 3 个字节,但在 UTF-16 中,每个都只需要 2 个字节。根据您的 QR 条形码大小,我猜是一个重要因素。

同样不要浪费将二进制数据(每字节 8 位)转换为 ASCII 文本(显示每个字符 4 位,但它本身又需要 1 字节 = 8 位)。看一下 Base64,它将 6 位编码为每个字节。一个你在生活中已经遇到过无数次的概念,因为它用于电子邮件附件。