15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Советы по использованию 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):

  1. Скопируйте AAA.def в AAAD.def и измените все AAA на AAAD;
  2. В Project/Settings, установите Win32 Debug
  3. В закладке Link измените Output file name на AAAD.DLL;
  4. Изначальны Вы можете наблюдать на этой закладке:
    /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 //MUDASDASDASDASDAS

Программирование с изменениями

Чтобы использовать класс, содержащийся в 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.