Советы по использованию Dynamic Link
Libraries (DLLs) в MFC
Автор: xicoloko.
Этот документ поможет начинающим
программистам получить некоторые знания по
разработке приложений для Windows. Когда я создаю
новый проект, то наиболее изменяемые куски
программы стараюсь поместить в DLL. Это даёт мне
возможность изменять уже откомпилированную
программу.
Чтобы сделать это грамотно, дам Вам несколько
советов. Многи могут спросить, а почему бы просто
не использовать технологию COM ?. Ответ очевиден,
конечно же COM является наиболее частым выбором в
таких ситуациях. Однако, DLL это одна из живучих
альтернатив для решения таких задач. Я
постараюсь объяснить использование технологии
DLL в рамках приложения под MFC.
Одна из главных проблемм использования DLL
(особенно в MFC) кроется в отладочной и релизной
версиях. Эти версии не совместимы. Дело в том, что
проблемма возникает при запуске отладочной
версии Вашего приложения с DLL релизной версии. В
таком случае Microsoft советует давать разные имена
DLL (для релизной и отладочной версий). Итак
релизная версия DLL остаётся в проекте с прежним
именем, а отладочная версия должна выглядеть
примерно так [Project Name]D.DLL. После этого Вы можете
скопировать оба варианта DLL-ки в системную
директорию, и быть счастливы :).
Вот несколько шагов по созданию такого
приложения (имя проекта AAA):
- Скопируйте AAA.def в AAAD.def и измените все AAA на AAAD;
- В Project/Settings, установите Win32 Debug
- В закладке Link измените Output file name на AAAD.DLL;
- Изначальны Вы можете наблюдать на этой
закладке:
/def:".\AAA.def" /out:"Debug/AAAD.DLL"
Измените на:
/def:".\AAAD.def" /out:"Debug/AAAD.DLL"
Теперь отладочная версия создаст файлы AAAD.lib и
AAAD.DLL. Когда я создаю DLL, я создаю для него
заголовок (include header) (думаю, что все так делают),
который называю DLL header. Этот заголовок содержит
объявления всех экспортируемых классов. Для
большей эффективности я включаю "linking stuff" в
него, теперь, чтобы использовать DLL вам не нужно
добавлять lib файл в Project Settings. Мой header выглядит
примерно так:
#ifndef DEF_MUDASDASDASDASDAS
#define DEF_MUDASDASDASDASDAS
#ifdef _DEBUG
#pragma comment(lib, AAAD.lib)
#else
#pragma comment(lib, AAA.lib)
#endif
#endif
Программирование с изменениями
Чтобы использовать класс, содержащийся в DLL,
достаточно объявить в Вашем проекте следующее:
class AFX_EXT_CLASS CFoo
{
}
В приложении, использующем этот класс,
необходимо включить хедер DLL. Проблемма: Вам всё
время прийдётся включать переменные-члены или
методы в экспортированный класс, а для этого
необходимо изменять DLL header, а это значит, что
прийдётся перекомпилировать все модули,
использующие DLL. Для новых методов я не знаю
способа преодоления этой проблеммы, но для новых
переменных такой способ есть.
Вместо того, чтобы объявлять члены класса
непосредственно в теле класса, вы создаёте класс
с указателем на включаемый класс:
class CFooImpl;
class CFoo
{
protected:
CFooImpl* m_pThis;
};
Теперь класс CFooImpl не требует объявления, чтобы
использовать его из DLL. Функции-члены класса CFoo
выглядят примерно так:
class CFooImpl
{
public:
CString m_sName;
};
CFoo::CFoo()
{
m_pThis = new CFooImpl;
m_pThis->m_sName = _T("Unknown");
}
CFoo::~CFoo()
{
delete m_pThis;
}
Другой способ - это использовать специальных
структур Windows API. Вам нужно объявить метод,
который использует в качестве входных и выходных
параметров LPVOID. Данные указатели - это адреса
экземпляров структур. Этот приём определяет
первого члена рассмотренной структуры - DWORD,
который является размером.
typedef struct tagCHANGEABLE
{
DWORD dwSize;
long lBytes;
}CHANGEABLE, *LPCHANGEABLE;
BOOL CFoo::Method(LPVOID lpIn)
{
LPCHANGEABLE lpChangeable = (LPCHANGEABLE)lpIn;
if (lpChangeable->dwSize == sizeof(CHANGEABLE))
{
return TRUE;
}
return FALSE;
}
Используем его:
CFoo myFoo;
CHANGEABLE changeable;
memset(&changeable, 0, sizeof(changeable));
changeable.dwSize = sizeof(changeable);
myFoo.Method(&changeable);
DLL загружается при необходимости
Иногда возникает ситуация, когда Вам нужно
вызвать диалог или инициализировать класс. Тогда
Вы решаете поместить его в DLL, но Вы не хотите,
чтобы он загружался во время выполнения
приложения. Вы хотите загрузить DLL только при
необходимости (COM). Это тип DLL я называю Dynamic DLL (масло
маслянное “Dynamic Dynamic link libraries”). Итак, Вы
объявляете экспортируемую функцию как:
__declspec( DLLexport )
void MyExportedFunc(DWORD dw)
{
}
Необходимо включить эту функцию в файлы .def
(debug и release). Отладочный def-файл будет выглядеть
примерно так:
; AAAD.def : Объявление параметров модуля для DLL.
LIBRARY "AAAD"
DESCRIPTION 'AAAD Windows Dynamic Link Library'
EXPORTS
MyExportedFunc @1
; Explicit exports can go here
Теперь, чтобы использовать эту функцию,
необходимо загрузить библиотеку, найдите точку
входа в функцию и вызовите её.
typedef void (*MYFUNC)(DWORD);
#ifdef _DEBUG
HINSTANCE hDLL = AfxLoadLibrary("AAADLLD");
#else
HINSTANCE hDLL = AfxLoadLibrary("AAADLL");
#endif
if (hDLL)
{
FARPROC pnProc = GetProcAddress(hDLL, "MyExportedFunc");
MYFUNC pnMyfunc = (MYFUNC)pnProc;
pnMyfunc(0);
FreeLibrary(hDLL);
}
Не забудьте, что для того, чтобы показать диалог,
Вам необходимо позаботиться о необходимых
ресурсах (AfxSetResource..). Такой подход позволяет
создать копии класса. В определении класса
должны использоваться только виртуальные
функции (чтобы избежать "unresolved external symbol").
Это примерно также как в COM.
Объявление класса будет выглядеть так:
class CFoo
{
public:
virtual void Initialize (CString sName) = 0;
};
Включаем этот "interface" в другой класс, не
видимый через заголовочный файл DLL.
class CFooImp : public CFoo
{
public:
CFooImp();
virtual ~CFooImp();
void Initialize (CString sName)
{
m_sName = sName;
}
protected:
CString m_sName;
};
Чтобы создать образец класса (интерфейс) вам
необходимо создать экспортированную функцию.
__declspec(DLLexport)
CFoo* CreateFoo(DWORD dwVersion)
{
if (dwVersion == CURRENT_VERSION)
return new CFooImp;
return NULL;
}
Приложение инициализирует класс следующим
образом:
typedef CFoo* (*MYFUNC)(DWORD);
#ifdef _DEBUG
HINSTANCE hDLL = AfxLoadLibrary("AAADLLD");
#else
HINSTANCE hDLL = AfxLoadLibrary("AAADLL");
#endif
if (hDLL)
{
FARPROC pnProc = GetProcAddress(hDLL, " CreateFoo");
MYFUNC pnMyfunc = (MYFUNC)pnProc;
CFoo* pFoo = pnMyfunc(0);
pFoo->Initialize(_T("Hi"));
delete pFoo;
FreeLibrary(hDLL);
}
Не забудьте, что Вы не можете освободить
библиотеку до тех пор пока Вы не удалите объект
класса CFoo.
|