DLL - Как писать

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

Типы DLL

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

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

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

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

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

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

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

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

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

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

Точка входа в 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 функция, когда процесс завершается.

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

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

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

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

__declspec(dllexport)

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

__declspec(dllimport)

Обычно используется один файл заголовка, имеющий define заявление и 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.

// 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 в DLL SampleDLL.

// SampleApp.cpp 

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

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

NOTE - При динамической компоновке во время загрузки необходимо связать библиотеку импорта 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 ищет DLL SampleDLL в следующих местах в указанном порядке:

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

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

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

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