Saving a TObject to a File - delphi

How can one save an Object, in its current state, to a file? So that it can immediately be read and restored with all its variables.

As already stated, the easiest way is to use a Stream and its WriteComponent and ReadComponent methods.
But be aware that :
- it works for descendants of TComponent, not plain TObject;
- only for the published properties (those saved in a dfm), not the public ones nor (a fortiori) the privwte ones;
- you have to pay a special attention for the Name property when restoring the component.
You may find some code you could use in these SO answers: Replace visual component at runtime in Delphi, Duplicating components at Run-Time

What you are looking for is called object persistance. This article might help, and there are many others if you google for "delphi persisting objects".

If you descend your object from TComponent, you can use some built-in functionality to stream the object to a file. I think this only works well for simple objects.
Some sample code to get you started:
unit Unit1;
interface
uses
Classes;
type
TMyClass = class(TComponent)
private
FMyInteger: integer;
FMyBool: boolean;
FMyString: string;
public
procedure ToFile(AFileName: string);
published
property MyInteger: integer read FMyInteger write FMyInteger;
property MyString: string read FMyString write FMyString;
property MyBool: boolean read FMyBool write FMyBool;
end;
implementation
{ TMyClass }
procedure TMyClass.ToFile(AFileName: string);
var
MyStream: TFileStream;
begin
MyStream := TFileStream.Create(AFileName);
try
Mystream.WriteComponent(Self);
finally
MyStream.Free;
end;
end;
end.

There's also a roll-your-own XML method here on S.O.

Very simple and efficient solution: DragonSoft's XML Class Serializer
Also you can use JVCL TJvAppXMLFileStorage:
uses
JvAppXMLStorage;
var
Storage: TJvAppXMLFileStorage;
begin
Storage := TJvAppXMLFileStorage.Create(nil);
try
Storage.WritePersistent('', MyObject);
Storage.Xml.SaveToFile('S:\TestFiles\Test.xml');
Storage.Xml.LoadFromFile('S:\TestFiles\Test.xml');
Storage.ReadPersistent('', MyObject);
finally
Storage.Free;
end;
end;

There is a good tutorial here. Keep in mind that you have to have RTTI (run time type information) to save an object at run-time using this approach, so it will only capture published properties of a class.

You've already gotten some good answers to your question. Depending on what you're actually doing, it might be desirable to use a pre-built library or component to save objects. This is an inexpensive and nifty library/component set that makes it trivial to persist and restore objects, and pretty easily (i.e., with a little bit of code) accommodates persisting even unpublished members of an object:
http://www.deepsoftware.ru/rsllib/index.html Not something that's rocket science, but if you're doing a lot of this sort of thing this component provides a nice framework for it.
Developer Express also includes a general purpose cxPropertiesStore component as part of the ExpressEditors library that comes with some of their components.

Related

Delphi - Extract setter method's name of a property

In the following type:
MyClass = class(TInterfacedPersistent)
private
FMyProperty: Integer;
published
procedure setMyProperty(Value: Integer); virtual;
property MyProperty: Integer read FMyProperty write setMyProperty;
I would like to know the name of the setter method of the "MyProperty" property via RTTI. I've tried the following:
procedure ShowSetterMethodsNames(pMyObject: TObject);
var
vPropList: TPropList;
vCount, I: Integer;
begin
vCount:= GetPropList(pMyObject.ClassInfo, tkProperties, #vPropList);
for I:= 0 to vCount -1 do
begin
if Assigned(vPropList[I]^.SetProc) then
ShowMessage(pMyObject.ClassType.MethodName(vPropList[I]^.SetProc));
end;
end;
Although the pointer is not nil, all I have is an empty message. Does anybody have some tip to me?
P.S.: I'm using Delphi XE4, and I know I should use extended RTTI instead of classic, but anyway, I can't do what I want in both features... So, any help will be appreciated. Thanks for the replies.
FINAL EDITION, problem solved:
Here is the code working, based in the (help of my friends and...) RTTI unit (DoSetValue method of TRTTIInstanceProperty class):
procedure ShowVirtualSettersNames(pObject: Pointer);
var
vSetter, vPointer: Pointer;
vPropList: TArray<TRttiProperty>;
vProp: TRttiProperty;
begin
vPropList:= RTTIUtils.ExtractProperties(TObject(pObject).ClassType); // Helper to get properties from a type, based in extended RTTI
for vProp in vPropList do
begin
vPointer:= TRttiInstanceProperty(vProp).PropInfo^.SetProc;
vPointer:= PPointer(PInteger(pObject)^ + Smallint(vPointer))^;
ShowMessage(TObject(pObject).ClassType.MethodName(vPointer));
end;
end;
This ONLY WORKS FOR VIRTUAL SETTERS, for statics the message is empty. Thanks everyone!
You can retrieve this method name, if
a) move the method to the published section (classic RTTI works with this section only (more accurately - compiled with {$M+} directive))
b) use right class specifier - MyClass.MethodName, because MethodName is class function
This code works on D7 and XE3:
MyClass = class(TInterfacedPersistent)
private
FMyProperty: Integer;
published
procedure setMyProperty(Value: Integer);
property MyProperty: Integer read FMyProperty write setMyProperty;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ppi: PPropInfo;
begin
ppi := GetPropInfo(MyClass, 'MyProperty');
ShowMessage(MyClass.MethodName(ppi.SetProc));
end;
P.S. What Delphi version are you using? What about Extended RTTI (since D2010)?
Read c:\rad studio\9.0\source\rtl\common\System.Rtti.pas
procedure TRttiInstanceProperty.DoSetValue
The setter of the property may be
a field (variable)
a static procedure
a virtual procedure (your case)
And those cases make PropInfo^.SetProc have different semantics of its value.
Direct address only applies to static procedures. For virtual methods you add a VMT offset and take the code address from that memory cell, as specified in that code i mentioned (but would not quote for copyright reasons).
Or you just could use TRttiProperty.SetValue and let Delphi do all those little under the hood details. See http://docwiki.embarcadero.com/Libraries/XE2/en/System.Rtti.TRttiProperty.SetValue
EDIT:
the code removed - it did not worked verbatim and the topic starter provided working version.
Regarding and I know I should use Extended RTTI instead of classic one - that is questionable claim. Extended RTTI is known to work noticeably slower than classic one. Dunno if someone did profiled it, but i suspect that is mostly due to the slow code of TValue. You can google and find that lot of people complained of slow TValue implementation and provided alternative ones with fixed efficiency. However since Extended RTTI only uses stock TValue it cannot benefit from those implementations and remains slower than classic one.

Can you remove a published property from a delphi component and not cause errors on the forms where the component is used?

I have a component that was developed by my company, and would like to remove one of the published properties that is no longer needed and clashes with the way the component works now.
Is there a way to remove the property and not cause property not found errors at runtime or design time when forms that use the component are loaded?
i.e. Is there a way to make Delphi silently drop a component property?
Yes. Just remove the property, then override DefineProperties and process it there. That will satisfy the streaming system by loading the value which you can just throw away. Be sure to do nothing when writing the stream.
Depending on the property, the easiest would be to leave the property, but mark it as deprecated and just have the Read/Write bits point to a field that's never used.
Alternatively, you can override DefineProperties and call Filer.DefineProperty('PropertyName', ReadProc); where PropertyName is the property you've removed, and ReadProc is a function that calls various TReader Read* functions. This has the advantage that the properties aren't in your interface anymore.
For example, say you've removed this property:
property Center: TPoint read FPoint write SetPoint;
Here's what you would add to your component:
TMyComponent = class...
private
procedure SkipReadPoint(Reader: TReader);
protected
procedure DefineProperties(Filer: TFiler); override;
end;
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('Center', SkipReadPoint, nil, False);
end;
procedure TMyComponent.SkipReadPoint(Reader: TReader);
begin
Reader.ReadListBegin;
Reader.ReadInteger;
Reader.ReadInteger;
Reader.ReadListEnd;
end;
Skipping a simple type like Boolean or Integer is easier, since you can just call ReadBoolean or ReadInteger without bothering with the ReadList functions.
In our case we had a lot of similar properties across a bunch of classes (for CLX compatibility) so we had global "dummy" functions like this:
procedure DummyReadBool(Self: Pointer; Reader: TReader);
begin
Reader.ReadBoolean;
end;
const
SkipReadBool: TMethod = (Code: #DummyReadBool; Data: nil);
and then the DefineProperty call looks like this:
Filer.DefineProperty('PropName', TReaderProc(SkipReadBool), nil, False);
That way each class doesn't have to have duplicate Skip* functions.
You can't "silently" remove it, if it's stored in the .DFM or referenced in code.
If it wasn't referenced in code, and it's stored in text .DFM files, the JVCL has a utility called DFM Cleaner that will go through the .DFMs and remove the properties for you. Then you can open the forms and recompile your applications safely. It's part of the JVCL and is placed in the $(JVCL)\DevTools folder during install.

Using Generic containers in Delphi XE - always?

Generic containers can be a time saver when having a item, and a strongly typed list of those items. It saves the repetitive coding of creating a new class with perhaps a TList internal variable, and typed Add/Delete type methods, among other benefits (such as all the new functionality provided by the Generic container classes.)
However, is it recommended to always use generic containers for strongly typed lists going forward? What are the specific downsides of doing so? (If not worried about backwards compatibility of code.) I was writing a server application yesterday and had a list of items that I created 'the old way' and was going to replace it with a generic list but decided to keep it lean, but mostly out of habit. (Should we break the habit and start a new one by always using generics?)
In Delphi XE, there is no reason not to use generic containers.
Switching from the old method with casting will give you:
cleaner, type-safe, less error-prone code,
enumerators, for in loops,
the samebetter performance characteristics.
This was prompted by Deltic's answer, I wanted to provide an counter-example proving you can use generics for the animal feeding routine. (ie: Polymorphic Generic List)
First some background: The reason you can feed generic animals using a generic base list class is because you'll usually have this kind of inheritance:
TBaseList = class
// Some code to actually make this a list
end
TSpecificList = class(TBaseList)
// Code that reintroduces the Add and GetItem routines to turn TSpecificList
// into a type-safe list of a different type, compatible with the TBaseList
end
This doesn't work with generics because you'll normally have this:
TDogList = TList<TDog>
end
TCatList = TList<TCat>
end
... and the only "common ancestor" for both lists is TObject - not at all helpful. But we can define a new generic list type that takes two class arguments: a TAnimal and a TSpecificAnimal, generating a type-safe list of TSpecificAnimal compatible with a generic list of TAnimal. Here's the basic type definition:
TCompatibleList<T1:class;T2:class> = class(TObjectList<T1>)
private
function GetItem(i: Integer): T2;
public
procedure Add(A:T2);
property Item[i:Integer]:T2 read GetItem;default;
end;
Using this we can do:
TAnimal = class;
TDog = class(TAnimal);
TCat = class(TAnimal);
TDogList = TCompatibleList<TAnimal, TDog>;
TCatList = TCompatibleList<TAnimal, TCat>;
This way both TDogList and TCatList actually inherit from TObjectList<TAnimal>, so we now have a polymorphic generic list!
Here's a complete Console application that shows this concept in action. And that class is now going into my ClassLibrary for future reuse!
program Project23;
{$APPTYPE CONSOLE}
uses
SysUtils, Generics.Collections;
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
TCat = class(TAnimal)
end;
TCompatibleList<T1:class;T2:class> = class(TObjectList<T1>)
private
function GetItem(i: Integer): T2;
public
procedure Add(A:T2);
property Item[i:Integer]:T2 read GetItem;default;
end;
{ TX<T1, T2> }
procedure TCompatibleList<T1, T2>.Add(A: T2);
begin
inherited Add(T1(TObject(A)));
end;
function TCompatibleList<T1, T2>.GetItem(i: Integer): T2;
begin
Result := T2(TObject(inherited Items[i]));
end;
procedure FeedTheAnimals(L: TObjectList<TAnimal>);
var A: TAnimal;
begin
for A in L do
Writeln('Feeding a ' + A.ClassName);
end;
var Dogs: TCompatibleList<TAnimal, TDog>;
Cats: TCompatibleList<TAnimal, TCat>;
Mixed: TObjectList<TAnimal>;
begin
try
// Feed some dogs
Dogs := TCompatibleList<TAnimal, TDog>.Create;
try
Dogs.Add(TDog.Create);
FeedTheAnimals(Dogs);
finally Dogs.Free;
end;
// Feed some cats
Cats := TCompatibleList<TAnimal, TCat>.Create;
try
Cats.Add(TCat.Create);
FeedTheAnimals(Cats);
finally Cats.Free;
end;
// Feed a mixed lot
Mixed := TObjectList<TAnimal>.Create;
try
Mixed.Add(TDog.Create);
Mixed.Add(TCat.Create);
FeedTheAnimals(Mixed);
finally Mixed.Free;
end;
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Should we break the habit and start a new one by always using generics?
YES
In most cases, yes, generic containers are a good thing. However, the compiler generates a lot of duplicate code, and unfortunately the linker doesn't know how to remove it yet, so heavy use of generics could result in a bloated executable. But other than that, they're great.
In the spirit of Cosmin's answer, essentially a response to Deltic's answer, here is how to fix Deltic's code:
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
TAnimalList<T:TAnimal> = class(TList<T>)
procedure Feed;
end;
TDogList = TAnimalList<TDog>;
Now you can write:
var
Dogs: TDogList;
...
Dogs.Feed;
You wrote about backwards compatibility... this is my biggest concern, if (like me) you are writing libraries which should better compile with most common versions of Delphi.
Even if you're using only XE for a closed project, you are probably making some custom libraries of your own, even if you never publish the code. We all have such favorite units at hand, just available not to reinvent the wheel for every project.
In a future assignment, you may have to maintain some older code, with no possibility to upgrade to a newer Delphi version (no money for the 1,000,000 code lines migration and review). In this case, you could miss your XE-only libraries, with shiny generic-based lists...
But for a 100% "private" application, if you are sure that you will never have to maintain older Delphi code, I don't see any reason not to use generics. My only concern is the duplicated code issue (as quoted by Mason): the CPU cache can be filled with unnecessary code, so execution speed could suffer. But in real app, I think you won't see any difference.
Note: I've just added some new features to my TDynArray wrapper. I tried to mimic the sample code from EMB docwiki. So you could have generic-like features, with good old Delphi versions... Of course, generics are better for working with classes, but with some arrays and records, it just rocks!
If you need polymoprhic lists then Generics are a hindrance, not a help. This does not even compile, for example, because you cannot use a TDogList where a TAnimalList is required:
uses
Generics.Collections;
type
TAnimal = class
end;
TDog = class(TAnimal)
end;
TAnimalList = TList<TAnimal>;
TDogList = TList<TDog>;
procedure FeedTheAnimals(const aList: TAnimalList);
begin
// Blah blah blah
end;
var
dogs: TDogList;
begin
dogs := TDogList.Create;
try
FeedTheAnimals(dogs);
finally
dogs.Free;
end;
end;
The reasons for this are quite clear and easily explained, but it is just as equally counter intuitive.
My own view is that you can save a few seconds or minutes (if you are a slow typist) by using a generic instead of rolling a type safe container more specific and appropriate to your needs, but you may well end up spending more time working around the problems and limitations of Generics in the future than you saved by using them to start with (and by definition if you haven't been using generic containers up to now then you don't know what those problems/limitations might be until you run into them).
If I need a TAnimalList then the chances are I need or could benefit from additional TAnimal specific methods on that list class that I would like to inherit in a TDogList, which in turn may introduce additional specific members relevant to it's TDog items.
(Animal and Dog being used for illustrative purposes only, of course. I don't actually work on veterinarian code currently - LOL)
The problem is, you don't always know this at the start.
Defensive programming principles suggest (to me, ymmv) that painting yourself into a corner for the sake of a bit of time saving is likely to end up costing a lot in the future. And if it doesn't, the additional "cost" of not taking the up front saving is itself negligible.
Plus your code is more shareable with users of older Delphi versions, if you are inclined to be so generous.
:)

Is there a function like PHP's vardump in Delphi?

I've given up on the Delphi 7 debugger and am pretty much relying on outputdebugstrings. Is there a standard function I can call to get the contents of an object as a string like the debugger would if I set a breakpoint?
Not exactly what your looking for, but you can use RTTI to get access to the values of various published properties. The magical routines are in the TypInfo unit. The ones you are probably most interested in are GetPropList which will return a list of the objects properties, and GetPropValue which will allow you to get the values of the properties.
procedure TForm1.DumpObject( YourObjectInstance : tObject );
var
PropList: PPropList;
PropCnt: integer;
iX: integer;
vValue: Variant;
sValue: String;
begin
PropCnt := GetPropList(YourObjectInstance,PropList);
for iX := 0 to PropCnt-1 do
begin
vValue := GetPropValue(YourObjectInstance,PropList[ix].Name,True);
sValue := VarToStr( vValue );
Memo1.Lines.Add(PropList[ix].Name+' = '+sValue );
end;
end;
for example, run this with DumpObject(Self) on the button click of the main form and it will dump all of the properties of the current form into the memo. This is only published properties, and requires that the main class either descends from TPersistent, OR was compiled with {$M+} turned on before the object.
Rumor has it that a "reflector" like ability will be available in a future version of Delphi (possibly 2010).
Consider something like Codesite which is a much more complete tracing solution. It allows you to output much more complex info, and then search, print, and analyse the data. But for your purposes, you can simply send an object to it with Codesite.Send('Before', self); and you get all the RTTI available properties in the log. Do an "After" one too, and then you can compare the two in the Codesite output just by selecting both. It's saved me many times.
if delphi 7 is the .NET version, then you could do (some of) that with reflection. (not easy, but not terribly hard). if it's the normal, compiled thing, then it's a hard problem and the debugger is you best bet, apart from specialized printing functions/methods.

RTTI on objects in Delphi

I'm trying to parse objects to XML in Delphi, so I read about calling the object's ClassInfo method to get its RTTI info.
The thing is, this apparently only works for TPersistent objects. Otherwise, I have to specifically add a compiler directive {$M+} to the source code for the compiler to generate RTTI info.
So I happily added the directive, only to find that, even if it did return something from the ClassInfo call (it used to return nil), now I cannot retrieve the class' properties, fields or methods from it. It's like it created the object empty.
Any idea what am I missing here? Thanks!
Did you put those properties and methods into the published section?
Besides that, 'classical' RTTI ($TYPEINFO ON) will only get you information on properties, not on methods. You need 'extended' RTTI ($METHODINFO ON) for those.
Good starting point for extended RTTI: David Glassborow on extended RTTI
(who would believe that just this minute I finished writing some code that uses extended RTTI and decided to browse the Stack Overflow a little:))
RTTI will only show you published properties,etc. - not just public ones.
Try your code with a TObject and see what happens - if that isn't working, post your code because not everyone is psychic.
Have you considered using the TXMLDocument component? It will look at your XML and then create a nice unit of Delphi classes that represents your XML file -- makes it really, really easy to read and write XML files.
As for the RttiType problem returning only nil, this probably occurs for one reason: in your test, you did not instantiate the class at any time. The compiler, because it never has a reference to this class (because it is not an instance at all), simply removes it from the information as a form of optimization. See the two examples below. The behavior is different when you have the class instantiated at some point in your code or not.
Suppose the following class:
type
TTest = class
public
procedure Test;
end;
and the following code below:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
so far, TTest class information is not available for use by RTTI. However, when we create at some point, within the application, then a reference is created for it within the compile, which makes this information available:
var
LContext: TRttiContext;
LType: TRttiType;
LTest: TTest;
begin
LTest := TTest.Create; //Here i´m using TTest.
//Could be in another part of the program
LContext := TRttiContext.Create;
for LType in LContext.GetTypes do
begin
if LType.IsInstance then
begin
WriteLn(LType.Name);
end;
end;
end;
At that point, if you use LContext.FindType ('TTest'), there will not be a nil return, because the compiler kept reference to the class. This explains the behavior you were having in your tests.

Resources