Delphi 2010: How to save a whole record to a file?
I have defined a record which 开发者_Python百科has lots of fields with different types (integer, real , string, ... plus dynamic arrays in terms of "array of ..."). I want to save it as a whole to a file and then be able to load it back to my program. I don't want to go through saving each field's value individually. The file type (binary or ascii or ...) is not important as long Delphi could read it back to a record.
Do you have any suggestions?
You can load and save the memory of a record directly to and from a stream, as long as you don't use dynamic arrays. So if you use strings, you need to make them fixed:
type TTestRecord = record
FMyString : string[20];
end;
var
rTestRecord: TTestRecord;
strm : TMemoryStream;
strm.Write(rTestRecord, Sizeof(TTestRecord) );
You can even load or save an array of record at once!
type TRecordArray = array of TTestRecord;
var ra : TRecordArray;
strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra));
In case you want to write dynamic content:
iCount := Length(aArray);
strm.Write(iCount, Sizeof(iCount) ); //first write our length
strm.Write(aArray[0], SizeOf * iCount); //then write content
After that, you can read it back:
strm.Read(iCount, Sizeof(iCount) ); //first read the length
SetLength(aArray, iCount); //then alloc mem
strm.Read(aArray[0], SizeOf * iCount); //then read content
As promised here it is: https://github.com/KrystianBigaj/kblib
When you defined for example record as:
TTestRecord = record
I: Integer;
D: Double;
U: UnicodeString;
W: WideString;
A: AnsiString;
Options: TKBDynamicOptions;
IA: array[0..2] of Integer;
AI: TIntegerDynArray;
AD: TDoubleDynArray;
AU: array of UnicodeString;
AW: TWideStringDynArray;
AA: array of AnsiString;
R: array of TTestRecord; // record contain dynamic array of itself (D2009+)
end;
You can save whole dynamic record to stream (as binary data) by :
TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord));
To load it back:
TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord));
It not need to be a record, you can do same for any dynamic type like:
TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString));
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray));
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord;
Tested on Delphi 2006/2009/XE. License: MPL 1.1/GPL 2.0/LGPL 3.0 See readme for information.
Another option which works very well for records (Delphi 2010+) is to use the SuperObject library. For example:
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
ctx: TSuperRttiContext;
data: TData;
obj: ISuperObject;
sValue : string;
begin
ctx := TSuperRttiContext.Create;
try
sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}';
data := ctx.AsType<TData>(SO(sValue));
obj := ctx.AsJson<TData>(data);
sValue := Obj.AsJson;
finally
ctx.Free;
end;
end;
I also tested this briefly with a simple TArray<Integer>
dynamic array and it did not have a problem storing and loading the array elements.
In addition to the answers that indicate how you do this, please also be aware of these:
You must be aware that writing records out to a file will be Delphi version specific (usually: specific to a series of Delphi versions that share the same memory layout for the underlying data types).
You can only do that if your record does not contain fields of a managed type. Which means that fields cannot be of these managed types: strings, dynamic arrays, variants, and reference types (like pointers, procedural types, method references, interfaces or classes) and file types, or types that contain those manages types. Which basically limits to to these unmanaged types:
- A: Simple types (including bytes, integers, floats, enumerations, chars and such)
- B: Short strings
- C: Sets
- D: Static arrays of A, B, C, D and E
- E: Records of A, B, C, D and E
In stead of writing out records to/from a file, it might be better to go with class instances and convert them to/from JSON, and them write the JSON string equivalent to a file and read it back in.
You can use this unit to do the JSON conversion for you (should work with Delphi 2010 and up; works for sure with Delphi XE and up) from this location this location.
unit BaseObject;
interface
uses DBXJSON, DBXJSONReflect;
type
TBaseObject = class
public
{ public declarations }
class function ObjectToJSON<T : class>(myObject: T): TJSONValue;
class function JSONToObject<T : class>(json: TJSONValue): T;
end;
implementation
{ TBaseObject }
class function TBaseObject.JSONToObject<T>(json: TJSONValue): T;
var
unm: TJSONUnMarshal;
begin
if json is TJSONNull then
exit(nil);
unm := TJSONUnMarshal.Create;
try
exit(T(unm.Unmarshal(json)))
finally
unm.Free;
end;
end;
class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue;
var
m: TJSONMarshal;
begin
if Assigned(myObject) then
begin
m := TJSONMarshal.Create(TJSONConverter.Create);
try
exit(m.Marshal(myObject));
finally
m.Free;
end;
end
else
exit(TJSONNull.Create);
end;
end.
I hope this helps you getting an overview of things.
--jeroen
You could also define an object instead of a record, so you can use RTTI to save your object to XML or whatever. If you have D2010 or XE, you can use DeHL to serialize it: Delphi 2010 DeHL Serialization XML and custom attribute : how it work?
But if you "google" you can find other libs with RTTI and serialization (with D2007 etc)
Another solution, working from Delphi 5 up to XE, is available as an OpenSource unit.
In fact, it implements:
- some low-level RTTI functions for handling record types:
RecordEquals, RecordSave, RecordSaveLength, RecordLoad
; - a dedicated
TDynArray
object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString
.
We use this in our ORM, to store high-level types like dynamic array properties into a database back-end. First step to a DB-Sharding architecture.
If you have dynamic strings or array you can't write the record "as a whole". Instead of using old style-25 characters max strings, I would add methods to the record to be able to "stream" itself to a stream, or better using a TFiler descendant:
TMyRec = record
A: string;
B: Integer;
procedure Read(AReader: TReader);
procedure Writer(AWriter: TWriter);
end;
procedure TMyrec.Read(AReader: TReader);
begin
A := AReader.ReadString;
B := AReader.ReadInteger;
end;
Codes from delphibasics :
type
TCustomer = Record
name : string[20];
age : Integer;
male : Boolean;
end;
var
myFile : File of TCustomer; // A file of customer records
customer : TCustomer; // A customer record variable
begin
// Try to open the Test.cus binary file for writing to
AssignFile(myFile, 'Test.cus');
ReWrite(myFile);
// Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
Write(myFile, customer);
customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
Write(myFile, customer);
// Close the file
CloseFile(myFile);
// Reopen the file in read only mode
FileMode := fmOpenRead;
Reset(myFile);
// Display the file contents
while not Eof(myFile) do
begin
Read(myFile, customer);
if customer.male
then ShowMessage('Man with name '+customer.name+
' is '+IntToStr(customer.age))
else ShowMessage('Lady with name '+customer.name+
' is '+IntToStr(customer.age));
end;
// Close the file for the last time
CloseFile(myFile);
end;
The problem with saving a record containing dynamic array or real strings (or other "managed" types for that matter) is, it's not an big blob of memory containing everything, it's more like a tree. Someone or something needs to go over everything and save it to storage, somehow. Other languages (Python for example) include all sorts of facilities to transform most objects to text (serialize it), save it to disk and reload it (deserialize it).
Even though a Embarcadero-provided solution doesn't exist for Delphi, one can be implemented using the extended RTTI available in Delphi 2010. A ready-made implementation is available in the DeHL library (here's a blog post about it) - but I can't say much about the implementation, I never used DeHL.
An other option is the one you want to avoid: manually serialize the record to an TStream; It's actually not half difficult. Here's the kind of code I usually use to read/write objects to a file stream:
procedure SaveToFile(FileName:string);
var F:TFileStream;
W:TWriter;
i:Integer;
begin
F := TFileStream.Create(FileName, fmCreate);
try
W := TWriter.Create(F, 128);
try
// For every field that needs saving:
W.WriteString(SomeStr);
W.WriteInteger(TheNumber);
// Dynamic arrays? Save the length first, then save
// every item. The length is needed when reading.
W.WriteInteger(Length(DArray));
for i:=0 to High(DArray) do
W.WriteString(DArray[i]);
finally W.Free;
end;
finally F.Free;
end;
end;
procedure ReadFromFile(FileName:string);
var F:TFileStream;
R:TReader;
i,n:Integer;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
R := TReader.Create(F, 128);
try
SomeStr := R.ReadString;
TheNumber := R.ReadInteger;
// Reading the dynamic-array. We first get the length:
n := R.ReadInteger;
SetLength(DArray, n);
// And item-by-item
for i:=0 to n-1 do
DArray[i] := R.ReadString;
finally R.Free;
end;
finally F.Free;
end;
end;
精彩评论