Програмування на Visual C + +, Різне, Програмування, статті

У цій статті описуються принципи і рішення, застосовувані при проектуванні додатків, які будуть використовувати зовнішні, спільні, модулі. Ця стаття більш орієнтована на тих, хто хоче використовувати механізми підключення / відключення функціональності програми, на зразок механізму Aobe Photoshop або Far, а не просто багаторазового використання коду в різних додатках.

Спільні модулі (DLL) – це модулі, які містять функції і дані. Ці модулі завантажуються під час виконання програми, що використовує ці модулі (хоста). В ОС Windows модулі містять внутрішні і експортовані функції (в UNIX подібних системах всі функції є експортованими). Експортовані функції доступні для виклику хостом, а внутрішні немає. Хоча дані теж можуть бути експортованими, але зазвичай використовуються експортовані функції для доступу даним.


Деякі, особливо початківці розробники ПЗ, і не уявляють, що при створенні програми, вже використовують зовнішні модулі. Хоча при розробці MFC програм цей факт більш очевидний. Просто компілятор сам вставляє код, який завантажує системні бібліотеки, інакше будь Windows додаток було б на 20-30 Мб більше.


Отже, перейдемо безпосередньо до створення механізму для використання у Ваших додатках плагинов.


Створіть нове DLL додаток (Builder і VC дозволяють вибрати тип при створенні нового проекту).


Кожна бібліотека має точку входу (але можна її і не описувати), як функція main () в звичайному додатку. Ось звичайне її опис:

HINSTANCE hDllInstance=NULL;
BOOL APIENTRY DllMain (HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH)

hDllInstance = (HINSTANCE)hModule;

return TRUE;
}


При приєднанні до викликав її процесу, в цю функцію передається instance. Тобто Ви зможете використовувати ресурси бібліотеки. Використовуйте в цій функції тільки прості завдання по ініціалізації! Ця функція дуже вразливе місце в модулі.


Для зв'язку хоста і модуля пропоную використовувати дві функції: перша функція буде взаємодіяти з хостом при ініціалізації (визначати версію, сумісність з поточною версією хоста, передавати текст для пункту в меню і т.д.), а друга буде обслуговувати запити хоста і реагувати на передані дані: аналізувати, обробляти і повертати результат соєю роботи.


Інший підхід у написанні плагінів – використовувати декілька різних функцій, для кожної дії, але особисто мені подобається перший.


Наприклад, один з можливих варіантів цих двох функцій:

#ifdef __cplusplus
extern “C” {
#endif

__declspec (dllexport) void GetPluginInfo (PluginInfo * pPluginInfo,
DWORD *pdwResult);
__declspec(dllexport) void PluginHandler(DWORD dwCode,
HostInfo *pHostInfo,
DWORD *pdwResult);

#ifdef __cplusplus
}
#endif


Ключові слова __declspec (dllexport) позначають, що функції є експортованими.


Цей блок визначень можна поміщати в самому початковому файлі, після секції підключення заголовних файлів.


Ось приклад структури, що передається при ініціалізації:

struct PluginInfo {
/ / Вид виконуваної плагином операції
/ / (Якщо передбачається декілька типів плагинов)
DWORD m_dwPluginType;
/ / Передамо пункт меню для нашого плагина
char * m_pcMenuString;
unused[64];
};

А ось приклад функції, яка знаходиться в плагіні і заповнює цю структуру:

 void GetPluginInfo (PluginInfo * pPluginInfo, DWORD * pdwResult) {

pPluginInfo->m_dwPluginType=5;
pPluginInfo-> m_pcMenuString = "/ Мій плагін";
*pdwResult=0;
}


Функція-обробник в плагіні:

 void PluginHandler (DWORD dwCode, HostInfo * pHostInfo, DWORD * pdwResult);

Перший параметр визначатиме мету, з якою викликана функція, другий параметр визначає вхідні дані, причому для кожного коду можуть бути ініціалізовані різні члени структури. Ну а третій параметр – Результат роботи.

 void PluginHandler (DWORD dwCode, HostInfo * pHostInfo, DWORD * pdwResult) {

switch(dwCode) {
case 1:
/ / Перша дія
*pdwResult=1;
break;
case 2:
/ / Друга дія
*pdwResult=1;
break;
default: *pdwResult=0;
}
}


Якщо плагін не знає переданого коду операції, він просто поверне код "Не підтримується" і не виконає некоректних дій.


Подивимося, які дії необхідно виконати в програмі-хості для того, щоб скористатися функціями, розташованих в плагінах.


Завантажимо бібліотеку хостом:

 / / Доводиться створювати тип для кожної експортованої функції
typedef void (*GetPluginInfoType)(PluginInfo*);
typedef void (*PluginHandlerType)(HostInfo*);

HMODULE hLib=LoadLibrary(“MyLib.dll”);
if (hLib==NULL) {
/ / Тут обробляємо помилку, якщо бібліотека не завантажилася
return FALSE;
}

GetPluginInfoType GetPluginInfo;
GetPluginInfo = (GetPluginInfoType) GetProcAddress (m_hInstance, "GetPluginInfo");
if (GetPluginInfo==NULL) {
FreeLibrary(hLib);
return FALSE;
}
DWORD dwResult;
PluginInfo PluginNfo;
memset(&PluginNfo,0,sizeof(PluginInfo));
GetPluginInfo(&PluginNfo,&dwResult);
/ / Тут аналізуємо заповнену в плагіні структуру
/ / (Створюємо меню для плагина, резервуємо пам'ять і т.д.)


Отже, плагін завантажений і готовий до роботи, очікуємо коли користувач вибере пункт меню.


Розміщуємо в обробник меню наступний код:

PluginHandlerType PluginHandler;
/ / Отримуємо адресу функції обробника в плагіні
PluginHandler = (PluginHandlerType) GetProcAddress (m_hInstance, "PluginHandler");
if (PluginHandler==NULL) return FALSE;
/ / Готуємо структури з даними,
/ / Які необхідно передати в плагин для обробки
HostInfo HostNfo;
memset(&HostNfo,0,sizeof(HostInfo));
/ / Якщо в плагінах будуть створюватися вікна,
/ / То необхідно передати HWND головного вікна в якості батьківського
HostInfo.m_hHostWnd=theApp->m_pMainWnd->GetSafeHwnd();
/ / Передаємо адреси функцій, реалізованих в хості
HostInfo.IPShowProgress=::ShowProgress;
DWORD dwResult;
try {/ / бажано поставити обробник виключень
/ / Викликаємо функцію-обробник в плагіні
PluginHandler(1,&HostNfo,&dwResult);
} catch(…)
{
AfxMessageBox ("У модулі відбулася необроблювана помилка.");
ASSERT(0);
return FALSE;
}
/ / Не забувайте вивантажувати бібліотеки після закінчення роботи хостом
FreeLibrary(hLib);

Ось власне і весь опис простого прикладу використання плагинов в своїх програмах.


Я хочу додати кілька порад для розробників:



А ось приклад функції, яка рекурсивно знаходить всі файли в папці з плагінами:

 / / Масив з усіма файлами, включаючи шлях щодо теки з плагінами
CStringArray PluginsArray;
/ / Задається шлях до теки з плагінами
CString sPlugInsPath=”Plugins\”;
void GetPlugInFiles(CString sPath) {
if (PluginsArray.GetSize()>=512) return;
CString sStr;
CString sCurFullPath=sPlugInsPath;
sCurFullPath+=sPath;
sCurFullPath+=”*”;
WIN32_FIND_DATA FindData;
HANDLE hFindFiles=FindFirstFile(sCurFullPath,&FindData);
if (hFindFiles==INVALID_HANDLE_VALUE) return;
for(;;) {
if ((strcmp (FindData.cFileName ,".")!= 0) & & (strcmp (FindData.cFileName ,"..")!= 0)) {
if (FindData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) {
sStr=sPath;
sStr+=FindData.cFileName;
sStr+=”\”;
GetPlugInFiles(sStr);
}
else {
char *ptr=strrchr(FindData.cFileName,.);
if (ptr) {
if (strlen(ptr)==4) {
if (ptr[1]==x && ptr[2]==x && ptr[3]==x) {
CString sPath1=sPath;
sPath1+=FindData.cFileName;
PluginsArray.Add(sPath1);
}
}
}
}
}
if (!FindNextFile(hFindFiles,&FindData)) break;
}
FindClose(hFindFiles);
}

/ / Так викликається функція. Після такого виклику масив
/ / PluginsArray буде заповнений
GetPlugInFiles(“”);


Покрашенко Олександр

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*