Во-первых, что такое
Plug-in? Это компонент программы, подключаемый пользователем
к готовому продукту без рекомпиляции кода, расширяющий и дополняющий
возможности продукта. Использование Plug-in’ов
позволяет облегчить обновление программы и расширение её возможностей
в отсутствие исходного кода.
Во-вторых, как программа должна узнавать
о наличие Plug-in’ов? Возможны несколько вариантов. Как известно, есть
два основных способа хранения пользовательской информации о программе:
Ini-файлы,
оставшиеся в наследство от Windows 3.x,
и системный реестр. Использование реестра делает программу более профессиональной
на вид, но требует регистрации компонент при установке либо специальным
инсталлятором (а его еще писать надо – влом !!), либо самим юзером,
желающим проинсталлировать ваше творение (а ручки то у юзера – крючки;
бойся юзера, реестр копающего). Отсюда вывод: будем использовать Ini-файлы,
а реестр отложим на потом( когда юзеры подрастут).
В-третьих, программа-то найдет свои Plug-in’ы
, а вот как об этом узнает пользователь? Если особо не извращаться,
то Plug-in’ы должны быть доступны через пункты меню. А меню, соответственно,
должно искать Plug-in’ы и включать в себя соответствующие пункты и
служебную информацию. Теперь можно перейти к делу.
Пусть Plug-in’ы представляют из себя простые
exe-модули
(чтоб не лезть в рамках данного текста в дебри динамических DLL),
принимающие необходимые им параметры через командную строку. Такие Plug-in’ы
обычно забирают данные из файла и пишут результат тоже в файл. Тогда
любой доступный вашей программе Plug-in можно охарактеризовать следующими
параметрами:
- имя exe-модуля;
- название пункта меню, связываемого с этим модулем;
- тип
Plug-in’а;
- тип входного файла
(ваша программа может различать несколько типов Plug-in’ов, обрабатывающих
разные данные);
- тип выходного файла (ваша программа может
различать несколько типов Plug-in’ов, обрабатывающих разные данные);
- дополнительные параметры;
Вообще в каждом конкретном случае этот
набор может меняться, так что первым делом спроектируйте более-менее
общий интерфейс для ваших Plug-in’ов, в том числе с возможностью последующего
расширения.
При таком наборе параметров структура Ini-файла
может быть следующей:
[Menu_Item_Name]
path=” plugin-path
”
type= _plugintype_
intype= _some_reserved_type_
outtype= _some_reserved_type_
Каждый такой
блок описывает один Plug-in. Из этих блоков и будет состоять ваш Ini-файл. Как было сказано выше, набор параметров может меняться, но каждый блок
должен предваряться уникальным идентификатором в квадратных скобках
(в данном примере – именем пункта меню).
Ini-файлы
хороши и тем, что в средах Delphi и C++ Builder есть
стандартные объекты, реализующие работу с ними. А работать с ним мы
будем только один раз, при вызове приложения. В этот момент надо поискать
в рабочей директории программы Ini-файл со стандартным именем (например, plugins.ini) и перекачать данные из него в специальную структуру данных
в программе, созданием которой мы сейчас и займёмся.
typedef enum PluginTypeT
{ your_defined_types };
typedef enum InTypeT
{ your_defined_types };
typedef enum OutTypeT
{ your_defined_types };
typedef struct PluginDescrTag
{
AnsiString Path;
PluginTypeT Type
InTypeT InType;
OutTypeT OutType;
} PluginDescrT;
TList * PluginList;
А параллельно
с заполнением элементов структуры PluginDescrT
и закидыванием её в PluginList, надо ещё подсуетиться и создать сами элементы меню,
реализующие вызов Plug-in’ов.
Добавим в функцию Create(…) вызов следующей процедуры
(пример взят из моего проекта Tree Checker –
смотри формирующийся раздел по теории графов).
void TfmMain::LoadSearchItems(){
TIniFile *Ini=new
TIniFile(RootDir+"Plugins//Search//Plugin.ini");
SearchPlugins= new
TList;
TStringList *MenuItems=new
TStringList,
*Section=new TStringList;
//создаём объекты…
Ini->ReadSections(MenuItems);
//читаем названия секций Ini-файла….
for (int i=0; i<MenuItems->Count;
i++){
Section->Clear();
//чистим место для текущего блока
Ini->ReadSectionValues;
MenuItems->Strings[i],Section);
//..считываем значения параметров из текущего блока
MenuItemDescrT *Item=new
MenuItemDescrT; // создаём дескриптор Plug-in'а описанной
выше структуры…
Item->Path=Section->Values["Path"];
Item->Caption=MenuItems->Strings[i];
// заполняем поля структуры…
if (Section->Values["InType"]=="Pair")
Item->InType=PairTrees; // приведенные здесь типы были
использованы в проекте TreeChecker MDI
else if (Section->Values["InType"]=="Single")
Item->InType=SingleTree;
if (Section->Values["OutType"]=="Substitutions")
Item->OutType=Substitutions;
else if (Section->Values["OutType"]=="Text")
Item->OutType=TextInfo;
TMenuItem *NewItem=new
TMenuItem(this); //
создаем сам пункт меню….
NewItem->Caption=Item->Caption;
NewItem->OnClick=&SearchPluginClick;
// подключаем универсальный обработчик для плагинов
miRunSearch->Add(NewItem);
//включаем пункт меню в существующую структуру ( в данном случае
в подменю пункта
miRunSearch )
SearchPlugins->Add(Item);
// добавляем дескриптор Plug-in'а в список Plug-in'ов.
}
if (!MenuItems->Count)
miRunSearch->Enabled=false;
else miRunSearch->Enabled=true;
// если ничего так и не добавили, то деактивируем пункт меню
delete MenuItems;
delete Section;
// чистим память…….
}
Осталось рассказать о последнем, но
не менее важном аспекте. Это динамически подключаемый обработчик вызова
пункта меню, который подключается в вышеописанной функции загрузки данных
о Plug-in'ах. Приведу пример такого обработчика из того же проекта Tree
Checker.
void __fastcall TfmMain::PluginClick(TObject
*Sender){
STARTUPINFO StartUp={sizeof(STARTUPINFO),
NULL, NULL, NULL, 0, 0, 0, 0, 0, 0, 0,
STARTF_USESHOWWINDOW,
SW_HIDE, 0, NULL, NULL, NULL};
PROCESS_INFORMATION
Inform; // Заполняем структуры, необзрдимые
для порождения нового
//
процесса
int Num=miRunSearch->IndexOf((TMenuItem*)Sender);//
получаем номер пункта меню, вызвавшего
// обработчик..
bool ChoiceMade=false;
switch (((MenuItemDescrT*)(SearchPlugins->Items[Num]))->InType){
// в соответствии с типом Plug-in' а
case SingleTree: ChoiceMade=ChooseTrees(1,true, true);break;
// вызываем процедуру подготовки
case PairTrees: ChoiceMade=ChooseTrees(2,true, true);break;
// временного файла для работы Plug-in'а
};
RepType=((MenuItemDescrT*)(SearchPlugins->Items[Num]))->OutType;//читаем
тип выходных
// данных Plug-in'а
if (ChoiceMade){//
если корректно выбраны данные для работа Plug-in'а
Proc=SearchProc;//
Фиксируем тип порождаемого процесса…..
fmMain->ProcBeg=fmMain->ProcBeg.CurrentDateTime();//
и момент запуска для накопления статистики
fmMain->ProcessName=((MenuItemDescrT*)(SearchPlugins->Items[Num]))->Caption;
RunAllName=RootDir+"Plugins\\Search\\"+((MenuItemDescrT*)(SearchPlugins->
Items[Num]))->Path+" " +
ExtractShortPathName(TempFileName) + " "+ExtractShortPathName(ReportName);
//
формируем пути к Plug-in'у, данным и отчету
int res=CreateProcess(NULL,
RunAllName.c_str(), NULL, NULL, 0, REALTIME_PRIORITY_CLASS, NULL,
RootDir.c_str(), &StartUp,
&Inform);
if (res){//
если процесс корректно порождён……..
fmMain->miRunSearch->Enabled=false;
fmMain->miRunBatch->Enabled=false;
fmMain->miGenerate->Enabled=false;//
временно отключаем некоторые пункты меню
sbBar->AutoHint=false;
fmMain->hProcess=Inform.hProcess;//
сохраняем handler порожденного процесса…..
Application->OnIdle=&fmMain->IdleProc;//
подключаем обработчик OnIdle – подробнее смотри
//статью о контроле процессов - Coming Soon
fmMain->Processes++;
fmMain->sbBar->SimpleText=((MenuItemDescrT*)(SearchPlugins->Items[Num]))->Caption;
}
else{//
если не удалось породить процесс, то выдаем сообщение об ошибке.
LPTSTR Buf;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,
NULL, GetLastError(), 0, Buf,
0, NULL);
Application->MessageBox(*(char**)Buf,"Error",MB_OK);
}
}
}
Вот вроде и всё, что
необходимо для того, чтобы начать работать с Plug-in'ами. Вообще, всегда
на практике нет смысла дословно передирать дословно чьи-то сырцы, но
в качестве отправной точки ими имеет смысл пользоваться. Надеюсь, что
эта статья кому-то да поможет разрешить некоторые вопросы, связанные
с использованием Plug-in'ов. А так, думайте, читайте Help’ы и экспериментируйте. Буду
рад ознакомиться с вашим мнением, вашими разработками и идеями.
Пишите
сюда: bearoleg@online.ru