Как вызвать private метод класса, находящегося в другом модуле
Оформил: DeeCo
Автор: Андрей Руфин
Иногда возникает необходимость вызвать 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
версия. |
Внимание! Запрещается перепечатка
данной статьи или ее части без согласования с автором. Если вы
хотите разместить эту статью на своем сайте или издать в печатном
виде, свяжитесь с автором.
|