需求
有时候需要把结构体的数据变成二进制串。比如一堆数据最后需要从网络传输出去。当然,对应的需求就是从二进制串变回结构体。
实现
常见办法
比较常见的办法是直接把结构体内存数据复制到二进制串的变量所指向的内存位置。例子代码如下:
Delphi
TMyRecord = packed record
MyName: string[6];
MySize: Integer;
end;
var
R, Ra: TMyRecord;
B1: TBytes;
begin
R.MyName := 'abcxyz';
R.MySize := 343;
SetLength(B1, SizeOf(R));
Move(R, B1[0], SizeOf(R));
//再变回来
Move(B1[0], Ra, SizeOf(Ra));
end;
Delphi 的一个比较著名的控件 Indy 就是这样干的。请看它的代码:
Delphi
function RawToBytes(const AValue; const ASize: Integer): TIdBytes;
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
SetLength(Result, ASize);
if ASize > 0 then begin
Move(AValue, Result[0], ASize);
end;
end;
procedure BytesToRaw(const AValue: TIdBytes; var VBuffer; const ASize: Integer);
{$IFDEF USE_INLINE}inline;{$ENDIF}
begin
Assert(Length(AValue) >= ASize);
Move(AValue[0], VBuffer, ASize);
end;
就是有两个变量,分别有自己的内存空间,使用 Move 方法把数据复制过去。
但是,复制数据是需要消耗 CPU 资源的。如果需要复制的数据很多,就会影响程序执行效率。
那么,我们不复制数据,直接使用指针,如何?
使用指针
Delphi 是强类型语言,最好不要使用通用指针,而是使用类型指针。因此我们需要先定义一下数据类型。代码:
Delphi
TMyRecord = packed record
MyName: string[6];
MySize: Integer;
end;
PMyRecord = ^TMyRecord;
TMyArr = array[0..10] of Byte;
PMyArr = ^TMyArr;
在上述的数据类型基础上,我写了一段测试代码,执行成功。
Delphi
procedure TForm1.Button4Click(Sender: TObject);
var
B, B1: TMyArr;
R: TMyRecord;
Rx: PMyRecord;
begin
//这样直接操作指针是成功的!
R.MyName := 'abcxyz';
R.MySize := 222;
B := TMyArr(R);
Move(B[0], B1[0], SizeOf(R));
Rx := PMyRecord(@(B1[0]));
Log(Rx^.MyName);
Log(Rx^.MySize.ToString);
end;
解释一下:
上述代码的 R 是一个结构体,B 是 Byte 数组。直接类型转换 R 为 B,实际上就是把 R 的指针地址给了 B;
后面的 Move 是为了验证把 B 的数据复制到 B1 里面,是否成功;
最后,Rx 是结构体指针,把 B1 的地址给 Rx,我们获得了结构体 Rx。因为 Rx 是指针,所以最后访问 Rx 的内容时,后面加上了那个 ^ 符号,这个是 Delphi 语法。
总结:
结构体和 Byte 数组之间,可以直接通过指针的方式做类型转换,不同的数据类型的变量,实际上都是指向同一个内存块。这样避免了数据复制,程序更少消耗 CPU。