在Delphi中读取二进制文件

时间:2013-07-11 19:43:07

标签: delphi

我想阅读二进制文件并在备忘录中显示结果,但不知道如何处理此错误:"不兼容的类型:' string'和'数组'",代码就是这个

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;

设法避免此错误并运行程序而没有错误?

任何人都可以帮助我吗?

2 个答案:

答案 0 :(得分:10)

您无法直接绕过此错误,因为stringarray[] of Byte无法直接分配。

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

最简单通过编译器错误的方法是将您的数组从array[] of Byte更改为array[] of AnsiChar,可以直接将其分配给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,因为第四个字节是#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;

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

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;

注意:如果您实际上是在读取整个二进制文件而不是第一个缓冲区,那么最后一个缓冲区可能不会完全填满文件内容(您可能在最后一次传递时没有读取完整缓冲区)。在这种情况下,您希望使用#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;

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

Executable displayed in TMemo

答案 1 :(得分:6)

字节数组不是字符串,因此编译器的消息意味着它所说的内容。字节是数字;字符串是文本的。数字不是文本,因此在过程的某个地方,您需要告诉程序如何将数字转换为文本。

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

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);

您可以按照自己的方式定义数据转换。您所要做的就是指定所需的输出,然后编写生成它的代码。如果您没有指定您想要的内容,并且无法准确描述,那么您还没有为代码做好准备。