Информационный портал Media Systems & Bear Corp.

Главная Новости Delphi C&C++ Tеория Графов Web-Design Математика Исходники и Проекты Лисп и Пролог Ссылки

Портал :: Программирование на С и С++
Использование Plug-in'ов в программах

Во-первых, что такое 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  

                                                                                                                                                                Bear.  (28.02 2001)

     
  Гостевая книга . Связь с разработчиками: Bear Corporation, Media Studio.  
  Это место для вашей рекламы  

Дизайн: Bear Corner, Inc. & Media Sudio.
Последнее обновление: 24.03.2001.

Rambler's Top100 Rambler's Top100
Hosted by uCoz