Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Как вызвать 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 версия.
Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay