Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
767
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

 

Borland C++ Builder (+CD). Библиотека программиста 371

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");

}

}

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

Создание Мастера

Мастер, или Эксперт, — это расширение системы CBuilder, которое служит пользователю для облегчения выполнения некоторых задач (в основном, по созданию чего-либо). Для создания компонентов в CBuilder существует Мастер компонентов, который можно вызвать посредством команды меню Component д New. Кроме этого, в системе есть Мастер форм (Dialog Form Wizard),

Borland C++ Builder (+CD). Библиотека программиста 372

Мастер диалогов (Dialog Wizard, доступен только в версиях CBuilder Professional и выше) и основной Мастер форм, находящиеся в Object Repository. Хорошо подготовив API для использова ния Мастеров, фирма Borland предоставляет вам возможность вносить собственные добавления в систему CBuilder.

На рынке условнобесплатных программ (shareware) существует несколько Мастеров для CBuilder. Причем самый интересный среди них вообще бесплатный (freeware). Написанный Даниэлем Карей (Daniel Carey), программирующим на CBuilder, он предоставляет возможность создавать простейшую основу для Мастеров в CBuilder. По своей сути это Мастер создания Мастеров, который облегчает вам создание своих собственных Мастеров. Я несколько раз прибегал к его помощи для создания основ своих Мастеров, и, естественно, вы также можете воспользоваться им. Вы найдете этот Мастер в виде и исходного, и двоичного (DLL) файла на прилагаемом компакт- диске в директории Extras.

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

Когда вы создаете новую DLL в системе CBuilder, вы выполняете практически всю ту же самую работу, что и при создании Мастера. Используя Мастер создания Мастеров мы создадим основу нашего Мастера компонентов++ и рассмотрим ее. Если вы не хотите самостоятельно инсталлировать Мастера или вообще заниматься им, просто посмотрите на код, написанный мной:

#include <vcl\sharemem.hpp> #include <vcl\vcl.h> #include <vcl\exptintf.hpp> #include "ClassTabForm.h" #include "DefPropForm.h" #include "DefMethodForm.h" #pragma hdrstop USERES("CompWiz.res"); USELIB("Bcbmm.lib");

USEFORM("ClassTabForm.cpp", PagesDlg); USEUNIT("Utility.cpp"); USEFORM("DefPropForm.cpp", Form1); USEFORM("DefMethodForm.cpp", Form2); //-------------------------------------------------------------------

TExpertState tx; //-------------------------------------------------------------------

int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason,void*)

{

return 1;

}

Это всего лишь точка входа для всех DLL. В DLL должна быть эта функция, и она должна экспортироваться, с тем чтобы Windows знала, как загружать DLL в память. Любая глобальная инициализация или выделение/высвобождение памяти будут осуществляться здесь. Эта функция будет вызываться неоднократно при работе с DLL, особо при загрузке и выгрузке DLL.

class __declspec(delphiclass) CompWiz; class CompWiz : public TIExpert

Borland C++ Builder (+CD). Библиотека программиста 373

{

public:

System::AnsiString __stdcall GetName(void);

//Короткое описательное имя

System::AnsiString __stdcall GetAuthor(void){};

//Не используется в данном Мастере

System::AnsiString __stdcall GetComment(void){};

//Не используется в данном Мастере

System::AnsiString __stdcall GetPage(void){};

//Не используется в данном Мастере

HICON __stdcall GetGlyph(void){};

//Не используется в данном Мастере

TExpertStyle __stdcall GetStyle(void);

//Стиль Мастера, форма, проект и т.п. TExpertState __stdcall GetState(void){};

//Не используется в данном Мастере

System::AnsiString __stdcall GetIDString(void);

//Уникальный внутренний идентификатор

System::AnsiString __stdcall GetMenuText(void){};

//Не используется в данном Мастере

void __stdcall Execute(void){};

// Не используется в данном Мастере

void __fastcall OnClick( TIMenuItemIntf* Sender);

// Вызывается, когда пользователь выбирает команду меню void __fastcall AddMenuItem(void);

__fastcall CompWiz(void){}; __fastcall virtual ~CompWiz(void){}; };

Это описание класса и есть, собственно говоря, наш Мастер. Обратите внимание, что класс наследует от класса VCL, называемого TIExpert. Этот класс, не отраженный в системе контекстной помощи по системе VCL CBuilder, формирует основу для всех Мастеров в системе CBuilder. Обратите внимание на комментарии под методами класса. Они были сгенерированы Мастером для того, чтобы вам было проще ориентировать ся в том, какие из них используются, а какие нет в данном типе Мастеров. Создаваемый нами Мастер принадлежит к расширениям системы (тип add-in) CBuilder, то есть он будет присутствовать в меню Tools (инструменты) и сможет выполнять довольно большой объем работ. К другим типам Мастеров относятся Мастера форм (создающие различные виды форм), Мастера проектов (создающие проекты целиком) и стандартные Мастера (выполняющие обычные повторяющиеся задания).

void HandleException(void)

{

}

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

void __stdcall RunExpert( TIMenuItemIntf* )

{

Borland C++ Builder (+CD). Библиотека программиста 374

}

Метод RunExpert основная точка входа в Мастер. Он отвечает практически за все, что делает Мастер. Код, который должен быть добавлен в Мастера, в основном будет нами добавляться именно в этот метод.

System::AnsiString __stdcall CompWiz::GetName(void)

{

try

{

return "CompWiz";

}

catch(...)

{

HandleException();

}

return "";

}

Метод GetName возвращает короткое, простое имя Мастера. Мы хотим, чтобы наш Мастер создавал компоненты, так что его короткое имя будет CompWiz, то самое имя, которое возвращается в IDE CBuilder для использования в различного рода сообщениях.

TExpertStyle __stdcall CompWiz::GetStyle(void)

{

try

{

return esAddIn;

}

catch(...)

{

HandleException();

}

return esAddIn;

}

Метод GetStyle вызывается системой CBuilder для того, чтобы определить тип Мастера, с которым она имеет дело. В нашем случае это Мастер-расширение системы (add-in), так что мы возвращаем константу esAddIn. Кроме этого, возможны следующие константы типов: esStandart, esForm, esProject. Эти константы определены в заголовочном файле EXPTINTF.HPP, который находится в директории include\vcl дерева каталогов CBuilder.

System::AnsiString __stdcall CompWiz::GetIDString(void)

{

try

{

return "CWZ100";

}

catch(...)

{

HandleException();

}

return "";

Borland C++ Builder (+CD). Библиотека программиста 375

}

Метод GetIDString возвращает строку-уникальный идентификатор Мастера. В нашем случае я выбрал значение CWZ100 (Component Wizard 1.00). Вы можете выбрать какую угодно строку, но с тем условием, чтобы она была уникальна для данного Мастера.

__fastcall CompWiz::OnClick( TIMenuItemIntf* Sender)

{

try

{

RunExpert( 0L );

}

catch(...)

{

}

}

Обработчик события OnClick вызывается системой, когда пользователь выбирает команду меню, ассоциированную с данным Мастером (командой меню мы займемся в ближайшем будущем). Мы

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

void __fastcall DoneExpert(void)

{

}

Метод DoneExpert вызывается , когда Мастер прекращает свою работу. Если вам надо что-нибудь подчищать при этом, код следует добавлять именно в этот метод.

void __fastcall CompWiz::AddMenuItem(void)

{

int index; TIMainMenuIntf *mmi; TIMenuItemIntf *miparent; TIMenuItemIntf *michild; TIMenuFlags mf;

try

{

mf << mfVisible << mfEnabled;

mmi = ToolServices->GetMainMenu();

michild = mmi->FindMenuItem( "ToolsGalleryItem"); index = michild->GetIndex();

index++;

miparent = michild->GetParent(); miparent->InsertItem( index, "ComponentWizard++", "CompWiz", "", 0, 0, 0, mf, OnClick ); michild->Release();

miparent->Release(); mmi->Release();

}

Borland C++ Builder (+CD). Библиотека программиста 376

catch(...)

{

}

}

Метод AddMenuItem вызывается для добавления нового пункта в главное меню IDE CBuilder. В данном случае мы добавляем в меню, содержащее пункт ToolsGalleryItem (то есть в меню Tools), пункт с заголовком «Component Wizard++». Идентификатор этого пункта меню был получен простым увеличением последнего в меню идентификатора, то есть новый пункт добавляется в конец меню Tools. Этому пункту соответствует метод OnClick, обрабатывающий обращение к пункту. Вот как организовано взаимодействие кода Мастера и IDE:

extern "C" __declspec(dllexport) bool

__stdcall INITEXPERT0016( Toolintf::TIToolServices* ToolServices, TExpertRegisterProc RegisterProc,

TExpertTerminateProc &Terminate)

{

//Убеждаемся, что нашли первый и единственный

//нужный пункт

int Result = Exptintf::ToolServices == NULL; if (!Result) return false; Exptintf::ToolServices = ToolServices;

if (ToolServices != NULL)

Application->Handle = ToolServices->GetParentHandle(); else

return false;

Terminate = DoneExpert; // Регистрация Мастера

CompWiz *ew = new CompWiz; (*RegisterProc)(ew); ew->AddMenuItem();

return true;

}

Это наша регистрирующая функция, которая вызывается IDE CBuilder при первой загрузке DLL. DLL загружается при регистрации в CBuilder (мы поговорим о том, как это происходит, чуть позже), и система начинает работу. После этого каждый Мастер в системе опрашивается и загружается регистрирующей функцией. Сначала эта функция проверяет, зарегистрированы ли мы уже. Если нет, то создается новый экземпляр класса CompWiz и в меню Tools добавляется новый пункт.

Замечание

В этой главе используется как термин Мастер (wizard), так и термин Эксперт (expert), так же как и в документации фирмы Borland. Использование того или иного из них это дело привычки.

Добавление функциональных возможностей в Мастер

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

Borland C++ Builder (+CD). Библиотека программиста 377

На самом деле добавление новых возможностей не составит большого труда. В конце концов, у нас уже есть форма для получения всех необходимых данных и есть код, позволяющий сгенерировать новый класс компонента, базирующийся на этих данных. То есть вся работа уже сделана, и все, что нам надо, — это добавить форму и все остальные модули из предыдущего приложения в DLL, отобразить эту форму при вызове Мастера, и все остальное будет сделано без нашего участия. Собственно, для этого-то мы и создавали форму сначала в простом приложении. Так гораздо проще тестировать функции, исправлять ошибки, короче говоря, доводить форму и остальные функции до удовлетворяющего нас состояния, а потом уже объединять их в полноценный Мастер CBuilder. В разработке любых приложений руководствуйтесь принципом на каждом этапе решать только одну задачу и ваша жизнь станет веселее.

Скопируйте файлы формы и модулей из проекта приложения (кроме файла проекта, MAK-файла и промежуточных файлов компилятора) в новую директорию. После этого проделайте приведенные ниже преобразования кода Мастера. Естественно, больше всего изменений придется внести в метод RunExpert, поскольку он отвечает за всю работу Мастера. Вот как выглядит код для этого метода:

__stdcall RunExpert( TIMenuItemIntf* )

{

TPagesDlg *frm; try

{

frm = new TPagesDlg(Application); Form1 = new TForm1(frm); Form2 = new TForm2(frm);

if( frm->ShowModal() )

{

TCreateModuleFlags mf;

mf << cmAddToProject << cmNewUnit << cmMarkModified; ToolServices->CreateCppModule( frm-> SourceFile->Text,"","","", 0L, 0L, 0L, mf );

}

frm->Free();

}

catch(...)

{

HandleException();

}

}

В этом коде есть несколько вещей, на которые стоит обратить внимание. Во-первых, это использование блока try ... catch по отношению ко всему коду метода для того, чтобы обезопасить пользовательскую систему не дать возможной ошибке в Мастере просочиться на уровень IDE. Следующий небезынтересный момент создание экземпляров всех форм системы непосредственно в функции RunExpert. Поскольку у DLL в отличие от приложений нет объекта application, нет, соответственно , и автоматического создания форм; поскольку нет автоматического создания, вам надо создать формы самостоятельно до того, как вы будете их использовать. Если формы создать не удастся, VCL сгенерирует исключительную ситуацию и работа Мастера будет прекращена. Если формы были успешно созданы, мы идем дальше и модально отображаем первую из них, то есть на экране появляется обычный страничный диалог, позволяющий пользователю вводить данные для нового компонента точно так же, как он это

Borland C++ Builder (+CD). Библиотека программиста 378

делал в простом приложении.

После отображения формы следует блок кода, который является вызовом метода CreateCppModule объекта ToolServices. Этот вызов весьма важен он добавляет в данный проект новый модуль, точно так же, как и CBuilder в своем «родном» Мастере компонентов. Для обеспечения совместимости нам также придется это сделать. Далее форма удаляется вызовом метода Free базовой формы.

Небольшое отступление: объект ToolServices

Одним из наиболее важных объектов, использующихся при создании Мастеров, является объект ToolServices, который содержит методы для работы с самой IDE. В табл. 17.4 перечислены методы класса TIToolServices, экземпляром которого и является этот объект2.

Таблица 17.4. Методы класса TIToolServices

CloseProject Закрывает текущий проект

OpenProject Открывает новый проект, определенный в вызове

SaveProject Сохраняет текущий проект CloseFile Закрывает редактируемый файл

OpenFile Открывает указанный файл в окне редактора SaveFile Сохраняет редактируемый файл

ReloadFile Заново считывает редактируемый файл с диска CreateModule Создает новый модуль (модули) в текущем проекте

CreateModuleEx То же самое, что и CreateModule, но с большим количеством опций GetParentHandle Возвращает ссылку (handle) родителя объекта, вызывающего метод

GetProjectName Возвращает имя текущего проекта

GetUnitCount Возвращает количество модулей (unit) в текущем проекте

GetUnitName Возвращает имя указанного (по индексу) модуля (unit) в текущем проекте EnumProjectUnits Перебирает все модули (unit) в проекте, вызывая для каждого обработчик,

определенный пользователем

GetFormCount Возвращает количество форм в текущем проекте

GetFormName Возвращает имя указанной (по индексу) формы в текущем проекте IsFileOpen Определяет, загружен ли указанный файл в редактор

GetNewModuleName Возвращает имя следующего модуля, используя стандартные для проекта обозначения (напр. Unit1,Unit2)

GetModuleCount Возвращает количество модулей (module) в текущем проекте GetComponentCount Возвращает количество компонентов, определенных в данной системе GetMainMenu Возвращает ссылку (handle) на главное меню системы IDE CreateCppModule Создает в проекте новый файл CPP и (в зависимости от параметров)

добавляет его в проект

RaiseException Позволяет программисту возбуждать исключительные ситуации приложения в системе

2 В данной главе слово «модуль» используется для обозначения двух различных терминов, module

иunit. Термином module обозначают все модули системы, то есть исходный файлы, формы, DLL

ит. п. Термин же unit означает пару из исходного файла (например, Unit1.cpp) и заголовочного

(Unit1.h). Будьте бдительны! — Примеч. перев.

Borland C++ Builder (+CD). Библиотека программиста 379

Как вы, наверное, поняли из таблицы, методы объекта ToolServices предостав ляют вам полный контроль над IDE. Имея в своем распоряжении подобные методы, вы можете посредством своих Мастеров добавить в систему любые новые возможности. Я думаю, что если вы привыкли самостоятельно создавать всяческие расширения для различных систем, то CBuilder вам весьма и весьма понравится. Действительно, эта система предоставляет вам для создания Мастеров все свои возможности и обрабатывает внутренние ошибки в Мастерах с той же тщательностью, что и свои собственные проблемы. Поэтому практически невозможно обрушить систему CBuilder посредством Мастера (хотя я уверен, что найдутся и такие умельцы ломать не строить!).

А теперь давайте вернемся к нашему примеру.

Проблемы, проблемы, проблемы

Первое, что вы обнаружите, сынсталлировав и запустив Мастера непосредственно из среды разработки, так это то, что он не работает. Удивительно? Не очень. Ведь мы велели формам использовать базы данных, находящиеся в локальной директории приложения. Для DLL локальной является текущая директория CBuilder, а не текущая директория DLL. Измените названия директорий в таблицах базы данных так, чтобы они соответствовали директории, в которую вы сынсталлировали DLL и базу данных.

А теперь важное замечание. Если вы инсталлируете DLL в ту же директорию, где и создаете ее, то вскоре выясните, что не можете встроить ее в среду. Причина этого весьма проста. CBuilder загружает DLL в память операционной системы, и операционная система не позволит вам переписывать этот файл, пока он загружен в нее, так что в случае если вы все-таки попробуете так сделать, то получите сообщение операционной системы о невозможности совместного доступа к файлу (sharing violation). Так что всегда инсталлируйте свои DLL из любой директории системы (например, CBuilder\bin), кроме той, в которой вы ее тестировали. Естественно, для того чтобы протестировать изменения, внесенные в DLL, вам придется закрыть IDE и запустить ее заново. Здесь мы столкнулись с проблемами скорее операционной системы, нежели CBuilder. Раз загруженная, DLL остается в памяти операционной системы до тех пор, пока не будет закрыта IDE. Поэтому вы не можете и скопировать ее поверх старой версии (вы получите то же самое сообщение о невозможности совместного доступа).

Инсталляция Мастера

Перечитав последний раздел этой главы, я вдруг осознал, что до сих пор не рассказал вам, как же инсталлировать Мастера. Это напомнило мне старый анекдот про двух саперов, обезвреживающих мину нового типа. Один возится с кусачками в хитросплетении проводов, ведущих к взрывателю, а другой зачитывает ему инструкцию. Между ними происходит следующий диалог:

Перекусите синий провод...

Так, есть, понял... готово!

...после того, как перекусите красный.

Надеюсь, что моя ошибка не столь критична, так что давайте рассмотрим-таки процесс инсталляции. Для инсталляции Мастера сначала закройте CBuilder. После этого запустите программу regedit (Registry Editor, редактор реестра системы), набрав ее название в запросе Выполнить... меню Пуск. Нажмите Enter, и программа запустится. Вы увидите список разделов (key) реестра. Дважды щелкните на разделе HKEY_CURRENT_USER, а потом на нижележащем разделе Software. Среди прочих вы увидите раздел Borland. Дважды щелкните на нем, чтобы добраться сначала до раздела CBuilder, а потом внутри него до раздела 1.0. В этом разделе находится большое количество подразделов. Если там есть раздел Experts, добавьте в него новый

Borland C++ Builder (+CD). Библиотека программиста 380

строковый параметр. Имя параметра не важно (оно нужно только вам), но значение его должно содержать полный путь к файлу DLL Мастера, который вы создали. На рис. 17.10 показано окно редактора реестра с раскрытым разделом Experts в дереве реестра системы.

Рис. 17.10. Окно редактора реестра с разделом мастеров CBuilder

Программа инсталляции Мастера

Весь описанный только что процесс весьма долог и мучителен. Я, честно говоря, не могу понять, почему фирма Borland не предоставила нам в распоряжение простенького пункта меню для инсталляции Мастера. С другой стороны, я не вижу причин, не позволяющих нам самим облегчить себе жизнь. Среда CBuilder призвана всемерно облегчать нам жизнь, так что давайте создадим небольшое приложение, позволяющее нам инсталлировать Мастера с комфортом. На рис. 17.11 показана форма, которую мы будем использовать для этого.

Рис. 17.11. Форма для приложения инсталляции Мастеров

Для того чтобы приложение работало, нам надо написать, по сути, два блока кода. В первом мы должны обработать нажатие на кнопку «...», при котором будет отображаться окно диалога открытия файлов, в котором пользователь сможет выбрать DLL или другой файл для инсталляции. Вот как выглядит код для этого обработчика:

void __fastcall TForm1::Button1Click(TObject *Sender)

{

if ( OpenDialog1->Execute() )

{

Edit2->Text = OpenDialog1->FileName;

}

}

Соседние файлы в предмете Программирование на C++