2

我想读取一个二进制文件并在备忘录中显示结果,但不知道如何处理这个错误:“不兼容的类型:'string' and 'Array'”,代码是这样的

unit yo;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  F: TFileStream;
  Buffer: array [0 .. 1023] of byte;
begin

  F := TFileStream.Create(ExtractFilePath(Application.ExeName)
      + 'yo.exe', fmOpenRead);

  while F.Position < F.Size do
  begin

    F.Read(Buffer, 1024);
    Memo1.Lines.Add(Buffer);

  end;

  F.Free;

end;

设法避免此错误并无错误地运行程序?

任何人都可以帮助我吗?

4

2 回答 2

12

你不能直接绕过这个错误,因为 astring和 anarray[] of Byte不能直接赋值。

由于二进制内容(#0特别是字符,或0x00十六进制值 (C/C++))无论如何都无法显示TMemo(文本将在第一个#0值处终止),因此您需要将其替换为某些内容。

克服编译器错误的最简单方法是将数组从 更改为array[] of Bytearray[] of AnsiChar可以直接分配给 a string(或类型转换为一个):

var
  Buffer: array[0..1023] of AnsiChar;
  TempStr: string;
begin
  // Fill buffer from stream
  TempStr := Buffer;
  Memo1.Lines.Add(TempStr);
  // The next line eliminates the need for `TempStr`
  // Memo1.Lines.Add(String(Buffer));
end;

但是,就像我说的,这并不能解决在备忘录中显示的问题。例如,当实际读取 Windows 可执行文件时,第一个缓冲区显示MZP,因为第四个字节是 a#0并且备忘录终止了字符串。

要克服此限制,您需要将所有#0字符替换为其他字符。当然,问题在于您替换它的任何值实际上也可能出现在可执行文件中(因为它们是字节,所以只有 256 个可能的值)。同样,简单的解决方案是将所有#0字符替换为0(#216):

var
  Buffer: array[0..1023] of AnsiChar;
  i: Integer;
  TempStr: string;
begin
  // Fill buffer as before
  for i := Low(Buffer) to High(Buffer) do
    if Buffer[i] = #0 then
      Buffer[i] := `Ø`;        // Try #144 instead
  TempStr := Buffer;
  Memo1.Lines.Add(TempStr);
  // You can still eliminate the string variable by typecasting
  // Memo1.Lines.Add(String(TempStr));
end;

下面是一个TForm.FormCreate事件的代码,它实际上从 Delphi 控制台应用程序读取 1K 缓冲区,执行上述替换,并将内容显示在TMemo. 将TMemo放在窗体上,将其Alignment属性设置为alClient和。向表单添加事件处理程序,并为该事件使用以下代码:ScrollBarsssVerticalFormCreate

procedure TForm1.FormCreate(Sender: TObject);
var
  Stream: TFileStream;
  Buffer: array[0..1023] of AnsiChar;
  TempStr: string;
  i: Integer;
begin
  Memo1.Clear;
  // Populate buffer elements
  Stream := TFileStream.Create('D:\Temp\Project2.exe', fmOpenRead);
  try
    Stream.Read(Buffer[0], SizeOf(Buffer));
  finally
    Stream.Free;
  end;
  // Replace null (#0) values with #216 (Ø)
  for i := Low(Buffer) to High(Buffer) do
    if Buffer[i] = #0 then
      Buffer[i] := 'Ø';
  TempStr := Buffer;
  Memo1.Lines.Add(TempStr);
end;

注意:如果您实际上是在读取整个二进制文件而不仅仅是第一个缓冲区,则最后一个缓冲区可能不会完全充满文件内容(您可能在最后一次读取时没有读取完整的缓冲区)。在这种情况下,您想用 a 标记缓冲区的结尾,#0以便备忘录正确显示该部分缓冲区。您可以更改for循环以使用以下内容:

for i := Low(Buffer) to High(Buffer) do
begin
  if (i = BytesRead) then
  begin
    Buffer[i] := #0;   // Mark the end of the buffer and exit loop; 
    Break;
  end
  else if (Buffer[i] = #0) then
   Buffer[i] := 'Ø';
end;

这是读取单个缓冲区已满的输出:

TMemo 中显示的可执行文件

于 2013-07-11T22:25:53.607 回答
7

字节数组不是字符串,因此编译器的消息就是它所说的意思。字节是数字;字符串是文本的。数字不是文本,所以在这个过程的某个地方,你需要告诉你的程序如何将数字转换成文本。

一种方法是将每个数值转换为其对应的数字。例如:

F.Read(Buffer, 1024);
s := '';
for b in Buffer do
  s := s + IntToStr(b);
Memo1.Lines.Add(s);

如果您希望将每个字节转换为具有相应数值的字符,那么您根本不需要任何转换;只是撒谎并告诉程序该文件包含文本,以便您可以将其直接加载到备忘录控件中:

Memo1.Lines.LoadFromFile(FileName);

如果您希望文件中可以表示可打印字符的字节按原样显示,而表示不可打印字符的字节按数字显示,那么您可以分别处理每个字符,类似于上面的循环:

F.Read(Buffer, 1024);
s := '';
for b in Buffer do begin
  c := AnsiChar(b);
  if TCharacter.IsControl(c) then
    s := s + IntToStr(b)
  else
    s := s + c;
end;
Memo1.Lines.Add(s);

您可以以任何您想要的方式定义数据转换。您所要做的就是指定您想要的输出,然后编写生成它的代码。如果你没有指定你想要什么,并且你不能准确地描述它,那么你还没有准备好接受代码。

于 2013-07-11T20:24:07.570 回答