Манипуляции с методами классов

Источник: CPPBUILDER
Vyacheslav Yermolaev

В качестве отрицательных сторон библиотеки VCL часто называется ее дельфийское происхождение, что приводит к тому, что по некоторым моментам поведение объектов VCL-классов не соответствует стандарту С++. К таким моментам в частности можно отнести своеобразный порядок вызова конструкторов базовых "дельфийных" классов, поведение виртуальной функции при вызове ее в теле конструктора, ограничения, накладываемые при использовании множественного наследования (до появления С++Builder 6 разговор велся не просто об ограничении, а о недопустимости применения множественного наследования для VCL-классов).

Кроме того, в компилятор, для поддержки VCL-библиотеки, были добавлены расширения, что то же не приветствуется сторонниками чистоты C++. К одним из этих относится введение ключевого слова __closure - указателя на метод класса. В отличие от предусмотренного стандартом С++ указателя на метод класса, __closure кроме самого адреса метод, хранит еще и адрес экземпляра класса и физически представляет собой структуру, состоящую из двух указателей: на экземпляр класса и на метод класса. Таким образом , __closure практически является указателем не просто метод класса, а на метод объекта (экземпляра) класса.

Не стоит думать, что применения указателя на метод объекта узко ограничено лишь областью VCL классов. Подобные указатели, хотя и не часто, встречаются в программисткой практике и оказываются довольно полезными. Имеются реализации таких указателей с использованием стандартного С++. [Александреску А. Современное проектирование на С++, М.Издательский дом «Вильямс», 2002.] В С++Builder программист получает эти возможности даром, в качестве своеобразной компенсации за «моральный ущерб» в результате потери совместимости со стандартом.

Для иллюстрации этих возможностей создадим простой проект, состоящий из одной формы. На форму положим пять кнопок и отредактируем им Caption в соответствии с задачами , которые попытаемся продемонстрировать.

Вот программа-минимум, которую мы должны выполнить:

  1. вызов обычной функции как метода в качестве обработчика события;
  2. вызов метода как обычной функции;
  3. вызов опубликованного (published) метода по его символьному имени;
  4. получение имени опубликованного метода.

В соответствии с этими задачами наша форма примет следующий вид:


Рисунок 1. Внешний вид формы проекта.

При нажатии самой верхней кнопки будет вызвать метод Button1Click обычным стандартным способом, при нажатии остальных четырех кнопок будут выполняться действия, соответствующие поставленным задачам.

Прежде всего, определим метод и функцию, с которыми будем экспериментировать.

Для определения метода просто зададим обработчик OnClick для верхней кнопки Button1 и в сгенерированном шаблоне наберем код тела метода:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
   ShowMessage(AnsiString("Метод:")+
    this->Name + "->" + ((TComponent*)Sender)->Name);
}   
//---------------------------------------------------------------------------

Данный метод при вызове просто будет выдавать сообщение , что метод класса и сообщать имя кнопки, которая была нажата в момент вызова.

Теперь определим обычную функцию GlobalClick:

//---------------------------------------------------------------------------
void __fastcall GlobalClick(void* This, TObject *Sender)
{
    ShowMessage(AnsiString("Функция:")+
    ((TComponent*)This)->Name + "->" +
    ((TComponent*)Sender)->Name);
}   
//---------------------------------------------------------------------------

Функция будет выдавать признак вызова функции и аналогично методу сообщать имя кнопки, которая была нажата в момент вызова..

Отметим следующую особенность: у функции в отличие от метода добавился еще один параметр типа void*. Через этот параметр будет передаваться указатель на объект класса. В нашем случае, через него будет передаваться указатель на Form1.

Теперь попытаемся организовать вызов обычной функции в качестве обработчика события OnClick кнопки Button2.

Замечание. События (events) реализуются в С++Builder посредством указателей на метод объекта (__closure), то есть события являются, по сути, указателями на метод объекта

Эту операцию выполним в теле конструктора формы:

//---------------------------------------------------------------------------
_fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
   TMethod Method;
   Method.Data = this;
   Method.Code= GlobalClick;
   Button2->OnClick = *(TNotifyEvent*)&Method;
} 
//---------------------------------------------------------------------------

При выполнении этой операции задействована структура TMethod. Структура довольна проста и содержит всего лишь два поля Data и Code, которые имеют тип void*

struct TMethod
{
  void *Code;
  void *Data;
};

и по размеру соответствует указателю на метод объекта, состоящего , как уже упоминалось выше, также из двух указателей . Следовательно, посредством этой структуры с помощью преобразования можно инициализировать указатель на метод нужным значением. Для этого предварительно в поле Data заносится адрес объекта (в данном случае это форма), в поле Code - адрес функции. После присвоения содержимого Method событию OnClick данное событие будет связано с функцией, и при нажатии на кнопку Button2 в процессе работы программы механизм выполнения событий вызовет функцию GlobalClick и передаст ей в качестве параметров адрес на Form1 и адрес на объект, запустивший событие. В нашем случае этим объектом будет кнопка Button2. Результат работы программы показан на рисунке 2:


Рисунок 2. Результат работы программы.

Вызов метода как обычной функции выполним в теле обработчика кнопки Button3:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
    //вызвать метод как обычную функцию
    TNotifyEvent Click = &Button1Click;
    TMethod Method = *(TMethod*)&Click;
    //через первый скрытый параметр передаем this
    typedef void (__fastcall *Func)(void*,TObject *);
    Func func;
    func = (Func)Method.Code;
    func(this, Sender);
} 
//---------------------------------------------------------------------------

Данная операция выполняется в обратном порядке. Переменной Method присваивается содержимое указатель на метод. Затем поле Code приводим к типу: указатель на функцию параметрами типа void*, TObject* и выполняем вызов функции, передав ей в качестве параметров this и Sender. Результат работы программы можно увидеть на рисунке 3.


Рисунок 3. Результат работы программы.

Для выполнения двух остальных задач потребуется задействовать дополнительные возможности расширенного RTTI, которые достались по наследству от Delphi и реализованы в виде методов класс TObject. Естественно, эта дополнительная информация доступна только для VCL-классов, то есть только тех классов, которые являются производными от TObject. Нам потребуются пока только два метода. Это

void * __fastcall MethodAddress(const ShortString &Name);

и

ShortString __fastcall MethodName(void *Address);

С помощью первого из них можно получить адрес метода по его символьному имени, с помощью второго - получить имя, зная адрес. При этом надо учитывать, что использовать эти функции можно только для опубликованных методов, то есть методов, которые размещены в секции __published.

Вызов метода по имени выполним в теле обработчика кнопки Button4, а получение имени метода в теле обработчика Button5:

//---------------------------------------------------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
    ShortString ProcName = "Button1Click";
    TMethod Method = { MethodAddress(ProcName), this } ;
    if (Method.Code)
    {
        TNotifyEvent Click = *(TNotifyEvent*)&Method;
        Click(Sender);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
    TMethod Method = *(TMethod*)&(Button1->OnClick);
    ShowMessage( MethodName(Method.Code));
}
//---------------------------------------------------------------------------

Как видно, код достаточно прост и учетом сделанных выше разъяснений не представляет трудностей для понимания.


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=8221