скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Как вызвать private метод класса, находящегося в другом модуле



Автор: Андрей Руфин

Иногда возникает необходимость вызвать 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.

Download Размер Описание
CallPrivate.zip (не рабочая ссылка) 4.5kb Демонстрационный проект. Delphi3/4/5 CBuilder 3/4/5 версия.
Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.






Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook