Иногда возникает необходимость вызвать private метод другого класса, расположенного в другом модуле. Это противоречит принципам ООП, заложенным в Delphi, но все-таки попробуем это сделать. Для примера рассмотрим случай, когда требуется сохранить/прочитать все свойства обьекта наследника TPersistent, например обьекта класса TFont.
В Delphi есть стандартные классы TReader,TWriter разработанные для сохранения/чтения свойств обьекта. В этих классах нам интересны методы TWriter.WriteProperties(Instance: TPersistent) и TReader.ReadProperty(AInstance: TPersistent). МетодWriteProperties позволяет сохранить в поток все свойства обьекта наследника TPersistent. Вызов в цикле метода ReadPropertyпозволяет прочитать из потока все сохраненные ранее свойства.
Рассмотрим сохранение свойств.
В Delphi5 все просто. Обьявление метода WriteProperties находится в Protected секции класса TWriter. Вызвать его особых проблем не составит:
type
THackWriter = class(TWriter);
....
THackWriter(Writer).WriteProperties(Instance); //вызов метода
....
В Delphi4 все несколько сложнее. Метод WriteProperties находится в private секции класса TWriter. Стандартно вызвать этот метод можно только в рамках модуля где находится класс TWriter т.е. модуля classes.pas. Все, скажете вы, ситуация безнадежна, ведь добавить свой код в стандартный модуль classes.pas нельзя, вызвать метод WriteProperties из другого модуля тоже нельзя. Но я хочу показать, что выход из этой ситуации есть. Для начала заметим что:
- WriteProperties статический метод. Тоесть адрес метода определяется на этапе компиляции проекта.
- метод WriteProperties вызывается в public методе TWriter.WriteCollection.
Для вызова метода WriteProperties нам необходимо узнать его адрес. Попробуем его узнать через public метод WriteCollection. Сделаем простенький проект, в котором будет вызов метода WriteCollection. Поставим точку останова на вызов методаWriteCollection. Запустим проект и дойдем до точки останова. Откроем CPU window и войдем в метод WriteCollection, нажимая F7 (Trace Info). А теперь самое интересное: в методе WriteCollection найдем вызов метода WriteProperties и вычислим смещение (в байтах) команды call TWriter.WriteProperties относительно начала метода WriteCollection. В нашем случае оно равно $36+1 байт. И так, код для определения адреса метода WriteProperties будет выглядеть так:
var
p: pointer;
....
p := @TWriter.WriteCollection;
Inc(PByte(p), $37);
Inc(PByte(p), PInteger(p)^+4);
добавим несколько дополнительных проверок для повышения надежности этого кода
var
p: pointer;
....
p := @TWriter.WriteCollection;
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;
адрес метода WriteProperties у нас уже есть, осталось только его вызвать
asm
push eax
push edx
mov eax, Writer
mov edx, Instance
call p
pop edx
pop eax
end;
Аналогично можно вычислить адрес TReader.ReadProperty. Для Delphi3, CBuilder3,4 придется провести все вышеперечисленные операции еще раз.
В результате мы получили код, который можно использовать для сохранения в поток/чтения из потока всех свойства любого обьекта наследника TPersistent.
Где его можно использовать? Например, можно запомнить TEdit.Font или TForm.Icon или TImage.Picture.
В чем преимущества этого метода? Мы создали универсальные методы для сохранения/чтения всех свойств любого обьекта наследника TPersistent, получили небольшой по размеру код. И в конечном итоге научились вызывать private методы другого класса.
В чем его недостатки? "Плохой стиль" программирования, в обход принципов ООП. Теперь наш код неявным образом зависит от модуля classes.pas. Любое изменение в модуле classes.pas, в обьектах TWriter, TReader, в методах TWriter.WriteCollection, TReader.ReadCollection может превести к сбоям в работе разработанного нами кода. Причем мы не сможем увидеть это на этапе компиляции приложения, только в момент его работы. Но часто ли вы изменяли и перекомпилировали модуль classes.pas? мне кажется, что не очень.
Весь код, приведенный выше, вы можете получить, скачав демонстрационный проект. Вы можете использовать его на свой страх и риск в своих приложениях. Но основное что я хотел вам показать - используя нестандартные методы програмирования можно добиться интересных результатов, даже вызвать private метод класса находящегося в другом модуле.
Все описанное выше возникло при работе над моим shareware проектом - Storage library. Там этот прием используется для сохранения/чтения всех свойств обьектов наследников TPersistent например TEdit.Font или TForm.Icon или TImage.Picture. Вы можете подробнее ознакомиться со Storage library на www сервере компании DeepSoftware
Download | Размер | Описание |
CallPrivate.zip | 4.5kb | Демонстрационный проект. Delphi3/4/5 CBuilder 3/4/5 версия. |