Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Interception of calls to published property set methods

34 views
Skip to first unread message

Pete Major

unread,
Dec 13, 2001, 9:12:12 AM12/13/01
to

Hi all.

I've been working on an object persistence framework for a little while now.
I'd like to add a property to the base class called 'Changed' that will be
set to true when any of the published property values change. This will
allow the framework to save only changed objects rather than every
instantiated object in the problem domain.

But for various reasons I don't want to write "set" methods for every
published property of every class setting this value to true. What I'd like
to do is intercept every call to read/write published properties and set the
'changed' field in the intercept.

So as a first step I've tried to override the SetProc pointer in the
TPropInfo record of a property with code like this:

// get the property info record
PropInfo := GetPropInfo( TestClass.ClassInfo, 'SomeProperty' );
// set the memory location access to read / write to avoid AV
VirtualProtect( @PropInfo^.SetProc, Sizeof( pointer ),
PAGE_EXECUTE_WRITECOPY, @oldProtect );
// change the method address to the new intercept method
PropInfo^.SetProc := MethodAddress(TestClass.InterceptMethod);
// reset the memory access to the original state
VirtualProtect( @PropInfo^.SetProc, Sizeof( pointer ), oldProtect,
@oldProtect );

Although this code works fine and subsequent tests with GetPropInfo show
that the SetProc value has been changed, the code continues to execute the
original SetProc... darn!

Can anyone provide any clues why changing the SetProc of a class's property
at run-time doesn't seem to change class behavior?

Also, has anyone else every tried to intercept the call to a SetProc method?

Thanks for any help,
Pete

-------------------------------------------------------
Peter Major
London, UK
pete...@NOPESPAMhotmail.com
-------------------------------------------------------

Gert Kello

unread,
Dec 13, 2001, 11:19:00 AM12/13/01
to
> I've been working on an object persistence framework for a little while now.
> I'd like to add a property to the base class called 'Changed' that will be
> set to true when any of the published property values change. This will
> allow the framework to save only changed objects rather than every
> instantiated object in the problem domain.
>
> But for various reasons I don't want to write "set" methods for every
> published property of every class setting this value to true. What I'd like
> to do is intercept every call to read/write published properties and set the
> 'changed' field in the intercept.

I believe that the only way to set the 'Changed' property is writing set
methods for every property You want to be guarded. In my knowledge there
is no way to install some kind of "monitor", and there will be no way
unless Borland seriously redesigns the concept of properties.

One possible solution to Your problem would be to store all published
properties of the object, and compare the stored values with current
values before the object is stored...

Gert

Pete Major

unread,
Dec 13, 2001, 11:48:22 AM12/13/01
to
Thanks for your reply.

I'm not so hot on the idea of objects storing original values for a few
reasons including:
memory requirements
room for developer error (that's what the framework is trying to elimate)
need to convert entire existing problem domain

Another possible option may be to use a checksum, like CRC32. Generate a
stream from the persistent properties and then generate a checksum value on
that stream. You know an object has changed if a new checksum doesn't match
the old checksum... the question is: what are the odds that a CRC32
computation will be the same for different streams.

Has anyone ever used checksums and / or CRC32 for this purpose? Is it a good
idea or is there something I'm missing?

Thanks again for any help,
Pete

-------------------------------------------------------
Peter Major
London, UK
pete...@NOPESPAMhotmail.com
-------------------------------------------------------

"Gert Kello" <ge...@gaiasoft.ee> wrote in message
news:3C18D4F4...@gaiasoft.ee...

Eric Hill

unread,
Dec 13, 2001, 12:20:20 PM12/13/01
to
If you're persisting objects to streams (files, database blobs, etc), you
can CRC a stream quite easily.

However I found it much easier to make your object ID a record like so...

type
TObjectID = record
Identity: Int64;
Stamp: TDateTime;
end;

When you go to save, compare the time Stamp of the old record with the new
one. If they're different, someone else has changed your object and you
need to ask the user what to do or handle the situation accordingly. If
they're the same, write freely.

If you want to copy one object to another, the ObjectID is copyable since
it's a record. Time stamp included.

Eric

"Pete Major" <pete...@NOSPAMhotmail.com> wrote in message
news:3c18db91$1_2@dnews...

Gert Kello

unread,
Dec 14, 2001, 1:57:09 AM12/14/01
to
> Another possible option may be to use a checksum, like CRC32. Generate a
> stream from the persistent properties and then generate a checksum value on
> that stream. You know an object has changed if a new checksum doesn't match
> the old checksum... the question is: what are the odds that a CRC32
> computation will be the same for different streams.

I do not know... Bu I think that this might happen. The hash algorithms
(like SHA1, MD5) are better from that aspect, but they are slower too.

Gert

Franz-Leo Chomse

unread,
Dec 14, 2001, 5:53:12 AM12/14/01
to
On Fri, 14 Dec 2001 08:57:09 +0200, Gert Kello <ge...@gaiasoft.ee>
wrote:

>> Another possible option may be to use a checksum, like CRC32. Generate a
>> stream from the persistent properties and then generate a checksum value on
>> that stream. You know an object has changed if a new checksum doesn't match
>> the old checksum... the question is: what are the odds that a CRC32
>> computation will be the same for different streams.

It will happen. CRCs are designed to avoid false negatives, i.e. if
the CRC is different the data is different. But the opposite equal CRC
but different data is possible.

Regards from Germany

Franz-Leo

Pete Major

unread,
Dec 14, 2001, 11:48:00 AM12/14/01
to
I also thought the same regarding the odds of identical CRC32 values on
different data, but upon further investigation I came across a site the
explained the the odds of an identical CRC32 checksum on different data was
approx. 1 in 4 billion.

I have no idea how that was calculated or if it is true, but if it were to
be true, I think those are odds I could live with... Does anyone have enough
mathmatical knowledge to confirm this one way or another? Below is the link
to the page where I obtained that stat and the code to preform CRC32 on a
buffer:
http://www.efg2.com/Lab/Mathematics/CRC.htm

Cheers,
Pete

-------------------------------------------------------
Peter Major
London, UK
pete...@NOPESPAMhotmail.com
-------------------------------------------------------

"Franz-Leo Chomse" <franz-le...@samac.de> wrote in message
news:ndmj1uki4d3jfvr9c...@4ax.com...

Radek Jedrasiak

unread,
Dec 14, 2001, 12:20:33 PM12/14/01
to
One more Idea:

what about Variants ? I mean can you use them as published properties ?
If so, wouldn't it be possible to construct a set of custom variants (delphi 6)
signaling changes in some way ?

Just an idea.

Cheerio
Radek


Pete Major

unread,
Dec 17, 2001, 4:49:33 AM12/17/01
to

Hi there.

Unfortunately I'm not using Delphi 6. What are custom variants? Are they new
in Delphi 6?

Pete

--

-------------------------------------------------------
Peter Major
London, UK

pe...@petermajor.co.uk
-------------------------------------------------------

"Radek Jedrasiak" <NNNOOOe...@student.tuwien.ac.atSSPPAAMM> wrote in
message news:3c1a34e2_1@dnews...

Franz-Leo Chomse

unread,
Dec 17, 2001, 5:40:09 AM12/17/01
to
On Mon, 17 Dec 2001 09:49:33 -0000, "Pete Major"
<pete...@NOSPAMhotmail.com> wrote:

>
>Hi there.
>
>Unfortunately I'm not using Delphi 6. What are custom variants? Are they new
>in Delphi 6?

Yes. And they have done more evil than good - starting with an time
penalty raise for using variants of more than 200% and having a lot
of additional problems.

Regards from Germany

Franz-Leo

Dennis Landi

unread,
Dec 17, 2001, 6:37:26 AM12/17/01
to
Hm. Interesting problem. I am just now implementing an OO-to-DB design
myself. I can see why it would be of obvious benefit to avoid unneccesary
writes to persistent storage.

Below, is an approach using the ObjectBinaryToText functionality to get a
snapshot of the component image on load. You can compare that image with
the image of the component on save... You can take it one step further and
do a checksum of the string image representation and use that as your point
of comparison if your were really worried about memory... And given an
InstanceID for your component you could actually just save this info to disk
if your were REALLY worried about memory but I wouldn't be... And then
there is alway string compression... etc. etc.

Try this (warning: this is written cold here, not tested in IDE):
...
tMyAncestorComponent = Class(tComponent)
private
fInitialImage : string;
protected
procedure SetInitialImage;
function GetInitialImage;
function GetComponentChanged:boolean
procedure _Load;
procedure _Save;
public
property InitialImage : string read fIntitialImage write
SetInitialImage;
property ComponentChanged : boolean read GetComponentChanged;
...

{from Delphi Help (slightly modified):}
function ComponentToString: string;
var
BinStream:TMemoryStream;
StrStream: TStringStream;
s: string;
begin
BinStream := TMemoryStream.Create;
try
StrStream := TStringStream.Create(s);
try
BinStream.WriteComponent(self);
BinStream.Seek(0, soFromBeginning);
ObjectBinaryToText(BinStream, StrStream);
StrStream.Seek(0, soFromBeginning);
Result:= StrStream.DataString;
finally
StrStream.Free;
end;
finally
BinStream.Free
end;
end;

Procedure MyAncestorComponent.SetInitialImage;
begin
fInitialImage := ComponentToString;
end;

Function MyAncestorComponent.GetComponentChanged:boolean;
begin
if ComponentToString = InitialImage then
result := false
else
result := true;
end;

Procedure MyAncestorComponent.procedure _Load;
begin
{Load your properties from storage}
...
SetInitialImage;
end;

Procedure MyAncestorComponent.procedure _Save;
begin
if ComponentChanged then
begin
{Actually save component to persistent storage}
...
SetInitialImage;
end;
end;

Pete Major

unread,
Dec 17, 2001, 9:02:56 AM12/17/01
to

Hi Dennis.

This is very similar to what I've implemented. The major difference was that
I didn't save the entire image, just the CRC32 value. I wrote a routine to
move all published property values to a stream and then compute the CRC on
the binary stream and then finally releasing the stream.

function TPDObject.ComputeCRC32: Cardinal;
var
Stream: TMemoryStream;
begin
result := NO_CRC;

// PersistInfo class uses RTTI to obtain property names, values for
persistence framework
Stream := TPersistInfo.CreateStreamFromObject( Self );
try
if Stream.Size > 0 then
begin
Stream.Position := 0;
result := CalcCRC32( Stream.Memory, Stream.Size );
end;
finally
FreeAndNil( Stream );
end;
end;

function TPDObject.GetModified: boolean;
var
NewCRC32: Cardinal;
begin
result := IsNewObject;
if not result then
begin
NewCRC32 := ComputeCRC32;
result := not( NewCRC32 = FCRC32 );
end;
end;

This works well, but I get conflicting opinions about how "unique" the CRC32
value is...

Regards,
Pete

-------------------------------------------------------
Peter Major
London, UK

pe...@petermajor.co.uk
-------------------------------------------------------

"Dennis Landi" <den...@dennislandi.com> wrote in message
news:3c1dd8aa_2@dnews...

Marc Rohloff

unread,
Dec 17, 2001, 11:08:42 AM12/17/01
to
I've been thinking about the idea of tracking changes to properties
for a while.

I hadn't thought of using variants. I had thought of using classes as
follows:

type
TProperty = class
private
fChanged:boolean;
protected
procedure MarkAsChanged;
public
property IsChanged:boolean read fChanged;
end;

TFloat = class(TProperty)
private
fValue:double;
protected
procedure SetValue(v:double);
public
property value:double read fValue write setvalue;
end;

TBObject = class(TProperty)
private
fValue:TObject
protected
procedure SetValue(v:TObject);
public
property value:TObject read fValue write setvalue;
end;

procedure TFloat.SetValue(v:double);
begin
if (v <> fValue) then
begin
MarkAsChanged;
fValue := value;
end;
end;

Obviously you would need to define these for all Delphi's types
(integer, float, boolean, string and TObjectList)

Then in your BO:

type
TEmployee = class(TBusObj)
private
fSalary:TFloat;
fManager:TBObject;
public
property salary:TFloat read fSalary;
property manager:TBObject read fManager;
end;

Notice that you can't set the salary property only get it.

So:
Employee1.Salary.value := Employee1.salary.value + 100;
Which unfortunately get's a bit long winded.
Especially if you want to navigate the object hierqarchy:
To get the manager's manager:
Mgr 2 := ((Employee1.Manager.value) as TEmployee).Manager.Value as
TEmployee;

So my thought was to change the type defenition:

type
TEmployee = class(TBusObj)
private
fSalary:TFloat;
fManager:TBObject;
public
property salary:double
read GetSalary {result := fSalary.value}
write SetSalary; {fSalary.value := v}
property manager:TEmployee
read GetManager {result := fManager.value as TEmployee}
write GetManager; {fManager.value := v}
end;

This would simplify the use of the object, at the cost of slightly
more implementation code. But at least the code to keep track of
changes would be more encapsulated.

Unfortunately you could no longer use RTTI. I had thought of the
business object owning it's 'properties'
ie:
TProperty = class
constructor Create(owner:TBusObj); {would add itself to a list in
the BO}
end;

TBusObj = class
property PropertyCount:integer;
property Properties[index:integer]:TProperty;
end;

Which the framework could use to persist the object.

This is still an idea I am thinking about. It is not as neat as I
would like but is the most useful I have thought of to date.

The only thing which I would still like to do is to somehow
encapsulate special values into the objects.

Marc

Dennis Landi

unread,
Dec 17, 2001, 12:00:30 PM12/17/01
to
Yeah, you might look into compressing the saved image, since component
load/save is not all that frequent comparatively speaking...


"Pete Major" <pete...@NOSPAMhotmail.com> wrote in message

news:3c1dfacb$1_2@dnews...

Max Hadley

unread,
Dec 18, 2001, 4:20:47 AM12/18/01
to
A CRC32 algorithm is designed to generate a 32-bit integer based on the byte
sequence to be 'signed'. The algorithms are ususally designed to ensure that
small changes (especially things like swapping byte order, extra or missing
bytes) almost always generate different CRC values. A good algorithm should
ensure that for 'sufficiently long' byte sequences the CRC value is
uniformly distributed in [-2^31 .. 2^31 - 1], hence for such algorithms the
odds of getting 2 byte sequences with the same CRC should approach 1 in
2^32. The CCITT-32 algorithm is a good bet.

Message hashing algorithms like MD5 or SHA usually generate longer,
typically 128-bit, signatures, so the purely random chance of a collision is
really titchy. They are also designed to use a secret number (e.g. a key) so
that it is very hard to change a message in such a way that the signature
value is the same.

I think a timestamp is what you want!

Max Hadley

"Pete Major" <pete...@NOSPAMhotmail.com> wrote in message

news:3c1a2cfb_2@dnews...

0 new messages