Исходники
Статьи
Языки программирования
.NET Delphi Visual C++ Borland C++ Builder C/С++ и C# Базы Данных MySQL MSSQL Oracle PostgreSQL Interbase VisualFoxPro Веб-Мастеру PHP HTML Perl Java JavaScript Протоколы AJAX Технология Ajax Освоение Ajax Сети Беспроводные сети Локальные сети Сети хранения данных TCP/IP xDSL ATM Операционные системы Windows Linux Wap Книги и учебники
Скрипты
Магазин программиста
|
Использование RTTI в приложениях на базе VCLВ статье рассматриваются механизм работы и использование механизма динамической идентификации типов (RTTI - runtime type identification) в приложениях, использующих объектную библиотеку ф. Borland VCL - Visual Component Library. Содержание:
Почему не стоит читать эту статью?RTTI используется ,как правило, во всех приложениях, созданных компилятором C++. Если компилятору указано не включать в объектный файл RTTI информацию, то не возможна будет динамическая идентификация типов и динамическое приведение типов. Обычно в состав RTTI входит следующая информация: имя типа (для идентификации), указатель на базовый тип (для приведения типов), указатель на конструктор копий. В RTTI поддерживающую VCL входит дополнительная информация, используемая IDE Builder C++, в первую очередь инспектором объектов. Однако структура и содержание этих дополнительных данных не документирована и вероятно может меняться в зависимости от версии Builder. Поэтому использование расширенной RTTI в приложениях возможно только при соблюдении ограничения: исходный код не может переноситься на другие версии Builder C++. Тем не менее, думаю приведенная информация будет интересна программистам в Builder C++, особенно при написании компонент. Все данные, приведенные в этой статье получены для Builder C++ 3.0. Что можно узнать из RTTI?Кроме имени типа, и указателя на базовый тип для классов со спецификатором DELPHICLASS в состав RTTI включается информация о всех свойствах, объявленных в секции __published. В том числе: имя свойства, его тип, указатели на методы установки, чтения и предикат хранения, а также значения индекса для методов установки/чтения, обслуживающих несколько свойств. А вот другие члены класса, объявленные в __published - переменные и методы в RTTI не попадают. Формат хранения информации о типе приведен в таблице №1.Таблица №1
Таблица №2
Для классовпотомков TObject получить указатель на RTTI информацию можно посредством метода ClassInfo(): void* rtti = p_class-> ClassInfo(); Указатель rtti будет указывать на область памяти, с данными с информацией о типе объекта p_class. Несколько слов о том, как самостоятельно исследовать информацию о типе. Для этого нужен какой-нибудь отладчик. В данном случае достаточно удобно пользоваться встроенным отладчиком (View|CPU). Получив значение указателя на RTTI класса, его содержимое просматривается в дампе памяти отладчика и имея описание класса (т.е. фактически зная информацию о его типе) можно делать логические заключения о структуре RTTI. Работа со свойствамиСвойства (property) введены в Builder C++ специально для поддержки классов, описанных на Object Pascal. О том, как описать свойство, установить или получить его значение, написано десятки книг (для начинающих я порекомендовал бы книгу К.Сурков, Д.Сурков, А.Вальвачев "Программирование в среде DELPHI 2.0" Минск 1997). Тем не менее остановимся на некоторых моментах:1. Доступ к значению свойства может быть напрямую или посредством функции: __property int ParamA = {read = pаram_a};//доступ к значению свойства напрямую __property int ParamB = {read = GetParamB};//доступ посредством функции метода GetParamB. В первом случае в RTTI будет приведено значение смещения переменной param_a в экземпляре класса, во втором - адрес функции, возвращающей значение свойства 2. Свойства бывают индексируемыми __property int ParamA[int index] = {read= GetParamA}; К индексированному свойству доступ возможен только через функцию, которая принимает параметр index. Самое интересное - из RTTI нельзя узнать, индексируется свойство или нет. 3. Если несколько свойств обслуживаются одним методом, в описании свойства указывается целый индекс, передаваемый в метод: __property int ParamA = {read = GetParamA,index = 0}; Значение этого индекса можно узнать из RTTI. 4. Для свойства можно указать, следует ли сохранять в файле формы его значение. Сохраняются значения свойств, отличные от значения по умолчанию, однако это можно запретить. //значение свойства не сохраняется __property int ParamA = {write = param_a, stored = false}; //значение свойства сохраняется __property int ParamB = {write = pram_b, stored = true}; //сохранением свойства управляет __property int ParamC = {write = pram_c, stored = StoredC); предикат StoredC. Значение параметра stored можно узнать из RTTI. 5.Свойство может иметь значение по умолчанию (см. п 4) __property int ParamA = {read = param_a, default = 100}; Отметим, что значение default не устанавливает начальное значение свойства - это работа конструктора. Значение параметра default можно посмотреть в RTTI. Кстати, хотя везде написано, что параметру default должно присваиваться константное выражение, на самом деле можно присвоить только целое константное выражение из-за того, что как мы увидим далее для значения default отводится только 4 байта, а вещественные константы могут занимать 4,8 и 10 байт. Получение информации о классе объектаКак уже говорилось выше, информация об классе находится по адресу, возвращаемому методом ClassInfo. По этому адресу находится следующая информация:
07 05 54 4D 65 6D 6F F0 3A 40 00 7C 3A 40 00 35 00 08 53 74 64 43 74 72 6C 73 2C 00 A8 62 41 00 48 00 00 FF 14 7D 41 00 01 00 00 00 00 00 00 80 00 00 00 00 0A 00 05 41 6C 69 67 6E Заголовок класса: 07 - идентификатор описания класса, имя класса, указатель на конструктор копий, указатель на базовый класс, количество опубликованных свойств, имя модуля, количество опубликованных в класск свойств. Перечисления свойств: Смещение переменной - прямой доступ для чтения, адрес метода установки значения, stored = true, index = -1 (нет индекса), default = 0, порядковый номер свойства, имя свойства (Align) Формат RTTI для опубликованных свойствИз выше приведенного дампа памяти можно уяснить себе, в каком формате храниться описание опубликованного свойства. Тем не менее приведем этот формат в явном виде:
Для того чтобы прочитать значение свойства следует выполнить следующие действия:
Установка значения свойства выполняется аналогично. Как видно из выше сказанного, установка и чтение свойства невозможны без знания типа свойства. Рассмотрим кратко возможные форматы описания типов: Формат целых типов:
Формат символьного типа
Формат перечислений
Примечание: Если в описании перечисления указаны значения для символьных констант, например: enum TSeason {winter = 100, spring = 200, summer = 300, autumn = 400}; то в RTTI такой тип трактуется как целый и вся символьная информация не включается в RTTI. Формат вещественного типа
К вещественным типам относятся также типы для представления времени TDateTime (он объявлен тождественным типу double). Формат классов Формат описания классов рассмотрен выше Формат обработчиков событий
Формат строковых типов
В Pascal для хранения строк существует специальный класс - String. В "чистом" С нет специальных типов для представления строковых данных, с ними работают как с массивами байтовых (или двухбайтовых) целых чисел. Для поддержки строк в формате String в Builder C++ объявлен специальный класс AnsiString. Примеры доступа к опубликованным свойствам используя RTTI.Хотел бы заметить, что в большинстве случаев, естественно, нет нужды организовывать доступ к свойством используя RTTI. Лишь в одном случае такой подход оправдан - когда на этапе компиляции программы неизвестен тип объектов, с которыми будет работать программа. Ситуация чем-то напоминающая использование OLE Автоматизации и диспетчерских интерфейсов. Кстати, использование упомянутой технологии в этом случае предпочтительно, но если объекты не предоставляют информацию о своем типе, и известно, что их код был скомпилирован в Builder C++ при использовании VCL, то работу с ними можно автоматизировать предлагаемым способом.Работу со свойствами рассмотри на примере доступа к свойству целого типа: //описание типа функции чтения свойства typedef int __fastcall(* FINTPROPREAD)(void*); Следует обратить внимание на две вещи: 1) соглашение о вызове функции должно совпадать с описанным в классе для доступа к значению функции; 2) поскольку метод- член класса неявно первым параметром получает указатель на экземпляр класса его вызвавший, следует описать этот параметр int ReadIntProperty(void* p_obj, void* p_read) { //указатель на объект char* p = (char*)p_obj; //адрес памяти, где указан способ чтения свойства (см выше) int* p4 = (int*)p_read; // выяснение, как осуществляется доступ к значению свойства - // напрямую или при помощи функции if (((int)p4 & 0xFF000000) == 0xFF000000) { //чтение напрямую //вычисление адреса памяти, где хранится значение свойства p += ((int)p4 & 0x00FFFFFF); p4 = (int*)p; return *p4; } else { //чтение через метод FINTPROPREAD reader = (FINTPROPREAD)p4; return reader(p_obj); } } Установка значения свойства производится аналогично: typedef void __fastcall(* FINTPROPSET)(void*, int); void SetIntProperty(void* p_obj, void* p_write, int value) { char* p = (char*)p_obj; int* p4 = (int*)p_write; if (((int)p4 & 0xFF000000) == 0xFF000000) { p += ((int)p4 & 0x00FFFFFF); p4 = (int*)p; *p4 = value; } else { FINTPROPSET writer = (FINTPROPSET)p4; writer(p_obj, value); } } Доступ к свойствам других типов осуществляется аналогично. |
Форум Программиста
Новости Обзоры Магазин Программиста Каталог ссылок Поиск Добавить файл Обратная связь Рейтинги
|