Чому не варто читати цю статтю?

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




















Поле Розмір Опис
TypeID 1 байт Ідентифікатор типу, см табл.2
NameLen 1 байт Довжина рядка з ім'ям типу
Different Розрізняється для різних
TypeID

Таблиця № 2






























Значення TypeID Опис
0x01 Цілі типи short, int
0x02 Сімволний тип char
0x03 Перелічувані
0x04 Речові типи float, double, long
double
0x05, 0x06 Не з'ясовано, швидше за все структури і
об'єднання
0x07 класи
0x08 покажчики на обробники подій
0х10 рядкові класи

Для классовпотомков 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. За цією адресою знаходиться наступна інформація:


  1. Ідентифікатор типу, рівний для класів 7 (1 байт)
  2. Довжина імені класу (1 байт)
  3. Рядок з ім'ям класу
  4. Покажчик на метод (імовірно конструктор копій) (4 байти)
  5. Покажчик на покажчик на базовий тип (от так от складно!) (4 байти)
  6. Кількість опублікованих властивостей, у тому числі і в базових класах (2 байти)

  7. Довжина імені модуля, де оголошено клас (1 байт)
  8. Назва модуля, де оголошено клас
  9. Число властивостей, опублікованих у самому класі (2 байти)
  10. Список властивостей, опублікованих у класі (кожне 26 байт + ім'я властивості)
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 для опублікованих властивостей


З вище наведеного дампа пам'яті можна усвідомити собі, в якому форматі
зберігатися опис опублікованого властивості. Тим не менш наведемо цей формат у
явному вигляді:


  1. Покажчик на покажчик з інформацією про тип властивості
  2. Доступ до значення властивості (читання): адреса методу або зсув в об'єкта.
    Якщо вказано зсув від початку класу, то в самому старшому байті записано 0xFF –
    щоб відрізнити від методу.
  3. Доступ до значення властивості (запис): адреса методу або зсув в об'єкті
  4. Значення параметра stored: 0, 1, або адресу предиката.
  5. Значення параметра index: 4 байтноє ціле
  6. Значення параметра default: 4 байтноє ціле
  7. Порядковий номер властивості: 2 байтноє ціле, з чого ясно, що більше ніж
    65535 властивостей не опублікувати, ді напевно і не треба.
  8. Ім'я властивості.

Для того щоб прочитати значення властивості слід виконати наступні
дії:


  1. Отримати RTTI для класу, що містить бажане властивість
  2. Отримати покажчик на опис бажаного властивості
  3. З'ясувати його тип
  4. Якщо читання здійснюється безпосередньо, обчислити адресу змінної – покажчик
    на об'єкт + зсув
  5. Якщо читання виконується через метод, слід привести покажчик до покажчика
    на метод і викликати його

Установка значення властивості виконується аналогічно.

Як видно з вище сказаного, установка і читання властивості неможливі без
знання типу властивості. Розглянемо коротко можливі формати опису типів:

Формат цілих типів:


  1. Ідентифікатор типу 0х01 (1 байт)
  2. Ім'я типу
  3. Варіанти (1 байт) 2 – short, 3 – unsigned short, 4 – int, long

Формат символьного типу


  1. Ідентифікатор типу 0х02 (1 байт)
  2. Ім'я типу
  3. Варіанти (1 байт) 0 – char, 1 – unsigned char

Формат перерахувань


  1. Ідентифікатор типу 0х03 (1 байт)
  2. Ім'я типу
  3. 9 байт
  4. Покажчик або на початок власного опису, або на покажчик на
    опису типу, де фактично і описано вміст перерахування (така ситуація
    характерна для багатьох типів, які прийшли з Pascalі мабуть пов'язана з особливостями
    компілятора цієї мови) (4 байти)
  5. Якщо покажчик (4) містить адресу початку власного опису, то слідом за
    ним йде список символьних констант, що входять до перерахування).
  6. Формат даних в перерахуванні 1,2 або 4 байти (1 байт)

Примітка: Якщо в описі перерахування вказані значення для символьних
констант, наприклад:

enum TSeason {winter = 100, spring = 200, summer = 300, autumn = 400};

то в RTTI такий тип трактується як цілий і вся символьна інформація не
включається до RTTI.

Формат дійсного типу


  1. Ідентифікатор типу 0х04 (1 байт)
  2. Ім'я типу
  3. Варіанти (1 байт) 0 – float, 1 – double, 2 – long double

До речовим типів відносяться також типи для представлення часу TDateTime
(Він оголошений тотожним типу double).

Формат класів

Формат опису класів розглянуто вище

Формат обробників подій


  1. Ідентифікатор типу 0х08 (1 байт)
  2. Ім'я типу
  3. Кількість параметрів у методу-обробника (1 байт)
  4. Опис параметрів у форматі: Ім'я параметра, Ім'я типу параметра (порядок,
    властивий для Pascal).

Формат строкових типів


  1. Ідентифікатор 0х10 (1 байт)
  2. Ім'я типу

У 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);
}
}


Доступ до властивостей інших типів здійснюється аналогічно.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*