使用Delphi 6处理Unicode字符

6
我有一个在Delphi 6中开发的轮询应用程序。它读取文件,根据规范解析文件,进行验证并上传到数据库(SQL Server 2008 Express Edition)。
我们必须支持具有双字节字符集(DBCS)的操作系统,例如日本操作系统。因此,我们将SQL Server中的数据库字段从varchar更改为nvarchar。
在具有DBCS的操作系统中,轮询可以正常工作。如果系统区域设置设置为日语/中文/韩语且操作系统具有相应的语言包,则非DBCS操作系统也可以成功运行。 但是,如果区域设置设置为英语,则双字节字符会显示为垃圾字符。
我进行了一些测试,但未能找到解决方案。
例如,如果我使用TStringList从UTF-8文件中读取并保存到另一个文件,则Unicode数据将被保存。但是,如果我使用文件内容运行TADOQuery组件的更新查询,则会显示垃圾字符。数据库中也包含垃圾字符。
以下是示例代码:
var
    stlTemp : TStringList;
    qry : TADOQuery;
    stQuery : string;
begin
    stlTemp := TStringList.Create;
    qry := TADOQuery.Create(nil);
    stlTemp.LoadFromFile('D:\DelphiUnicode\unicode.txt');
    //stlTemp.SaveToFile('D:\DelphiUnicode\1.txt'); // This works. Even though 
    //the stlTemp.Strings[0] contains junk characters if seen in watch

    stQuery := 'UPDATE dbo.receivers SET company = ' + QuotedStr(stlTemp.Strings[0]) +
        ' WHERE receiver_cd = N' + QuotedStr('Receiver'); 
    //company is a nvarchar field in the  database
    qry.Connection := ADOConnection1;
    with qry do
    begin
        Close;
        SQL.Clear;
        SQL.Add(stQuery);
        ExecSQL;
    end;
    qry.Free;
    stlTemp.Free
end;

以上代码在双字节字符集操作系统中运行良好。

我已尝试使用string、widestring和UTF8String进行调整。但是,在英文操作系统上,如果区域设置为英语,则无法正常工作。

请提供有关此问题的任何指针。


“D:\DelphiUnicode\unicode.txt”是什么意思?有很多Unicode格式,例如UTF-8、UTF-7和几种UTF-16变体。尝试使用TWideStringList来加载UTF-16文件。如果您的Delphi没有它,请尝试使用Jedi CodeLibrary中的WideStringList。 - Arioch 'The
@ArnaudBouchez 报告称RDBMS是一些“SQL Server”,可能是微软SQL Server的某个版本。 - Arioch 'The
@Arioch'The:该文件为UTF-8编码。数据库为SQL Server Express。这是一个测试应用程序,因此没有使用参数化查询。另外,我忘记了一件事,如果我将varchar数据类型修改为nvarchar,则数据库插入不会有问题。 - Abhineet
@Arioch'The:轮询实用程序是一个庞大应用程序的模块。它自2001年以来一直在生产中使用。代码库相当大。客户端在功能没有影响的情况下都很好。整个代码大小包括第三方工具约为600 MB。此外,由于数据库的大小也约为300 MB - 1 GB,因此使用了SQL Express。我们建议客户升级Delphi,但由于成本和时间问题,他们不愿意。无论如何,这样的要求可能会鼓励他们做出决策。 - Abhineet
这样的数据库大小对于许多不同的服务器来说都是可以接受的。也许 MS-SQL 2008 比 2000 更优化,安装和运行更快,我不知道。如果您方面的支持价格对于 D6 和更新版本的 Lazarus/Delphi 是相同的,那么您的客户肯定没有升级的理由。 - Arioch 'The
显示剩余4条评论
2个回答

4
在非Unicode版本的Delphi中,基本上你需要使用WideString(Unicode)代替String(Ansi)。不要考虑TADOQuery.SQL(TStrings),而是使用TADODataSet.CommandText或者TADOCommand.CommandText(WideString),或者将TADOQuery强制转换为TADODataSet。例如:
stlTemp: TWideStringList; // <- Unicode strings - TNT or other Unicode lib
qry: TADOQuery;
stQuery: WideString; // <- Unicode string

TADODataSet(qry).CommandText := stQuery;
RowsAffected := qry.ExecSQL;

您也可以使用TADOConnection.Execute(stQuery)直接执行查询。


关于参数查询,请特别注意:ADODB.TParameters.ParseSQL是 Ansi编码的。如果 ParamCheck 是true(默认情况下),TADOCommand.SetCommandText -> AssignCommandText会导致问题,如果您的查询是Unicode编码的(InitParameters 是Ansi编码)。

(请注意,您可以直接使用 ADO 的 Command.Parameters,使用?作为参数占位符,而不是 Delphi 的约定:param_name)。


QuotedStr返回 Ansi 编码的字符串。您需要这个函数的 Wide 版本(TNT)


此外,正如 @Arioch 所提到的那样,TNT Unicode Controls套件是制作 Delphi Unicode 应用程序的最佳选择。它具有您在应用程序中成功管理 Unicode 任务所需的所有控件和类。

简而言之,您需要考虑 Wide 编码:)


TWideStringList - 它是在 D6 中就有了吗? - Arioch 'The
@Arioch'The,不行。你需要TNT或其他Unicode库来处理TWideStringList。 - kobik
它是否能够加载UTF-8文件,或者他必须通过内存流传递脚本文件并自己重新编码为UTF-16。虽然不是不可能,但似乎超出了当前主题发起者的能力范围。说到JCL,我很惊讶TJclWideStrings不支持UTF-8加载,而TWideStringList可以。也许有一天会有人将它们合并... - Arioch 'The
@kobik:感谢您的建议,我会尝试一下。 - Abhineet

3
  1. 您没有指定数据库服务器,所以这项调查仍由我们进行。您应该检查数据库服务器如何支持Unicode。这意味着如何为数据库和其中的表/列/索引/排序等指定Unicode字符集。您必须确保整个数据库在每个细节上都普遍启用了Unicode,以避免数据丢失。

  2. 通常,您还应该检查您的数据库连接(使用所选的数据库访问库)是否也启用了Unicode。一般来说,Microsoft ADO就像OLE一样应该启用Unicode。但是仍然需要检查数据库服务器手册,了解如何在连接字符串中指定Unicode代码页或字符集。非Unicode连接也可能导致数据丢失。

  3. 当您告诉您读取了某些Unicode文件时-它是含糊不清的。什么是Unicode文件?是UTF-8吗?还是UTF-16的四种口味之一?或者是UTF-7?还是其他一些Unicode传输格式?通常的Windows WideChar大致对应于传统的UCS-2,并且预计会剥离BOM Intel-Endian风格的UTF-16。http://msdn.microsoft.com/en-us/library/windows/desktop/ms221069.aspx

  4. 如果文件肯定是那种UTF-16的口味,则可以使用Delphi TWideStringList或Jedi CodeLibrary TJclWideStringList加载它。查看您的代码,确保您从不使用字符串变量处理数据-无论何时都要使用WideString以避免数据丢失。
    由于D6是最多错误的版本之一,我更喜欢确保安装了Delphi的每个更新,然后安装和使用JCL。 JCL还提供了代码页转换函数,这可能比纯粹的AnsiStringVar:= WideStringVar方法更灵活。
    对于UTF-8文件,可以使用JCL的TWideStringList类加载它(但不是TJclWideStringList)。

  5. 调试时,将列表的行加载到WideString变量中,并查看其内容是否得到保留。

  6. 不要编写这样的查询。请参见http://bobby-tables.com/ 即使您不希望恶意黑客-您自己也可能出错或遇到意外数据。始终在任何地方使用参数化查询!每次都是如此!EVER!
    请参见以下示例:http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/ADODB_TADOQuery_Parameters.html
    检查每个SQL VARCHAR参数是否为ftWideString以包含Unicode,而不是ftString。检查字段(列)是否也是如此。

  7. 考虑是否可以放弃旧技术,因为它们的支持将来只会更加困难。

    7.1. 由于Microsoft ADO已被弃用(例如,新版本的Microsoft SQL Server将不支持它),请考虑切换到“实时”数据访问库。像AnyDAC、UniDAC、ZeosDB或其他库。T


2
@whosrdaddy,使用ADO在非Unicode Delphi中进行参数化查询有一个大问题,因为它们无法正确处理Unicode。请查看我的答案。 - kobik
@Kobik:这个事实已经足以成为升级到支持Unicode的Delphi版本的好理由了 :) - whosrdaddy
@whosrdaddy,如果你有足够的钱和时间来升级,那是摆脱这个痛苦世界的最佳途径 :) - kobik
1
@Arioch'The,问题不在ADO上,而是它的非Unicode Delphi实现。如果实现使用Ansi String而不是ADO所预期的WideString/BSTR,无论死活都不会有太大影响。 - kobik
@whosrdaddy,目前 ADO 本身还没有过时,但是对于 Delphi ADO 来说,ADO 的支持正在逐渐减少。如果主题发起人在使用 AnyDAC/UniDAC/ZeosDB 时遇到了错误,他可以寻求支持并期待解决方案甚至修复更新。那么,他如何向 EMB 提出请求,让其为 D6 发布修复后的 ADO 呢? - Arioch 'The
显示剩余3条评论

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