Whats wrong with my code? I try to save and then load again an object to a blob field in a database, but get nothing back.
Records are saved, but I can't say if the data was written correctly because I cant read the data back.
Here is the object type:
TMyObject = class
Name: string;
end;
And here I try to save:
procedure TForm1.btnSaveObjectClick(Sender: TObject);
var
myObject: TmyObject;
aMemoryStream: TMemoryStream;
begin
myObject:= TMyObject.Create;
myObject.Name:=edtName.Text;
aMemoryStream:= TMemoryStream.Create;
aMemoryStream.Write(myObject, myObject.InstanceSize);
aMemoryStream.Position:=0;
with TSQLQuery.Create(nil) do
begin
DataBase:=Conn;
SQL.Text:='INSERT INTO testtable (data) VALUES (:data)';
try
ParamByName('data').LoadFromStream(aMemoryStream, ftBlob);
ExecSQL;
TX.CommitRetaining;
finally
aMemoryStream.Free;
myObject.Free;
Free;
end;
end;
end;
Trying to read the data back up again:
procedure TForm1.btnLoadObjectClick(Sender: TObject);
var
myObject: TMyObject;
BlobStream : TStream;
begin
with TSQLQuery.Create(nil) do
begin
DataBase:=Conn;
SQL.Text:='SELECT data FROM testtable';
myObject:= TmyObject.Create;
try
Open;
Last;
BlobStream:= CreateBlobStream(FieldByName('data'), bmread);
BlobStream.Position:=0;
BlobStream.Read(myObject, BlobStream.Size);
ShowMessage('Stored Name: ' +myObject.Name);
finally
myObject.Free;
Free;
end;
end;
end;
Also, should BlobStream be free'd?
The correct way for storing your objects into files, streams or blob fields is to first extend your object with additional methods for loading and saving data from your objects fields (objects internal variables) into single memory block.
You do this by saving one field after another.
If your objects are dynamically sized (containing dynamic arrays or strings) don't forget to store the size of these separately so you will know how much data belongs to them when loading your objects later on.
Also if your objects contain some other objects you also need them to have similar methods for storing and loading their data.
The implementation depends heavily on your object's class design. Here a code example for a string field:
type
TMyObject = class
public
Name: string;
procedure SaveToStream(AStream: TStream);
procedure LoadFromStream(AStream: TStream);
end;
procedure TMyObject.SaveToStream(AStream: TStream);
var
Len: Integer;
begin
Len := Length(Name);
AStream.Write(Len, SizeOf(Len));
AStream.Write(PChar(Name)^, Len);
end;
procedure TMyObject.LoadFromStream(AStream: TStream);
var
Len: Integer;
begin
AStream.Read(Len, SizeOf(Len));
SetString(Name, PChar(nil), Len);
AStream.Read(PChar(Name)^, Len);
end;
With this, it is possible to use the stream that CreateBlobStream returns and just save myObject to the blobfield:
BlobField := FieldByName('data') as TBlobField;
Stream := CreateBlobStream(BlobField, bmWrite);
myObject.SaveToStream(Stream);
..or load it from the stream:
Stream:= CreateBlobStream(FieldByName('data'), bmread);
myObject.LoadFromStream(Stream);
Related
I'm trying to write a marshaller for a TPicture so that I can send objects classes containing a TPicture though datasnap methods.
I'm having difficulties finding an appropriate way of doing that, I have tried to send TMemoryStream as TObject but it appears that the "data from the picture is lost". I have tried to put the picture into a string - but that would, I guess, give me all kind's of troubles putting binary data into a string.
Does anyone have any ideas on how to marshal TPicture properly?
Here's what I'm doing, so far it looks like I get the picture into the result tobject, but I get a leak since I create an instance of the PictureMarshal class.
Don't mind the FChildren, that works fine.
type
TPictureMarshall = class
FPictureName : string;
FPictureSize : Integer;
FPictureData : TBytes;
end;
TPersonMar = class(TObject)
private
FStringList : TStringList; // complex object that needs marshalling
FChildren: array of TPerson; // complex object that needs marshalling
FMar : TJsonMarshal;
FPicture : TPicture;
procedure RegisterConverters;
procedure SetStringlist(const Value: TStringList);
public
function Marshal : string; // should handle marshalling
property Stringlist : TStringList read FStringlist write SetStringlist;
constructor Create;
destructor Destroy; override;
procedure AddChild(AKid: TPerson);
end;
procedure TPersonMar.RegisterConverters;
begin
FMar.RegisterConverter(ClassType, 'FPicture', function(Data: TObject; Field: String): TObject
var
AMemoryStream: TMemoryStream;
APic : TPictureMarshall;
begin
AMemoryStream := TMemoryStream.Create;
try
result := TPictureMarshall.Create; // but who frees this?
FPicture.Graphic.SaveToStream(AMemoryStream);
TPictureMarshall(Result).FPictureName := 'Somepicturefilename.png';
TPictureMarshall(Result).FPictureSize := AMemoryStream.Size;
SetLength(TPictureMarshall(Result).FPictureData, TPictureMarshall(Result).FPictureSize);
ZeroMemory(TPictureMarshall(Result).FPictureData, TPictureMarshall(Result).FPictureSize);
AMemoryStream.Position := 0; // reset position
AMemoryStream.ReadBuffer(TPictureMarshall(Result).FPictureData, AMemoryStream.Size);
finally
AMemoryStream.Free;
end;
end);
end;
"FMar":null,"FPicture":{"type":"TestObjU.TPictureMarshall","id":14,"fields":{"FPictureName":"Somepicturefilename.png","FPictureSize":71290,"FPictureData":[137,80,78,71,13,...snip]}}}}
I'm trying to store a pointer to a record in a Tqueue, then dequeue the pointer later and extract the data but am getting in a muddle with the pointers and keep getting a 'Abstract Error'
Can anyone please see what I am doing wrong and advise me on the correct solution?
(BTW, Initially I had it all without the ^ but then realised my mistake but was surprised that it still gave an error)
The record holds email data that gets sent to a smtp server, It uses a TstringList to hold each line of the body and another one to hold each attachment filename
This is the record structure used to store the email data
TPtrEmailData = ^TEmailDataRec;
TEmailDataRec = record
ToAddr : string; //one email address
CcAddr : string; //addresses delimitated by semicolons
BccAddr : string; //addresses delimitated by semicolons
Subject : String;
Body : TStrings; //each string contains one line of the body
attachments: TStrings;//each string contains a filename
end;
To create the records I use
function TFrmSendEmail.CreateNewEmailRec: TPtrEmailData;
var
EmailRecPtr : TPtrEmailData;
begin
new(EmailRecPtr); //make a new record
EmailRecPtr^.Body := Tstrings.Create ;
EmailRecPtr^.attachments := Tstrings.create;
result := EmailRecPtr ;
end;
and to free them after dequeing I use
procedure TFrmSendSllSmtptEmail.DestroyEmailRec(EmailRecPtr : TPtrEmailData);
//frees memory for the Tstrings and then frees the record
begin
freeandnil(EmailRecPtr^.Body); //free one Tstringlist
FreeAndNil(EmailRecPtr^.attachments); //and the other
FreeAndNil(EmailRecPtr ); //now free the precord pointer
end;
CreateNewEmailRec is called when I enqueue a new record pointer in the queue using the following, passing in the memo and list box containig th ebody and attachments. This is where I get the error.
procedure TFrmSendEmail.AddToEmailQueue(ToAddr, CCAddr,
BccAddr,Subject:String;
Body: Tmemo; Attachments: TListBox);
var
i : integer;
s : string;
EmailRecPtr : TPtrEmailData;
begin
EmailRecPtr := CreateNewEmailRec; //allocate memory
//deallocated in RemoveFromEmailQueue
EmailRecPtr^.ToAddr := ToAddr;
EmailRecPtr^.CCAddr := CCAddr;
EmailRecPtr^.BccAddr := BccAddr;
for I := 0 to Attachments.Count - 1 do
begin
s := Attachments.Items[i];
EmailRecPtr^.attachments.add(s ); <---- !!! get abstract error here
end;
for I := 0 to Body.lines.Count - 1 do
begin
s := Body.lines[i];
EmailRecPtr^.Body.Add(s) ;
end;
EmailQueue.Enqueue(EmailRecPtr );
end;
and DestroyEmailRec is called when I dequeue a pointer to use the data in
procedure TFrmSendEmail.RemoveFromEmailQueue(var ToAddr,
CCAddr,
BccAddr,
Subject: String;
var Body,
Attachments: TStringlist);
var
EmailRecPtr :TPtrEmailData;
i : integer;
s : string;
begin
if EmailQueue.Count > 0 then
begin
Body.Clear;
Attachments.Clear;
EmailRecPtr := EmailQueue.Dequeue; //get pointer to next record
ToAddr := EmailRecPtr^.ToAddr; //populate procedure parameters
CCAddr := EmailRecPtr^.CCAddr;
BccAddr := EmailRecPtr^.BccAddr;
for EmailRecPtr^.attachments.Count - 1 do
begin
s := EmailRec^.attachments[i];
Attachments.Add(s) ;
end;
for I := 0 to EmailRecPtr ^.Body.Count - 1 do
begin
s := EmailRecPtr ^.Body[i];
Body.Add(s);
end;
DestroyEmailRec(EmailRecPtr); //release memory
end;
The call to RemoveFromEmailQueue passes in a couple of created TStringLists
TheBody := Tstringlist.Create ;
TheAttachments := Tstringlist.create;
try
RemoveFromEmailQueue(ToAddr, CCAddr, BccAddr, Subject,TheBody,TheAttachments);
// do stuff with the data;
finally
TheBody.Free;
TheAttachments.Free;
end;
Oh, and the queue is declared as
var
EmailQueue : Tqueue<TPtrEmailData>;
You get the "Abstract Error" because you use an astract object (TStrings)! In the TFrmSendEmail.CreateNewEmailRec method replace TStrings with TStringList:
function TFrmSendEmail.CreateNewEmailRec: TPtrEmailData;
begin
new(result); //make a new record
Result^.Body := TStringList.Create ;
Result^.attachments := TStringList.create;
end;
Also, you can't free records using FreeAndNil! So your method to free the record should be like
procedure TFrmSendSllSmtptEmail.DestroyEmailRec(EmailRecPtr : TPtrEmailData);
//frees memory for the Tstrings and then frees the record
begin
EmailRecPtr^.Body.Free; //free one Tstringlist
EmailRecPtr^.attachments.Free; //and the other
Dispose(EmailRecPtr); //now free the precord pointer
end;
I am trying to implement a simple contact manager using the VirtualStringTree component. I have it set up to look like a list-view component with only three columns that will all contain text:
For the data structure, I am using svTree by Linas, which was mentioned in another Stack Overflow question.
I have declared a record like this:
type
TMainData = record
Name, Email, Password: string;
end;
In the form's OnCreate I have this:
procedure TForm1.FormCreate(Sender: TObject);
begin
MyTree := TSVTree<TMainData>.Create(False);
MyTree.VirtualTree := vst1;
end;
I am adding the data from TEdits like this:
procedure TForm1.BuildStructure;
var
svNode: TSVTreeNode<TMainData>;
Data: TMainData;
begin
MyTree.BeginUpdate;
try
Data.Name := edtname.Text;
Data.Email := edtEmail.Text;
Data.Password := edtPassword.Text;
svNode := MyTree.AddChild(nil, Data);
finally
MyTree.EndUpdate;
end;
Label1.Caption := 'Count: '+IntToStr(MyTree.TotalCount);
end;
How can I save this into a stream or a file to be loaded back? I have tried using MyTree.SaveToFile('C:/Test.dat') and MyTree.LoadFromFile('C:/Test.dat'), but when it's loaded back the tree view contains no data, only a blank row.
You need to set OnLoadNode and OnSaveNode procedures for your TSVTree and implement your logic here. You can look at Project2 in the Demos folder. E.g.:
uses
uSvHelpers;
MyTree.OnSaveNode := DoSave;
MyTree.OnLoadNode := DoLoad;
procedure TForm1.DoLoad(Sender: TSVTree<TMainData>; Node: TSVTreeNode<TMainData>; Stream: TStream);
var
obj: TMainData;
begin
//
if Assigned(Node) then
begin
//read from stream
//read in correct order
obj.Name := Stream.AsString;
obj.Email := Stream.AsString;
obj.Password := Stream.AsString;
Node.FValue := obj;
end;
end;
procedure TForm1.DoSave(Sender: TSVTree<TMainData>; Node: TSVTreeNode<TMainData>; Stream: TStream);
begin
if Assigned(Node) then
begin
//read from stream
Stream.WriteString(Node.FValue.Name);
Stream.WriteString(Node.FValue.Email);
Stream.WriteString(Node.FValue.Password);
end;
end;
After that you can just call MyTree.SaveToFile('C:/Test.dat') or MyTree.LoadFromFile('C:/Test.dat'). In my demo and this example i've used another unit (uSvHelpers) which implements TStream helper for more OO stream support. You can of course use your own way to write your data information to stream.
Looks like you need to implement the OnSaveNode and OnLoadNode events:
procedure TForm.VTLoadNode(Sender: TBaseVirtualTree;
Node: PVirtualNode; Stream: TStream);
begin
// Load Node Data record from the stream
end;
procedure TForm.VTSaveNode(Sender: TBaseVirtualTree;
Node: PVirtualNode; Stream: TStream);
begin
// Save Node Data record to the stream
end;
I'm sorry I'm not being clear...lets try again
I have a record type :
MyRecord = Record
Name: string;
Age: integer;
Height: integer;
several more fields....
and an INI file with:
[PEOPLE]
Name=Maxine
Age=30
maybe one or two other key/value pairs
All I want to do is load the record with the data from the INI file.
I have the data from the INI in a TStringList I want to be able to loop through the TStringList and assign/update only the Record Fields with key value pairs in the TStringList.
Charles
So you have an INI file with the content
[PEOPLE]
Name=Maxine
Age=30
and want to load it into a record defined by
type
TMyRecord = record
Name: string;
Age: integer;
end;
? That is very easy. Just add IniFiles to the uses clause of your unit, and then do
var
MyRecord: TMyRecord;
procedure TForm1.Button1Click(Sender: TObject);
begin
with TIniFile.Create(FileName) do
try
MyRecord.Name := ReadString('PEOPLE', 'Name', '');
MyRecord.Age := ReadInteger('PEOPLE', 'Age', 0);
finally
Free;
end;
end;
Of course, the MyRecord variable need not be a global variable. It can also be a local variable or a field in a class. But that all depends on your exact situation, naturally.
A Simple Generalisation
A slightly more interesting situation is if your INI files contains several people, like
[PERSON1]
Name=Andreas
Age=23
[PERSON2]
Name=David
Age=40
[PERSON3]
Name=Marjan
Age=49
...
and you want to load it into an array of TMyRecord records, then you can do
var
Records: array of TMyRecord;
procedure TForm4.FormCreate(Sender: TObject);
var
Sections: TStringList;
i: TIniFile;
begin
with TIniFile.Create(FileName) do
try
Sections := TStringList.Create;
try
ReadSections(Sections);
SetLength(Records, Sections.Count);
for i := 0 to Sections.Count - 1 do
begin
Records[i].Name := ReadString(Sections[i], 'Name', '');
Records[i].Age := ReadInteger(Sections[i], 'Age', 0);
end;
finally
Sections.Free;
end;
finally
Free;
end;
end;
If you have the INI section in a string list you can just use the Values[] property:
String list contents
Name=Maxine
Age=30
Code to read into record
MyRecord.Name := StringList.Values['Name']
MyRecord.Age = StrToInt(StringList.Values['Age'])
Naturally you would want to handle errors one way or another, but this the the basic idea.
For some reason my OpenID account no longer exists even when I used it yesterday. But anyway.
I need to save record data into a .dat file. I tried a lot of searching, but it was all related to databases and BLOB things. I wasn't able to construct anything from it.
I have the following record
type
Scores = record
name: string[50];
score: integer;
end;
var rank: array[1..3] of scores;
I just need a simple way of saving and reading the record data from a .dat file. I had the book on how to do it, but that's at school.
You should also take a look at the file of-method.
This is kinda out-dated, but it's a nice way to learn how to work with files.
Since records with dynamic arrays (including ordinary strings) can't be stored to files with this method, unicode strings will not be supported. But string[50] is based on ShortStrings and your record is therefore already non-unicode...
Write to file
var
i: Integer;
myFile: File of TScores;
begin
AssignFile(myFile,'Rank.dat');
Rewrite(myFile);
try
for i := 1 to 3 do
Write(myFile, Rank[i]);
finally
CloseFile(myFile);
end;
end;
Read from file
var
i: Integer;
Scores: TScores;
myFile: File of TScores;
begin
AssignFile(myFile, 'Rank.dat');
Reset(myFile);
try
i := 1;
while not EOF(myFile) do
begin
Read(myFile, Scores);
Rank[i] := Scores; //You will get an error if i is out of the array bounds. I.e. more than 3
Inc(i);
end;
finally
CloseFile(myFile);
end;
end;
Use streams. Here is a simple demo (just demo - in practice there is no need to reopen file stream every time):
type
Scores = record
name: string[50];
score: integer;
end;
var rank: array[1..3] of scores;
procedure WriteScores(var Buf; Count: Integer);
var
Stream: TStream;
begin
Stream:= TFileStream.Create('test.dat', fmCreate);
try
Stream.WriteBuffer(Buf, SizeOf(Scores) * Count);
finally
Stream.Free;
end;
end;
procedure ReadScore(var Buf; Index: Integer);
var
Stream: TStream;
begin
Stream:= TFileStream.Create('test.dat', fmOpenRead or fmShareDenyWrite);
try
Stream.Position:= Index * SizeOf(Scores);
Stream.ReadBuffer(Buf, SizeOf(Scores));
finally
Stream.Free;
end;
end;
// write rank[1..3] to test.dat
procedure TForm1.Button1Click(Sender: TObject);
begin
rank[2].name:= '123';
WriteScores(rank, Length(Rank));
end;
// read rank[2] from test.dat
procedure TForm1.Button2Click(Sender: TObject);
begin
rank[2].name:= '';
ReadScore(rank[2], 2 - Low(rank));
ShowMessage(rank[2].name);
end;
Look in the help under "blockread" and or "blockwrite". There probably will be an example