If you are looking for more hacks, you should be subscribing to Roy Nelson's blog. Roy is a great asset of technical info and hacks - in this case he was helping out another great spelunker and Delphi expert, Brian Long.
Monday, June 21, 2004
Wednesday, June 16, 2004
Hack #6:Checking for a valid object instance
I just answered a question in a newsgroup, by simply copying-and-pasting an old post of mine. Reuse is nice, so I decided to put it in my blog as well. Here it is: "Just for the fun of it, I've made some routines to check if a pointer points to a valid object instance. This relies on some types and routines from Ray Lischner's "Secrets of Delphi 2" (the S_VMT unit). Note that this code is D3 specific. Small changes are needed for D1/D2. [Right :)]
uses
S_VMT;
function ValidPtr(P: pointer; Size: Cardinal): boolean;
begin
Result := not IsBadReadPtr(P, Size);
end;
function ValidObjType(Obj: TObject; ClassType: TClass): boolean;
begin
Result := Assigned(Obj) and
ValidPtr(Pointer(Obj), SizeOf(TObject)) and
ValidPtr(Pointer(Obj), ClassType.InstanceSize);
end;
type
PClass = ^TClass;
function ValidPShortString(S: PShortString): boolean;
begin
Result := ValidPtr(S, SizeOf(Byte)) and
ValidPtr(S, Ord(S^[0])) ;
end;
function ValidClassParent(ClassParent: PClass): boolean;
begin
if ClassParent = nil then
Result := true
else
if ValidPtr(ClassParent, SizeOf(ClassParent^)) then
Result := (ClassParent^ = nil) or ValidClassType(ClassParent^)
else
Result := false;
end;
function ValidClassType(ClassType: TClass): boolean;
var
Vmt: PVmt;
begin
Vmt := GetVmt(ClassType);
Result := ValidPtr(Vmt, SizeOf(Vmt^)) and
(Vmt^.SelfPtr = ClassType) and
ValidPShortString(Vmt^.ClassName) and
ValidClassParent(PClass(Vmt^.ClassParent)) ;
end;
function ValidObj(Obj: TObject): boolean;
begin
Result := Assigned(Obj) and
ValidPtr(PClass(Obj), SizeOf(TClass)) and
ValidClassType(Obj.ClassType) and
ValidPtr(Pointer(Obj), Obj.InstanceSize);
end;
This is probably not foolproof, but it should work in most instances. It works by checking for valid pointers using the Win32 API IsBadReadPtr, checking that the VMT-pointer for the given object is valid. Using this code is not recommended as an alternative to setting instance pointers to nil after freeing them."
Update; I found another old post with a simpler (and probably safer) way of doing it:
function ValidateObj(Obj: TObject): Pointer;
type
PPVmt = ^PVmt;
PVmt = ^TVmt;
TVmt = record
SelfPtr : TClass;
Other : array[0..17] of pointer;
end;
var
Vmt: PVmt;
begin
Result := Obj;
if Assigned(Result) then
try
Vmt := PVmt(Obj.ClassType);
Dec(Vmt);
if Obj.ClassType <> Vmt.SelfPtr then
Result := nil;
except
Result := nil;
end;
end;
Notice that this version is specific to D6 and D7 (IIRC). Other versions might need to update the hardcoded magic number (17).
Update II [June 22, 2007]:
Note that Pierre's "new" FastMM (and thus the D2006/2007 MM) reuses memory blocks more aggressively than the old MM, so you may be false positives from my function if the instance pointer has been freed and just happens to be reallocated by a different object instance (of potentially a different object type).
As always, use this hack with care and a grain of salt or two.
Tuesday, June 15, 2004
Shameless plug: Buy book, get my autograph!
As part of the renumeration for tech editing Xavier Pacheco's excellent book, Delphi for .NET Developer's Guide, I got three copies of the printed version. I don't really need the extra two, so now they are for up for grabs at eBay: Book 1 Book 2 And, yes, I'll sign the books (if the buyer is interested in that). :-)
Wednesday, June 02, 2004
Hack #5: Access to private fields
As your mama taught you, you should protect your private parts, and leave other peoples private parts alone. The same goes for programming, of course. Encapsulation and information hiding is two of the main pillars of object oriented programming, so we better not be messing with those, right? Right. But once in a blue moon, we just have the urge to behave badly and reach into the private fields of another class, defined in another unit.
When possible, the previously described hack of accessing a private field through a published property's RTTI is preferred to the technique presented in this article. However, sometimes that hack is not very practical or even possible. Using RTTI is relatively slow (although the RTTI access to retrieve field offsets could be cached) and the private field you need access to may not be exposed as a property. In addition, if you need access to all or most of a class's internal private fields, the solution described here may be preferable.
As mentioned earlier, Delphi's concept of private (in contrast with D8/D2006-2007's strict private) still allows other code in the same unit full access, while excluding access to external units. So all classes and routines in the same unit are intimate friends of each other.
Have you noticed how promiscuous some of the VCL classes are with each other? For instance, the abstract base class of the component streaming system, TFiler, defines a number of private fields. The actual worker classes TReader and TWriter (both descending from TFiler) are both defined in the same unit, and blatantly access the private fields defined in TFiler (for instance the all-important FStream field).
If you should wish to write your own application-level streaming system (with the ability to stream and auto-create non-TComponent classes) that still participates in the TPersistent DefineProperties system, you would have to extend TReader and TWriter. It looks like these classes have been designed to be extended, considering the virtual protected methods they introduce. Your own TMyReader and TMyWriter classes would not have access to the TFiler private fields, nor to the private fields defined in TReader and TWriter. This could(*) severely limit what your descendant classes could do.
Ok, enough talk already - on to the meat. To get access to the private fields of TFiler, declare a shadow class that exactly matches the private declaration from Classes.pas:
type
TFilerHack = class(TObject)
private
FStream: TStream;
// other private fields here
end;
Given a TFiler instance, you can now simply cast to TFilerHack to get access to the fields:
procedure Foo(Filer: TFiler);
var
Stream: TStream;
begin
Stream := TFilerHack(Filer).FStream;
end;
Notice that I still use the private visibility directive for the field - this still gives access to all code in the same unit. Normally, this kind of cracker classes should be declared in the implementation section of the unit. This hack is of course extremely version sensitive to changes in the cracked class. Compared to other solutions of using magic field offsets it has the benefit of having the compiler computing the offset for you and from being protected against changes in the base class (TObject in this case). Still, if you ever use this hack, make sure you {$IFDEF} or {$IF} protect the code against changes in compiler and RTL version (or 3rd party component set version).
(*) A few years ago (using D5), I actually implemented an application-level streaming system by writing my own TReader and TWriter classes and using hacks similar to the ones described in this article. Since then TFiler, TReader and TWriter have been improved with regards to extensibility - providing protected read-only properties to get to some of the private fields, for instance (kudos to Borland CodeGear on that one). It may well be that a functional application-level streaming system could be built without using this hack in D7, but I wouldn't count on it. I still use TFiler as an illustrative example.