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

0

Назначение приложения просмотра классов состоит в синтаксическом анализе и отображении на экране информации о классах компонентов C++, хранящейся в заголовочных файлах этих компонентов. Это приложение не является ни полноценным броузером классов (хотя, хорошенько вложившись, можно его довести до подобного уровня), ни идеальным анализатором (задавшись целью, его можно наполнить абсолютной чепухой), но в большинстве случаев оно вполне выполняет свою  миссию. Я абсолютно уверен, что если у вас есть время и  силы, вы сможете превратить это приложение в полноценный броузер классов и анализатор, у меня просто  не хватило сил на это.

Рис. 17.1. Форма приложения просмотра классов

На рис. 17.1 показана главная форма приложения. Она содержит списки, в которых будут перечислены доступные классы, а также методы и свойства для этих классов. Стоит сразу же отметить, что сам по себе анализатор не различает событие и свойство, поскольку на самом деле для системы CBuilder они одно и то же. Если вы хотите знать, что из них есть что, то, как правило, можете ориентировать ся по приставке «On» (при), отсутствующей у свойств и наличествующей у большинства событий.

Для создания этого приложения нам потребуется некоторое количество вспомогательного кода — для синтаксического анализа и создания списков. Если это ваш первый опыт  работы  с тяжеловесным кодом на C++ в CBuilder (но, скорее всего, не последний), вы получите хорошее представление о написании настоящего кода C++ (вместо работы только с визуальными (GUI) элементами системы). Помните, что CBuilder содержит мощный  компилятор  C++.  Используйте этот компилятор и все его возможности.

Вспомогательный  модуль

Вспомогательный модуль будет первой частью системы, которую мы  воплотим.  Этот  модуль будет предоставлять вспомогательные возможности, которые понадобятся нам в системе. Как правило, лучше разделять в ваших проектах код GUI (код, воплощающий и поддерживающий визуальные аспекты приложения) и вспомогательный код (многократно используемые классы и функции). Мы это сделаем в нашем теперешнем примере, выявив весь код, не относящийся к визуальным аспектам работы, и перенеся его во вспомогательный файл.

Выбрав File|New Unit, создайте в CBuilder новый модуль. Этот модуль содержит вспомогательные элементы, так что мы назовем его Utility. В созданный файл добавьте (или скопируйте с компакт- диска) следующий код. Листинг довольно большой, так что лучше, возможно, все же скопировать его с диска.

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

#include "Utility.h"

//——————————————————————-

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <ctype.h>

int GetWordQuoted ( char *string, char *word, char *brkChar )

{

// Ищем начало строки

int nEndPos = 0;

for ( int i=0; i<(int)strlen(string); ++i )

if ( !isspace(string[i])&&(string[i] != ‘,’ && string[i] != ‘|’) )

{

nEndPos = i; break;

Создаем приложение просмотра классов

}

// Ищем конец слова

char term_char = 0;

if ( string[nEndPos] == ‘"’ && string[nEndPos-1] != ‘\\’ )

{

term_char = ‘"'; nEndPos++;

}

int nWordPos = 0;

for ( int j=nEndPos; j<(int)strlen(string); ++j )

{

if ( term_char )

{

if ( string[j] == term_char )

{

word[j-nEndPos] = 0; j++;

nWordPos = j;

break;

}

}

else

if (isspace(string[j]) || string[j]==’,’ || string[j] == ‘|’ )

{

nWordPos = j; break;

}

word[j-nEndPos] = string[j]; nWordPos = j;

}

word[nWordPos-nEndPos] = 0; if ( brkChar )

*brkChar = string[nWordPos];

if ( string[nWordPos] == ‘,’ || string[nWordPos] == ‘|’ ) nWordPos++;

return nWordPos;

}

Функция GetWordQuoted возвращает отдельное «слово» в строке, где под «словом» понимается любой набор символов (возможно, заключенных в кавычки), начинающийся и заканчивающийся допустимым символом-разделителем. Эта функ

ция отлично подходит для выделения и выведения на экран отдельных слов из блока, так что мы будем ее использовать для просмотра названий классов, свойств и методов в наших заголовочных

файлах.

Int IsEndOfLine( char ch )

{

if ( ch == ‘;’ ) return 1;

if ( ch == ‘{‘ ) return 1;

if ( ch == ‘}’ ) return 1;

return 0;

}

Метод IsEndOfLine предназначен для синтаксического анализа выражений C++, он проверяет, является переданный ему символ концом строки в выражении C++ или нет. Выражения в C++ могут заканчиваться точкой с запятой, а также открывающейся или закрывающейся фигурными скобками. То есть, проще говоря, этот метод проверяет свой символ-параметр на совпадение с этими тремя символами и дает нам знать о результате сравнения.

BOOL CheckClass( char *strLine, char *strClassName )

{

char szWord[ 256 ];

int nPos = GetWordQuoted ( strLine, szWord, NULL ); if ( !strcmp(szWord, "typedef") ||

!strcmp(szWord, "extern") ) return false;

if ( !strcmp(szWord, "class") && strLine[strlen(strLine)-1] != ‘;’)

{

nPos += GetWordQuoted ( strLine+nPos, szWord, NULL ); if ( !strncmp(szWord, "__declspec", 10) )

GetWordQuoted ( strLine+nPos, szWord, NULL );

strcpy( strClassName, szWord ); return true;

}

return false;

}

Функция CheckClass проверяет, является ли переданное ей выражение описанием класса. Для этого она извлекает первое слово из этого выражения при помощи функции GetWordQuoted, а потом проверяет это слово на совпадение с описанием класса («class»). Функция игнорирует форвардные описания классов, принимая значение true, только в случае нормальных описаний классов.

int SkipToEndOfComment(FILE *fp)

{

int c = 0;

int last_c = 0; do {

c = fgetc(fp);

if ( c == ‘/’ && last_c == ‘*’ ) break;

if ( c == EOF ) break;

last_c = c;

} while ( !feof(fp) );

// Запоминаем, что следует за комментарием

if ( !feof(fp) )

c = fgetc(fp); return c;

}

Метод ScipToEndOfComment просматривает содержимое файла в поисках конца комментария (вложенные комментарии не поддерживает). Этот метод возвращает символ, следующий непосредственно за блоком комментария.

BOOL PreScanClasses(char *strFileName, TListBox *pListBox)

{

FILE *fp = fopen(strFileName, "r"); if ( fp == NULL )

{

return false;

}

char szBuffer[ 1024 ]; char szClassName[ 256 ]; int brace_cnt = 0;

while ( !feof(fp) )

{

int c = 0; int pos = 0;

// Очищаем буфер символов

memset ( szBuffer, 0, 1024 ); int last_c = 0;

do {

c = fgetc(fp); if ( c == ‘{‘ ) brace_cnt ++; if ( c == ‘}’ ) brace_cnt ++;

if ( c == ‘#’ && pos == 0 )

break;

if ( c == ‘/’ && last_c == ‘/’ ) break;

if ( c == EOF ) break;

if ( c == ‘*’ && last_c == ‘/’ )

{

last_c = 0;

pos –; // Отступаем назад к началу комментария

c = SkipToEndOfComment(fp);

}

// Проверяем, надо ли добавлять этот символ

if ( c != ‘\n’ )

if ( pos || !isspace(c) ) szBuffer[pos++] = c; last_c = c;

} while ( !IsEndOfLine(c) );

if ( (c == ‘#’ && pos == 0) || (c == ‘/’ && last_c == ‘/’) )

{

while ( c != ‘\n’ && !feof(fp) ) c = fgetc(fp);

}

else

if ( brace_cnt )

{

if ( CheckClass(szBuffer, szClassName ) ) pListBox->Items->Add( szClassName );

}

}

fclose(fp);

}

Функция PreScanClasses используется для извлечения из заголовочного файла имен классов для отображения в начальном списке классов в окне нашего приложения. Она просматривает заголовочные файлы и выделяет выражения C++, которые передает функции CheckClass для проверки, подходят ли они к синтаксису описания классов.

BOOL ProcessLine( BOOL bDisplay, char *strLine, char *szClassName, TListBox *pPropList,  TListBox *pMethodList )

{

char szWord[ 256 ];

int nPos = GetWordQuoted ( strLine, szWord, NULL ); if ( !strcmp(szWord, "typedef") ||

!strcmp(szWord, "extern") )

return bDisplay;

if ( !strcmp(szWord, "class") && strLine[strlen(strLine)-1] != ‘;’)

{

nPos += GetWordQuoted ( strLine+nPos, szWord, NULL );

// Пропускаем слово __declspec

if ( !strncmp(szWord, "__declspec", 10) )

nPos += GetWordQuoted ( strLine+nPos, szWord, NULL ); if ( !strcmp(szWord, szClassName) )

return true; else

return false;

}

// Если мы до сих пор не нашли класса,

// дальше можно не ходить if ( bDisplay == false ) return false;

// Ищем свойства

if ( !strcmp(szWord, "__property") )

{

char szType[ 256 ];

char szPropName[ 256 ];

nPos += GetWordQuoted ( strLine+nPos, szType, NULL ); nPos += GetWordQuoted ( strLine+nPos, szPropName, NULL );

if ( !strcmp(szPropName, "*") ||

!strcmp(szPropName, "&") )

{

strcat( szType, szPropName );

nPos += GetWordQuoted ( strLine+nPos, szPropName, NULL );

}

// Унаследованные свойства уже имеют имя

if ( strlen(szPropName) && strcmp(szPropName, ";") && strcmp(szPropName, "=") )

pPropList->Items->Add(szPropName);

else

pPropList->Items->Add(szType);

}

else

if ( strstr(strLine, "__fastcall") )

{

pMethodList->Items->Add(strLine);

}

return true;

}

Метод ProcessLine — это вспомогательная функция, используемая следующей функцией (GetMethodsAndProperties) для извлечения методов или свойств, обнаруженных в заданном выражении C++.

void GetMethodsAndProperties(char *fileName, char *className, TListBox *pPropList, TListBox *pMethodList )

{

FILE *fp = fopen(fileName, "r"); if ( fp == NULL )

{

return;

}

char szBuffer[ 1024 ]; int brace_cnt = 0;

BOOL bFoundIt = FALSE;

while ( !feof(fp) )

{

char c = 0; int pos = 0;

// Очищаем символьный буфер memset ( szBuffer, 0, 1024 ); char last_c = 0;

do {

c = fgetc(fp); if ( c == ‘{‘ ) brace_cnt ++; if ( c == ‘}’ ) brace_cnt ++;

if ( c == ‘#’ && pos == 0 )

break;

if ( c == ‘/’ && last_c == ‘/’ ) break;

if ( c == EOF )

break;

if ( c == ‘*’ && last_c == ‘/’ )

{

last_c = 0;

pos –; // Отступаем назад на начало комментария

c = SkipToEndOfComment(fp);

}

if ( c != ‘\n’ )

if ( pos || !isspace(c) ) szBuffer[pos++] = c;

if ( !strcmp(szBuffer, "public:") ||

!strcmp(szBuffer, "private:") ||

!strcmp(szBuffer, "protected:") )

{

pos = 0;

szBuffer[pos] = 0;

}

last_c = c;

} while ( !IsEndOfLine(c) );

if ( (c == ‘#’ && pos == 0) || (c == ‘/’ && last_c == ‘/’) )

{

while ( c != ‘\n’ && !feof(fp) ) c = fgetc(fp);

}

else

bFoundIt = ProcessLine( bFoundIt, szBuffer, className, pPropList, pMethodList );

}

fclose(fp);

}

Последняя   в   нашем   коде   функция   GetMethodsAndProperties   используется   для   заполнения второго (свойств) и третьего (методов) списка в нашем приложении.

Приведенные выше методы отвечают за синтаксический анализ строк и размещение данных таким

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

Теперь, когда мы разобрались со вспомогательными функциями, пришло время написать код для самой формы, используя эти замечательные функции. Первое, что надо сделать, — это добавить обработчик для команды меню Файл  д Открыть. Итак, добавьте обработчик  для  этого  пункта меню, а в него впишите следующие строки:

void __fastcall TForm1::Open1Click(TObject *Sender)

{

ListBox1->Clear();

if ( OpenDialog1->Execute() )

{

FstrFileName = OpenDialog1->FileName; PreScanClasses(FstrFileName.c_str(),  ListBox1);

}

}

Этот метод открывает  диалог открытия файла,  для того чтобы пользователь мог выбрать заголовочный файл для рассмотрения содержимого. После того как файл выбран,  загружается первый список (с именами классов) посредством вызова функции PreScanClasses, которую мы воплотили во вспомогательном модуле нашего проекта.

Выбор  следующего  пункта  меню  —  Файл  д  Выход  прекращает  работу  нашего  приложения. Добавьте обработчик для пункта меню Файл д Выход и внесите в него следующие строки:

void __fastcall TForm1::Exit1Click(TObject *Sender)

{

Application->Terminate();

}

Последним шагом процесса разработки формы станет обработка выбора класса в первом списке. Когда пользователь выбирает какой-либо класс в списке имеющихся классов, мы обновляем оба остальных списка с тем, чтобы в них были отражены свойства и методы выбранного класса. Добавьте обработчик для щелчка мыши в поле первого списка и в него добавьте строки:

void __fastcall TForm1::ListBox1Click(TObject *Sender)

{

AnsiString s;

// Получаем выбранный элемент

for ( int i=0; i<ListBox1->Items->Count; ++i ) if ( ListBox1->Selected[i] )

{

s = ListBox1->Items->Strings[i];

}

// Загружаем остальные списки

ListBox2->Clear(); ListBox3->Clear();

GetMethodsAndProperties(FstrFileName.c_str(),

s.c_str(), ListBox3, ListBox2 );

}

Как можно видеть из приведенного кода, большую часть  работы  выполняют  наши вспомогательные функции, которые могут произвести  синтаксический  разбор  заголовочного файла, загрузить списки и проверить корректность отображения информации для пользователя. Итак, теперь у вас есть полностью завершенное приложение просмотра классов, которое может использоваться для отображения всех классов, а также их свойств и методов, содержащихся в выбранном заголовочном файле. На рис. 17.2 показано окно работающего приложения, в котором отображены классы, найденные в директории, в которой находится VCL системы.

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

По теме:

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