Учебники

DLL — Как написать

Сначала мы обсудим проблемы и требования, которые вы должны учитывать при разработке своих собственных библиотек DLL.

Типы DLL

Когда вы загружаете DLL в приложение, два метода связывания позволяют вам вызывать экспортированные функции DLL. Два метода связывания —

  • динамическое связывание во время загрузки и
  • динамическое связывание во время выполнения.

Динамическое связывание во время загрузки

При динамическом связывании во время загрузки приложение выполняет явные вызовы экспортируемых функций DLL, таких как локальные функции. Чтобы использовать динамическое связывание во время загрузки, предоставьте файл заголовка (.h) и файл библиотеки импорта (.lib) при компиляции и компоновке приложения. Когда вы сделаете это, компоновщик предоставит системе информацию, необходимую для загрузки DLL, и разрешит расположение экспортированных функций DLL во время загрузки.

Динамическое связывание во время выполнения

При динамическом связывании во время выполнения приложение вызывает либо функцию LoadLibrary, либо функцию LoadLibraryEx, чтобы загрузить DLL во время выполнения. После успешной загрузки DLL вы используете функцию GetProcAddress, чтобы получить адрес экспортированной функции DLL, которую вы хотите вызвать. Когда вы используете динамическое связывание во время выполнения, вам не нужен файл библиотеки импорта.

В следующем списке описаны критерии приложения для выбора между динамическим связыванием во время загрузки и динамическим связыванием во время выполнения.

  • Производительность при запуске. Если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.

  • Простота использования — при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.

  • Логика приложения. При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.

Производительность при запуске. Если важна начальная производительность при запуске приложения, следует использовать динамическое связывание во время выполнения.

Простота использования — при динамическом связывании во время загрузки экспортированные функции DLL похожи на локальные функции. Это помогает вам легко вызывать эти функции.

Логика приложения. При динамическом связывании во время выполнения приложение может выполнять ветвление для загрузки различных модулей по мере необходимости. Это важно при разработке многоязычных версий.

Точка входа в DLL

Когда вы создаете DLL, вы можете дополнительно указать функцию точки входа. Функция точки входа вызывается, когда процессы или потоки присоединяются к DLL или отсоединяются от DLL. Вы можете использовать функцию точки входа для инициализации или уничтожения структур данных в соответствии с требованиями DLL.

Кроме того, если приложение является многопоточным, вы можете использовать локальное хранилище потоков (TLS) для выделения памяти, которая является частной для каждого потока в функции точки входа. Следующий код является примером функции точки входа DLL.

BOOL APIENTRY DllMain(
   HANDLE hModule,	   // Handle to DLL module 
   DWORD ul_reason_for_call, 
   LPVOID lpReserved )     // Reserved
{
   switch ( ul_reason_for_call )
   {
      case DLL_PROCESS_ATTACHED:
      // A process is loading the DLL.
      break;
      
      case DLL_THREAD_ATTACHED:
      // A process is creating a new thread.
      break;
      
      case DLL_THREAD_DETACH:
      // A thread exits normally.
      break;
      
      case DLL_PROCESS_DETACH:
      // A process unloads the DLL.
      break;
   }
   return TRUE;
}

Когда функция точки входа возвращает значение FALSE, приложение не запускается, если вы используете динамическое связывание во время загрузки. Если вы используете динамическое связывание во время выполнения, не будет загружаться только отдельная DLL.

Функция точки входа должна выполнять только простые задачи инициализации и не должна вызывать какие-либо другие функции загрузки или завершения DLL. Например, в функции точки входа не следует прямо или косвенно вызывать функцию LoadLibrary или функцию LoadLibraryEx . Кроме того, вы не должны вызывать функцию FreeLibrary, когда процесс завершается.

ВНИМАНИЕ — В многопоточных приложениях убедитесь, что доступ к глобальным данным DLL синхронизирован (потокобезопасен), чтобы избежать возможного повреждения данных. Для этого используйте TLS, чтобы предоставить уникальные данные для каждого потока.

Экспорт функций DLL

Чтобы экспортировать функции DLL, вы можете добавить ключевое слово функции в экспортированные функции DLL или создать файл определения модуля (.def), в котором перечислены экспортированные функции DLL.

Чтобы использовать ключевое слово функции, вы должны объявить каждую функцию, которую вы хотите экспортировать, со следующим ключевым словом —

__declspec(dllexport)

Чтобы использовать экспортированные функции DLL в приложении, вы должны объявить каждую функцию, которую вы хотите импортировать, с помощью следующего ключевого слова —

__declspec(dllimport)

Как правило, вы использовали бы один заголовочный файл, имеющий оператор определения и оператор ifdef, чтобы разделить оператор экспорта и оператор импорта.

Вы также можете использовать файл определения модуля для объявления экспортированных функций DLL. Когда вы используете файл определения модуля, вам не нужно добавлять ключевое слово function к экспортируемым функциям DLL. В файле определения модуля вы объявляете оператор LIBRARY и оператор EXPORTS для DLL. Следующий код является примером файла определения.

// SampleDLL.def
//
LIBRARY "sampleDLL"

EXPORTS
   HelloWorld

Написать пример DLL

В Microsoft Visual C ++ 6.0 вы можете создать DLL, выбрав либо тип проекта Win32 Dynamic-Link Library, либо тип проекта MFC AppWizard (dll) .

Следующий код представляет собой пример библиотеки DLL, созданной в Visual C ++ с использованием типа проекта Win32 Dynamic-Link Library.

// SampleDLL.cpp

#include "stdafx.h"
#define EXPORTING_DLL
#include "sampleDLL.h"

BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved )
{
   return TRUE;
}

void HelloWorld()
{
   MessageBox( NULL, TEXT("Hello World"), 
   TEXT("In a DLL"), MB_OK);
}
// File: SampleDLL.h
//
#ifndef INDLL_H
#define INDLL_H

   #ifdef EXPORTING_DLL
      extern __declspec(dllexport) void HelloWorld() ;
   #else
      extern __declspec(dllimport) void HelloWorld() ;
   #endif

#endif

Вызов примера библиотеки DLL

Следующий код является примером проекта приложения Win32, который вызывает экспортированную функцию DLL в библиотеке SampleDLL.

// SampleApp.cpp 

#include "stdafx.h"
#include "sampleDLL.h"

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{ 	
   HelloWorld();
   return 0;
}

ПРИМЕЧАНИЕ. — При динамическом связывании во время загрузки необходимо связать библиотеку импорта SampleDLL.lib, которая создается при сборке проекта SampleDLL.

При динамическом связывании во время выполнения вы используете код, подобный следующему коду, для вызова экспортированной функции DLL SampleDLL.dll.

...
typedef VOID (*DLLPROC) (LPTSTR);
...
HINSTANCE hinstDLL;
DLLPROC HelloWorld;
BOOL fFreeDLL;

hinstDLL = LoadLibrary("sampleDLL.dll");

if (hinstDLL != NULL)
{
   HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld");
	
   if (HelloWorld != NULL)
      (HelloWorld);

   fFreeDLL = FreeLibrary(hinstDLL);
}
...

Когда вы компилируете и связываете приложение SampleDLL, операционная система Windows ищет библиотеку SampleDLL в следующих местах в следующем порядке:

Папка приложения

Текущая папка

Системная папка Windows (функция GetSystemDirectory возвращает путь к системной папке Windows).

Папка Windows (функция GetWindowsDirectory возвращает путь к папке Windows).