Главная » C++, C++ Builder » Генерация исходного кода компонента приложение просмотра классов в CBuilder

0

Когда пользователь нажимает кнопку OK страничного диалога, он подразумевает, что приложение в ответ на это сгенерирует ему код для нового компонента. Очевидно, что сначала программа должна провести некоторые проверки (например, проверить, введены ли имена исходного и заголовочного файла), но в основном она будет оставлять введенные вами значения на вашей совести, не пытаясь их исправлять. Если, например, вы задали недопустимые имена для свойств, программа позволит сгенерировать эти свойства. Это неизбежная расплата за объем работы по написанию приложения — чем больше в программе проверок, тем труднее ее написать, а главное, отладить. Естественно, никто не собирается ограничивать свободу вашего творчества — если вы захотите сделать программу более строгой — пожалуйста.  Если  вы  сможете  довести ее  до ума,

дайте мне знать — я с большим интересом посмотрю на результат.

Итак, добавьте обработчик для кнопки OK; добавьте в него следующий код: void __fastcall TPagesDlg::OKBtnClick(TObject *Sender)

{

// Проверяем введенную информацию на полноту

if ( ClassName->Text.Length() == 0 )

{

MessageBox(NULL,"Необходимо  указать имя класса!","Error",MB_OK); return;

}

if ( SourceFile->Text.Length() == 0 )

{

MessageBox(NULL, "Необходимо задать имя исходного файла !", "Error", MB_OK ); return;

}

if ( HeaderFile->Text.Length() == 0 )

{

MessageBox(NULL, " Необходимо задать имя заголовочного файла!", "Error", MB_OK ); return;

}

// Создаем результирующий исходный файл FILE *fp = fopen(HeaderFile->Text.c_str(), "w"); if ( fp == NULL )

{

MessageBox(NULL, " Не могу открыть заголовочный файл! ", "Error", MB_OK );

return;

}

GenerateHeaderFile(fp); fclose(fp);

FILE *sfp = fopen(SourceFile->Text.c_str(), "w"); if ( sfp == NULL )

{

MessageBox(NULL, " Не могу открыть исходный файл!", "Error", MB_OK );

return;

}

GenerateSourceFile(sfp); fclose(sfp);

}

Этот код сам по себе особо не примечателен, но на его примере я хотел бы дать вам пару советов. Во-первых, из этого кода видно, как можно прервать процесс генерации кода для компонента, выдав сообщение об ошибке (настоятельно рекомендую) и возвратив управление из обработчика. Во-вторых, в этом коде используются старомодные функции ввода-вывода fopen и fprintf. Так вот, эти функции чудесно работают в CBuilder, так что не бойтесь их использовать. Вы, конечно, вполне можете работать и с потоками ввода/вывода (stream), но в данном приложении широко используется форматированный вывод — слабое место этих потоков.

Сама функция проверяет, введены ли все необходимые для генерации поля (имя класса, базовый класс, заголовочный файл). Если  это сделано, программа  пытается открыть заголовочный файл для записи. Хочу отметить, что  это решение вовсе не является очевидным. В данном случае я предпочел целиком переписывать при генерации исходный и заголовочный файлы, хотя более перспективным на первый взгляд вариантом было бы добавление кода в существующий файл. Однако в этом случае нас подстерегает куда больше проблем, чем видно с первого взгляда, особенно это касается заголовочного файла. Придется беспокоиться обо всех подключаемых файлах, особенно о файлах базового класса, и не только об этом. Чтобы избежать всех  этих проблем, я принял более простой вариант — каждый раз создавать новые файлы. Собственно, эта проблема не относится напрямую к основной теме нашего разговора; мне просто хотелось обратить ваше внимание на один из подводных камней, подстерегающих вас на пути разработчика компонентов и Мастеров, и продемонстрировать необходимость мыслить  глобальными категориями, а не конкретными нуждами отдельной программы.

Эта функция использует несколько вспомогательных функций. Сначала давайте рассмотрим генерацию заголовочного файла. В заголовочном файле класса компонента будут  содержаться только описания и никакого кода. Ниже приведен код функции GenerateHeaderFile, которую надо добавить в наше приложение (надеюсь, вы не забываете добавлять прототипы функций в заголовочный файл?):

void TPagesDlg::GenerateHeaderFile(FILE *fp)

{

fprintf(fp,"//——————————————————-   ——————-\n");

fprintf(fp,"#ifndef %sH\n", ClassName->Text.c_str() ); fprintf(fp,"#define %sH\n", ClassName->Text.c_str() );

fprintf(fp,"//——————————————————-   ——————–\n");

fprintf(fp,"#include  <vcl\SysUtils.hpp>\n"); fprintf(fp,"#include  <vcl\Controls.hpp>\n"); fprintf(fp,"#include  <vcl\Classes.hpp>\n"); fprintf(fp,"#include  <vcl\Forms.hpp>\n");

fprintf(fp,"//——————————————————-   ——————–\n");

fprintf(fp,"class %s : public %s\n", ClassName->Text.c_str(), ComboBox1->Text.c_str() ); fprintf(fp,"{\n"); fprintf(fp,"private:\n"); GenerateMemberVariables(fp); fprintf(fp,"protected:\n"); fprintf(fp,"public:\n");

for ( int i=0; i<ListBox2->Items->Count; ++i )

{

fprintf(fp, " %s\n",

ListBox2->Items->Strings[i].c_str()  );

}

fprintf(fp,"__published:\n"); GenerateProperties(fp); fprintf(fp,"};\n");

fprintf(fp,"//——————————————————-   ——————–\n");

fprintf(fp,"#endif\n");

}

Ничем  не  примечательная  функция,  не  правда  ли?  Для  генерации  заголовочного  файла  нам требуется  некоторая основа.  В  заголовочном  файле  всегда  содержится  пара  ifdef/endif,  которая

предотвращает неоднократное включение заголовочного файла; для использования VCL включаются заголовочные файлы базового класса. После этого идет собственно описание класса. Оно состоит из выражения, определяющего класс, фигурных скобок, секций private, public и protected. Мы добрались до нашей первой проблемы — генерации переменных-членов класса в секции private. Каждое свойство, определенное в классе, должно иметь соответствующую ему переменную класса для хранения данных.

Для генерации переменных-членов класса нам нужно знать о них две вещи — тип определяемой переменной и ее имя. В данной версии генератора мы сделаем вполне логичное допущение о том, что типы переменных совпадают с типами свойств, которые они  представляют. В большинстве случаев наше допущение будет вполне оправдано. Наш генератор не поддерживает индексированные свойства (к сожалению, это еще одно ограничение), так что нам не придется разбирать те случаи, при которых могут возникнуть различия.

Определив тип переменной, можно переходить к имени. На наше счастье в CBuilder существуют определенные соглашения, которые помогут нам определиться с именами. Имена переменных- членов класса, определяющих свойства, формируются путем добавления заглавной буквы  F  к имени свойства, так мы и будем поступать для наших компонентов. В конце концов, соглашения ведь существуют для того, чтобы им следовать, не так ли?

Следующая проблема, с которой нам придется столкнуться, связана с тем, что нам надо генерировать переменные класса не для всех свойств, а только для тех, которые не были подсажены в класс. На самом деле, если бы мы даже и сгенерировали переменные для подсаженных свойств, они бы стали локальными внутри нашего класса и нам не было бы от них никакого прока. Как нам справиться с этой проблемой? Ответ лежит в том, как мы создавали новые свойства. Если вы помните, для них мы записывали и тип, и имя свойства, а у подсаженных свойств — только имя. Следовательно, мы можем проверять свойства на наличие пробела в имени (пробела     между     типом     свойства     и      его      именем),      который      будет      служить нам своеобразным индикатором. Так мы и поступим. Вот код для функции GenerateMemberVariables, в которой мы будем осуществлять эту проверку:

void TPagesDlg::GenerateMemberVariables(FILE  *fp)

{

// Ищем свойства, в описании которых есть пробел

// Этим свойствам надо сопоставить переменные класса

for ( int i=0; i<ListBox4->Items->Count; ++i )

{

AnsiString s = ListBox4->Items->Strings[i]; if ( strchr( s.c_str(), ‘ ‘) )

{

char szProp[ 256 ]; char szType[ 256 ];

// Можем найти конец строки

s += " ";

int nPos = GetWordQuoted( s.c_str(), szType, NULL); GetWordQuoted( s.c_str()+nPos, szProp, NULL);

// Создаем переменную класса этого типа

fprintf(fp, " %s F%s;\n", szType,

szProp );

}

}

}

Для самих свойств мы вызываем метод  GenerateProperties.  Этот  метод  тоже  выполняет различные действия в зависимости от того, является ли свойство подсаженным. Если да, то генерируется прототип свойства:

__property Fred;

С другой стороны, если свойство является новым, нам потребуется его полное описание:

__property int Fred={read=FFred, write=FFred}

Вот как будет выглядеть полный код для метода GenerateProperties: void TPagesDlg::GenerateProperties(FILE *fp)

{

// Ищем свойства, в описании которых есть пробел

for ( int i=0; i<ListBox4->Items->Count; ++i )

{

AnsiString s = ListBox4->Items->Strings[i]; if ( strchr( s.c_str(), ‘ ‘) )

{

char szString[ 256 ]; char szProp[ 256 ]; char szType[ 256 ];

strcpy ( szString, s.c_str() );

// So we can find the end of the string. strcat ( szString, " " );

int nPos = GetWordQuoted( szString, szType, NULL); GetWordQuoted( szString+nPos, szProp, NULL); fprintf(fp, " __property %s %s={read=F%s, write=F%s};\n",

szType, szProp, szProp, szProp );

}

else

fprintf(fp, " __property %s;\n", s.c_str());

}

}

В заголовочном файле методы уже определены посредством нашего метода GenerateHeader. Все методы хранятся в списке в том формате, в котором представляются в заголовочном файле, вне зависимости от того, были они скопированы  туда или же созданы заново, поэтому мы  можем просто копировать элементы списка в заголовочный файл.

Сгенерировав заголовочный файл, можно приступать к созданию исходного файла. Метод GenerateSourceFile используется для создания файла и добавления в него всех синтаксических конструкций, необходимых для создания исходного кода для методов. Вот как  выглядит  код метода GenerateSourceFile:

void TPagesDlg::GenerateSourceFile(FILE *fp)

{

fprintf(fp,"//——————————————————-   ——————–\n");

fprintf(fp,"#include  <vcl\\vcl.h>\n"); fprintf(fp,"#pragma  hdrstop\n\n");

fprintf(fp,"#include \"%s\"\n",  HeaderFile->Text.c_str());

fprintf(fp,"//——————————————————-   ——————–\n");

fprintf(fp,"static inline %s *ValidCtrCheck()\n", ClassName->Text.c_str());

fprintf(fp,"{\n");

fprintf(fp," return new %s(NULL);\n", ClassName->Text.c_str()); fprintf(fp,"}\n");

fprintf(fp,  "//——————————————————  ———————\n");

fprintf(fp, "__fastcall %s::%s(TComponent* Owner)\n", ClassName->Text.c_str(),

fprintf(fp," : %s(Owner)\n", ComboBox1->Text.c_str()); fprintf(fp,"{\n");

fprintf(fp,"}\n"); GenerateMethodsSource(fp);

}

Этот метод чрезвычайно прост. Он создает в начале исходного файла список подключаемых файлов, включая и заголовочный файл для нашего класса. Для создания корректного экземпляра компонента, который можно было бы использовать в редакторе форм, генерируется вызываемый VCL метод ValidCtrlCheck.  После этого создается основной конструктор, который потребуется VCL для вызова конструктора базового класса. Как нетрудно догадаться, весь этот код является обобщенным вариантом кода базового компонента.

Последний  этап   —  это   генерация  отдельных  методов  класса.   Это  осуществлено   в  методе

GenerateMethodSource. Создайте этот метод и добавьте в него следующие строки:

void TPagesDlg::GenerateMethodsSource(FILE *fp)

{

for ( int i=0; i<ListBox2->Items->Count; ++i )

{

AnsiString s = ListBox2->Items->Strings[i]; char szString[ 256 ];

char szType[ 256 ];

strcpy ( szString, s.c_str() );

int nPos = GetWordQuoted( szString, szType, NULL);

// Избавиться от хвоста строки; szString[strlen(szString)-1] = 0;

fprintf(fp, "%s %s::%s\n", szType, ClassName->

Text.c_str(), szString+nPos ); fprintf(fp, "{\n}\n");

}

}

На данный момент  у нас получилось вполне работоспособное приложение для  генерации компонентов. Однако цель примера была создать Мастера. Давайте теперь  займемся непосредственно этим.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии