В качестве отрицательных сторон библиотеки VCL часто называется ее дельфийское происхождение, что приводит к тому , что по некоторым моментам поведение объектов VCL-классов не соответствует стандарту С++. К таким моментам в частности можно отнести своеобразный порядок вызова конструкторов базовых « дельфийных» классов, поведение виртуальной функции при вызове ее в теле конструктора, ограничения, накладываемые при использовании множественного наследования ( до появления С++Builder 6 разговор велся не просто об ограничении, а о недопустимости применения множественного наследования для VCL-классов).
Кроме того, в компилятор, для поддержки VCL-библиотеки, были добавлены расширения, что то же не приветствуется сторонниками чистоты C++. К одним из этих относится введение ключевого слова __closure - указателя на метод класса. В отличие от предусмотренного стандартом С++ указателя на метод класса, __closure кроме самого адреса метод, хранит еще и адрес экземпляра класса и физически представляет собой структуру состоящую из двух указателей: на экземпляр класса и на метод класса. Таким образом , __closure практически является указателем не просто метод класса, а на метод объекта (экземпляра) класса.
Не стоит думать, что применения указателя на метод объекта узко ограничено лишь областью VCL классов. Подобные указатели , хотя и не часто, встречаются в программисткой практике и оказываются довольно полезны. Имеются реализации таких указателей с использованием стандартного С++.[Александреску А. Современное проектирование на С++, М.Издательский дом «Вильямс», 2002.] В С++Builder программист получает эти возможности даром, в качестве своеобразной компенсации за «моральный ущерб» в результате потери совместимости со стандартом.
Для иллюстрации этих возможностей создадим простой проект, состоящий из одной формы . На форму положим пять кнопок и отредактируем им Caption в соответствии с задачами , которые попытаемся продемонстрировать.
Вот программа-минимум, которую мы должны выполнить:
- вызов обычной функции как метода в качестве обработчика события;
- вызов метода как обычной функции;
- вызов опубликованного ( published) метода по его символьному имени;
- получение имени опубликованного метода.
В соответствии с этими задачами наша форма примет следующий вид:
Рис.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));
}
Как видно, код достаточно прост и учетом сделанных выше разъяснений не представляет трудностей для понимания.
Код к этой статье можно скачать отсюда