Title: How to call a private method of other class
Question: Sometimes it is necessary to call a private method of a class saved in another module. It contradicts the OOP principles used in Delphi, but one can try to execute such an operation.
Answer:
Sometimes it is necessary to call a private method of a class saved in another module. It contradicts the OOP principles used in Delphi, but one can try to execute such an operation. Let us consider, as an example, a case when it is required to load/save all properties of TPersistent descendants, e.g. of an object of TFont class.
There are standard Delphi classes TReader, TWriter designed to load/save properties of an object. The methods TWriter.WriteProperties(Instance: TPersistent) and TReader.ReadProperty(AInstance: TPersistent) are the most interesting for our purposes. The WriteProperties method allows one to write into the stream all properties of the TPersistent descendants. A call of the ReadProperty method within a loop allows the user to read from the stream all already saved properties.
Let us consider a procedure of the properties saving.
It is evident in case of Delphi5: the declaration of the WriteProperties method is located in the protected section of the TWriter class, and it is easy to call it:
type
THackWriter = class(TWriter);
....
THackWriter(Writer).WriteProperties(Instance); //call the WriteProperties
....
However, the situation is more complicated for Delphi4: The WriteProperties method is in the private section of the TWriter class, and a standard use of Delphi allows the user to call this method only from the module with the TWriter class, i.e. from classes.pas module. This problem is seemed to be insoluble because it is impossible to add a user code to classes.pas module as well as it is impossible to call the WriteProperties method from another module: But I'll show that this problem has a solution.
Note that
WriteProperties is a static method, i.e. its address is set while the program compilation.
WriteProperties is called within the public method TWriter.WriteCollection.
To call WriteProperties, one has to know its address. Let us try to obtain it using the public method WriteCollection. One needs to develop a simplest project with a call of the WriteCollection method. This project must have a break point at the call of WriteCollection. Let us run this project: it will go up to the break point. Then open CPU window and enter the WriteCollection method by pressing the F7 key (Trace Info). Now we have reached the most interesting point of our technique: one needs to find the WriteProperties call within the WriteCollection method and to calculate the offset (in bytes) of the "call TWriter.WriteProperties" command with respect to the start point of the WriteCollection method. In the given case this offset is equal to $36 + 1 bytes. Thus, the code designed to determine the WriteProperties method address is the following:
var
p: pointer;
....
p := @TWriter.WriteCollection;
if PByte(p)^=$FF then begin //skip jmp table (for package)
Inc(PByte(p), $2);
p := Pointer(PInteger(p)^);
p := Pointer(PInteger(p)^);
end;
Inc(PByte(p), $37);
Inc(PByte(p), PInteger(p)^+4);
let us add several commands to increase the robust of this code:
var
p: pointer;
....
p := @TWriter.WriteCollection;
if PByte(p)^=$FF then begin //skip jmp table (for package)
Inc(PByte(p), $2);
p := Pointer(PInteger(p)^);
p := Pointer(PInteger(p)^);
end;
Inc(PByte(p), $37);
if PByte(PChar(p)-1)^$E8 then begin exit; end;
Inc(PByte(p), PInteger(p)^+4);
if PByte(p)^$55 then begin exit; end;
Now we have already the WriteProperties address and it is sufficient to call it:
asm
push eax
push edx
mov eax, Writer
mov edx, Instance
call p
pop edx
pop eax
end;
Similary, one can determine the TReader.ReadProperty address.
As for Delphi3 and CBuilder3,4, it is necessary to execute all the above mentioned operations once more.
It results in a creation of code which can be used to save all properties of the TPersistent descendants.
Where it can be used?
In particular, one can save TEdit.Font or TForm.Icon or TImage.Picture.
Advances of this technique:
We have developed a universal technique allowing the user to load/save all properties of any TPersistent descendants. This technique has been implemented as a small code which can call private methods of another class.
Imperfections of this technique:
"Bad style" of programming which contradicts to the OOP principles. In fact, our code is implicitly depended on the classes.pas module, and any changes in this module or in objects TWriter, TReader, or in methods TWriter.WriteCollection, TReader.ReadCollection can result in run-time errors in our code (i.e. the compiler does not find these errors). But do you change often the classes.pas module? I think that it is a rather rare operation.
One can download the demonstration project and get the source of the above mentioned code. You can use this code into own applications, but I declines any responsibility for possible consequences of its use. On the other hand, I showed that the use of "non-standard" programming technique allowed the user to reach interesting results: in particular it is possible to call a private method of another class declared in another program module.
The above described problems appeared while the development of the shareware project Storage library. Such a technique is used there to load/save all the properties of the TPersistent object descendants, e.g. TEdit.Font, TForm.Icon or TImage.Picture. More information on Storage library project can be found on www server of DeepSoftware company at http://dsr.tomsk.ru/rsllib/index.html