【问题标题】:Delphi Unicode and ConsoleDelphi Unicode 和控制台
【发布时间】:2012-04-20 06:04:19
【问题描述】:

我正在编写一个与一对硬件传感器接口的 C# 应用程序。不幸的是,在设备上公开的唯一接口需要提供用 Delphi 编写的 dll。

我正在编写一个 Delphi 可执行包装器,它调用 DLL 的必要函数并通过 stout 返回传感器数据。但是,此数据的返回类型是 PWideChar(或 PChar),我无法将其转换为 ansi 以便在命令行上打印。

如果我直接将数据传递给 WriteLn,我会得到“?”对于每个字符。如果我查看字符数组并尝试使用 Ansi 转换一次打印一个字符,则只会打印几个字符(尽管它们确实确认了数据),并且它们通常会乱序打印。 (打印时暴露的索引只是跳来跳去。)我还尝试将 PWideChar 转换为整数:'I' 对应于 21321。我可能会找出所有的转换,但有些数据有很多值。

我不确定 dll 使用的是什么版本的 Delphi,但我相信它是 4。肯定在 7 之前。

感谢任何帮助!

TLDR:需要将 UTF-16 PWideChar 转换为 AnsiString 进行打印。

示例应用:

program SensorReadout;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Windows,
  SysUtils,
  dllFuncUnit in 'dllFuncUnit.pas'; //This is my dll interface.

var  state: integer;
     answer: PChar;
     I: integer;
     J: integer;
     output: string;
     ch: char;

begin
  try
    for I := 0 to 9 do
    begin
      answer:= GetDeviceChannelInfo_HSI(1, Ord('a'), I, state); //DLL Function, returns a PChar with the results.  See example in comments.

      if state = HSI_NO_ERRORCODE then
      begin
        output:= '';
        for J := 0 to length(answer) do
        begin
          ch:= answer[J]; //This copies the char. Originally had an AnsiChar convert here.
          output:= output + ch;
        end;
        WriteLn(output);
      end;

  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  ReadLn(I);
end.`

问题是 PAnsiChar 必须是源自 DLL 的函数的返回类型。

【问题讨论】:

  • 您希望输出的文本是什么样的?也许它不能表示为 AnsiString?例如,Unicode 字符串 ⌬‽∫∇❦∴φ┶ 不能“转换”为 AnsiString。
  • 文本显示为???。一个示例的预期输出是:“ISO 4190250”,但我得到“????????”。如果我将 char 逐个转换,我会得到 S 和 4,但其他的要么是空的,要么会导致奇怪的 IO 问题。
  • 某处可能有一些微妙的错误。我认为您需要展示一个显示此问题的最小项目(带有代码)。
  • 对我来说看起来不错。你只是不知道如何解释结果。你声称你得到了一个 PWideChar,但我不认为你真的是。您说第一个字符的数值为 21321。当解释为两个 AnsiChars 时,即为 IS。你得到的是PAnsiChar,而不是PWideChar。你不必转换任何东西。您只需修复导入单元以使用正确的类型,即PAnsiChar,而不是PChar
  • 制造商给了你为 Delphi 4 编写的代码。在 Delphi 4 中,PCharPAnsiChar 相同,所以代码是正确的,在那个版本中。你正在用 Delphi XE2 编译 Delphi 4 代码,其中PChar 的含义不同。

标签: delphi


【解决方案1】:

PWideChar 转换为AnsiString

function WideCharToAnsiString(P: PWideChar): AnsiString;
begin
  Result := P;
end;

代码从 UTF-16、以 null 结尾的 PWideChar 转换为 AnsiString。如果您在输出中得到问号,那么您的输入不是 UTF-16,或者它包含无法在您的 ANSI 代码页中编码的字符。

我的猜测是,实际发生的情况是您的 Delphi DLL 是使用预 Unicode Delphi 创建的,因此使用 ANSI 文本。但是现在您正试图从后 Unicode Delphi 链接到它,其中 PChar 具有不同的含义。我确定 Rob 在您的另一个问题中向您解释了这一点。因此,您可以通过声明您的 DLL 导入返回 PAnsiChar 而不是 PChar 来简单地修复它。像这样:

function GetDeviceChannelInfo_HSI(PortNumber, Address, ChNumber: Integer;
  var State: Integer): PAnsiChar; stdcall; external DLL_FILENAME;

完成此操作后,您可以按照我上面描述的方式将其分配给字符串变量。

您需要了解的是,在旧版本的 Delphi 中,PCharPAnsiChar 的别名。在现代 Delphi 中,它是 PWideChar 的别名。这种不匹配可以解释您报告的所有内容。


我确实想到将 Delphi 包装器写入 DLL 并通过 stdout 与您的 C# 应用程序进行通信是一种非常迂回的方法。我只是直接从 C# 代码 p/invoke DLL。您似乎认为这是不可能的,但它很简单。

[DllImport(@"mydll.dll")]
static extern IntPtr GetDeviceChannelInfo_HSI(
    int PortNumber, 
    int Address, 
    int ChNumber,
    ref int State
);

这样调用函数:

IntPtr ptr = GetDeviceChannelInfo_HSI(Port, Addr, Channel, ref State);

如果函数返回一个 UTF-16 字符串(这似乎值得怀疑),那么您可以像这样转换 IntPtr

string str = Marshal.PtrToStringUni(ptr);

或者,如果它实际上是一个 ANSI 字符串,在我看来很可能,那么你可以这样做:

string str = Marshal.PtrToStringAnsi(ptr);

然后你当然会想要调用你的 DLL 来释放返回给你的字符串指针,假设它是在堆上分配的。

【讨论】:

  • 不幸的是,这只是导致我的字符串从 ????要单?字符。
  • 编辑了 TLDR 以更好地反映。感谢您尝试解决此问题。
  • 该函数输出一个 PChar (PWideChar),它确实包含我需要的文本,但字符编码似乎与 Delphi 的 WriteLn 不兼容。因此???根据这些值,它似乎是 UTF16 的一种形式。您的转换方法无法将UTF16转换为Ansi使用。
  • @David:我怀疑@Jonneh 实际上可能是对的。通常你不使用^'后缀运算符'。没有它,您的代码会更好地运行!
  • @David:好吧,我什至试过了,它可以通过简单的分配 (Result := P) 正常工作,但如果你坚持做Result := P^,那么Result 将只包含P 的第一个字符。这几乎是有道理的,因为如果PPWideChar,那么P^ 就是WideChar,即单个字符。试试看!
【解决方案2】:

改变了我对评论的看法 - 我会回答:)

根据该代码,如果“状态”是代码 HSI_NO_ERRORCODE 并且没有异常,则它将未初始化的字符串“输出”写入控制台。这可能是任何内容,包括意外显示“S”和“4”以及一系列 1 个或多个问号

【讨论】:

  • 原来在我的街区里。哎呀。可悲的是,它并没有解决任何问题。
  • 没有未初始化的字符串。如果没有分配任何其他内容,该字符串将为空。
  • OK 是的 - 误读它不是局部变量,所以应该初始化
【解决方案3】:

答案类型(变量)是 PChar。使用适合字符串变量的长度函数。 使用 strlen 而不是 length。

 for J := 0 to StrLen(answer)-1 do

PChar(char *) 也可以访问的范围是 0..n-1

【讨论】:

    【解决方案4】:

    要将 UTF-16 PWideChar 转换为 AnsiString,您可以使用简单转换:

    var
      WStr: WideString;
      pWStr: PWideString;
      AStr: AnsiString;
    begin
      WStr := 'test';
      pWStr := PWideString(WStr);
      AStr := AnsiString(WideString(pWStr));
    end;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-12-13
      • 1970-01-01
      • 2012-03-19
      • 2016-02-24
      • 2012-12-13
      • 2018-07-21
      • 1970-01-01
      相关资源
      最近更新 更多