Учебник
Теоретический материал.
Глава V
Библиотеки динамической компоновки DLL
Одной из особенностей Windows-приложений является их способность подключать во время работы необходимые функции и ресурсы, которые размещены в так назы- ваемых библиотеках динамической компоновки (Dynamic-Link Libraries, DLL). Все функции Windows содержатся в dll-файлах, например, графические функции раз- мещены в файле gdi32.dll. Преимущество использования библиотек динамиче- ской компоновки перед статическими библиотеками проявляется в том, что прило- жение, состоящее даже из нескольких модулей, использует лишь один экземпляр функции, тогда как из статической библиотеки каждый программный модуль при- соединяет свою копию функции на этапе компоновки. Рассмотрим подробно спо- собы создания динамических библиотек и их использование.Создание DLL
В среде Visual Studio создание новой DLL-библиотеки не представляет больших сложностей. Нужно при создании нового Win32-проекта выбрать тип DLL. Будет создан файл реализации dllmain.cpp, который может служить заготовкой новой DLL-библиотеки, а также установятся необходимые ключи компоновки:
#include "stdafx.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Следует обратить внимание на то, что имя головной функции фиксировано — DllMain(). Эта функция будет вызываться при загрузке и выгрузке DLL-библиотеки. Все, что нам пока требуется знать об этой функции, это то, что она должна вернуть TRUE при успешной загрузке библиотеки.
Далее в файле реализации проекта должны располагаться функции, которые мы хотели бы поместить в данную библиотеку.
Чтобы процесс создания DLL-библиотеки был более ясен, рассмотрим его на при- мере (листинг 5.1), в котором создается библиотека, состоящая всего лишь из од- ной функции, строящей треугольник. Поскольку пример небольшой, то приведем его полностью.
#include "stdafx.h"
declspec(dllexport) BOOL WINAPI Triangle(HDC, POINT*); BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved)
{
return TRUE; //переключатель switch нам пока не нужен
}
BOOL WINAPI Triangle(HDC hdc, POINT *pt)
{
MoveToEx(hdc, pt[0].x, pt[0].y,NULL);
LineTo(hdc, pt[1].x, pt[1].y);
LineTo(hdc, pt[2].x, pt[2].y);
return LineTo(hdc, pt[0].x, pt[0].y);
}
Листинг 5.1. Описание DLL-библиотеки (dllmain.cpp)
Функция Triangle() принимает контекст устройства hdc и массив из трех точек типа POINT. Треугольник строится вызовом стандартных функций MoveToEx() и LineTo(). В качестве возвращаемого значения используется значение, которое вер- нет последний вызов функции LineTo(). Разумно предположить, что если этот вы- зов был удачным, то и наша функция должна сообщить об удачном завершении. Спецификатор WINAPI означает стандартное соглашение Windows для порядка пе- редачи параметров GDI-функций.
Особый интерес вызывает описание прототипа функции:
declspec(dllexport) BOOL WINAPI Triangle(HDC, POINT*);
Директива
declspec(dllexport) используется компоновщиком для построения
раздела экспорта dll-файла.
ПРИМЕЧАНИЕПри обнаружении хотя бы одного экспортируемого элемента компоновщик кроме dll строит и lib-файл, структура которого отличается от соответствующего файла статической библиотеки. Здесь lib-файл содержит только имена экспортируемых функций и их адреса в библиотеке динамической компоновки. Функция Triangle() использует две стандартные функции, прототипы которых размещены в файле включений wingdi.h: WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT); WINGDIAPI BOOL WINAPI LineTo(HDC, int, int); Если посмотреть, что такое WINGDIAPI, то можно найти определение: #define WINGDIAPI DECLSPEC_IMPORT Отслеживая определение DECLSPEC_IMPORT, мы находим: #define DECLSPEC_IMPORT declspec(dllimport) Фактически перед функциями MoveToEx() и LineTo() расположен спецификатор declspec(dllimport). Он служит указанием компоновщику для создания раздела импорта, куда помеща- ются ссылки на эти функции.
DLL, как и EXE-файл, имеет несколько разделов, в частности разделы экспорта и им- порта, которые используются при его загрузке для поиска экспортируемых из библио- теки функций, а также функций, импортируемых из других библиотек.
Использование DLL
Итак мы создали DLL-библиотеку из одной функции, но это не принципиально — функций может быть сколько угодно много. Библиотеку нужно скомпилировать и убедиться, что папка проекта содержит lib- и dll-файлы. Теперь возникает вопрос, как все это применить? Существует два способа использования DLL-библиотек: неявное (implicit linking) и явное (explicit linking) связывание. Неявное связывание Неявное связывание (Implicit linking) — самый распространенный в настоящее время способ загрузки DLL-библиотек. Суть его заключается в том, что компонов- щик при построении исполняемого exe-файла не включает код функций в тело про- граммы, а создает раздел импорта, где перечисляются символические имена функ- ций и переменных для каждой из DLL-библиотек. Для этого к проекту необходимо подключить соответствующий lib-файл. При запуске программы загрузчик опера- ционной системы анализирует раздел импорта, загружает все необходимые DLL- библиотеки и, что важно, проецирует их на адресное пространство загружаемого приложения. Причем, если в загружаемой DLL-библиотеке существует свой раздел импорта, то загружаются и те dll-файлы, которые там указаны. Однако если биб- лиотека один раз уже загружена, то второй раз она не загружается, а строится ссылка на уже существующий экземпляр. Поиск DLL-библиотек осуществляется по стандартной схеме: В папке, откуда запущено приложение. В текущей папке. В системной папке Windows\system32. В папке Windows. В папках, которые перечислены в переменной окружения PATH. Если библиотека не найдена, загрузка приложения прекращается и выводится со- общение об ошибке. Посмотрим, как же будет загружаться наша DLL-библиотека (листинг 5.1). Поскольку она имеет раздел экспорта, то библиотека загружается в память, но функция Triangle() использует еще две стандартные функции, которые помещены в разделе импорта DLL-библиотеки. Если говорить точнее, то в нашей библиотеке хранятся не коды этих функций, а лишь ссылка на них из библиотеки gdi32.dll. Таким образом, при загрузке приложения, когда дело дойдет до загрузки DLL- библиотек, вначале загрузится библиотека Triangle.dll, затем будет загружена gdi32.dll. Эта библиотека, как правило, уже загружена, поскольку используется операционной системой, поэтому будут использованы ссылки на функции MoveToEx() и LineTo() из уже имеющейся в памяти библиотеки. Рассмотрим задачу для демонстрации разработанной нами библиотеки DLL (лис- тинг 5.2). Создадим новый проект, скопируем в папку проекта файлы Triangle.dll и Triangle.lib. Последний файл необходимо явно подключить к проекту, что можно сделать в меню Project | Properties… на вкладке Linker | Input с помощью диалогового окна Additional Dependencies (см. рис. 2.3).
WINGDIAPI BOOL WINAPI Triangle(HDC, POINT*);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;
static HPEN hPen; static int sx, sy; POINT pt[3];
switch (message)
{
case WM_CREATE:
hPen = CreatePen(PS_SOLID, 4, RGB(0, 0, 255));
break;
case WM_SIZE:
sx = LOWORD(lParam); sy = HIWORD(lParam); break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); SelectObject(hdc, hPen); pt[0].x = sx/4;
pt[0].y = pt[1].y = sy/4; pt[1].x = sx*3/4;
pt[2].x = sx/2;
pt[2].y = sy/2; Triangle(hdc, pt); EndPaint(hWnd, &ps); break;
case WM_DESTROY: DeleteObject(hPen); PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Листинг 5.2. Пример неявного связывания DLL-библиотеки
Для созданной нами DLL-библиотеки желательно подготовить заголовочный h-файл с прототипами функций. Однако можно описать прототип функции в тексте программы. Обратите внимание, что в качестве директивы компоновщика исполь- зуется WINGDIAPI, поскольку, как мы выяснили ранее, это то же самое, что
и declspec(dllimport).
Из файла Triangle.cpp мы приводим лишь оконную функцию, в которой опишем дескриптор пера hPen, две переменных целого типа для хранения размеров окна sx, sy, а также массив точек pt[3].
Сплошное синее перо толщиной 4 пиксела создадим в сообщении WM_CREATE:
hPen = CreatePen(PS_SOLID, 4, RGB(0, 0, 255));
В сообщении WM_SIZE определим размер клиентской области окна sx, sy.
Все рисование сосредоточим в сообщении WM_PAINT, где получим контекст устрой- ства и выберем для него перо:
SelectObject(hdc, hPen);
Теперь заполним поля массива pt для рисования треугольника в верхней части окна:
pt[0].x = sx/4;
pt[0].y = pt[1].y = sy/4; pt[1].x = sx*3/4;
pt[2].x = sx/2;
pt[2].y = sy/2;
Обращаемся к функции Triangle() как к обычной GDI-функции:
Triangle(hdc, pt);
Задача решена. Нужно не забыть уничтожить перо при закрытии окна.
После запуска программы будет выведено окно с изображением треугольника (рис. 5.1). При изменении размеров окна треугольник будет перестраиваться.
ПРИМЕЧАНИЕ
Если запустить созданный exe-файл в командной строке, то приложение не сможет выполниться, поскольку в текущей папке не будет DLL-библиотеки. Решением про- блемы может быть размещение библиотеки динамической компоновки в той же папке, где находится файл приложения, либо в одной из папок поиска DLL-библиотек.

Рис. 5.1. Графические построения с применением пользовательской DLL-библиотеки
DLL общего использования
Созданная нами DLL-библиотека работает с программами, написанными на языке С++. Если же попытаться использовать ее в программе, написанной на другом язы- ке программирования, мы выясним, что в ней отсутствует функция с именем Triangle. Но имеется функция ? Triangle@@YGHPAUHDC @@PAUtagPOINT@@@Z. Дело в том, что по соглашению языка С++ функция идентифицируется не только именем, но и типами формальных параметров, что называется сигнатурой функции. Нетрудно сообразить, что именно эта особенность С++ позволяет определять несколько функций с одним именем, но с разным числом и типом параметров. В нашей ситуации это является скорее помехой, поскольку вряд ли кому понравит- ся строить такие специфические имена функций. Однако когда приложение создается в С++, то имя так и строится, поэтому проблем с поиском функций не возникает. Другое дело, если приложение пишется на дру- гом языке, который "ничего не знает" о соглашениях С++. Одним из выходов в данной ситуации является использование спецификатора ком- поновки для классического языка C, где искажения имени функции не происходит: extern "язык" прототип функции Такая конструкция позволяет строить обращение к функции в соответствии со стандартом выбранного языка. Например, в нашем случае функцию можно было бы описать так:
extern "C" declspec(dllexport) BOOL Triangle(HDC, POINT*);
ПРИМЕЧАНИЕОбратите внимание, что в этом случае функция Triangle() использует стандарт- ный для классического С порядок передачи параметров. Если необходимо создать библиотеку функций обратного вызова CALLBACK, необходимо использовать иной подход. Более приемлемый путь решения данной задачи заключается в использовании def- файла (файла определений с расширением def — от англ. definition). Его можно до- бавить к проекту в меню Project | Add New Item… | Code | Module-Definition File (.def) (рис. 5.2).
К сожалению, имеющиеся в настоящее время компиляторы фирмы Microsoft поддер- живают спецификатор компоновки только для языка С.

Рис. 5.2. Добавление к проекту файла определений
Если при разработке проекта DLL-библиотеки включить в него def-файл с парой строк, которые представляют раздел экспорта:
EXPORTS
Triangle
то компоновщик помещает в раздел экспорта библиотеки функцию с именем Triangle без искажений. После этого библиотека станет доступной для использо- вания любому Windows-приложению.
ПРИМЕЧАНИЕ
Если поставить цель написать DLL-библиотеку общего использования, нужно всегда применять def-файл и только те типы данных, которые определены в Windows. Имя файла произвольно, лишь бы он был включен в проект.
Явная загрузка DLL
Неявное связывание, несмотря на свою очевидную простоту, имеет и определенные недостатки. Основным из них является одновременная (с приложением) загрузка и последующее удаление из памяти всех DLL-библиотек, независимо от того, что большая часть из них может и не понадобиться. Однако имеется возможность во время работы приложения загружать только необходимые DLL, освобождая зани- маемую ими память, когда потребность в них отпала. Такой способ называют явным связыванием (explicit linking). Каждое приложение имеет свое адресное пространство, и чтобы получить доступ к функциям и ресурсам, размещенным в DLL-библиотеке, нужно спроецировать библиотеку на адресное пространство приложения. Это можно сделать функцией LoadLibrary():
HMODULE WINAPI LoadLibraryW(LPCWSTR lpLibFileName);
аргументом которой является имя DLL-библиотеки.
Функция возвращает дескриптор спроецированного в память dll-файла.
HMODULE — тип возвращаемого значения, это просто другое обозначение дескрип- тора приложения HINSTANCE.
Функция FreeLibrary() выгружает DLL-библиотеку из памяти:
BOOL WINAPI FreeLibrary(HMODULE hLibModule);
Эта функция принимает дескриптор, полученный при загрузке DLL, и возвращает
TRUE при успешном завершении.
ПРИМЕЧАНИЕРассмотрим реализацию библиотеки для загрузки ее явно. Мы должны обеспечить неизменность имен функций, входящих в библиотеку. Этого проще всего добиться использованием def-файла. Добавим к проекту библиотеки def-файл и перекомпи- лируем проект. Создадим новый проект ExplicitLinking (листинг 5.3) для использования этой биб- лиотеки и скопируем в этот проект единственный файл Triangle.dll. При явной загрузке lib-файл нам не понадобится, и не нужно включать его в проект. Листинг 5.3. Явная загрузка DLL-функций
На самом деле функция FreeLibrary() лишь уменьшает счетчик пользователей библиотеки на 1, а выгружается библиотека из памяти тогда, когда счетчик пользова- телей становится равным 0.
typedef BOOL (WINAPI* PFN) (HDC, POINT*);
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPa- ram)
{
PAINTSTRUCT ps;
HDC hdc;
static HINSTANCE hLibrary; static PFN pfnTriangle; static HPEN hPen;
static int sx, sy; POINT pt[3];
switch (message)
{
case WM_CREATE:
hLibrary = LoadLibrary(_T("Triangle")); if (hLibrary)
{
pfnTriangle = (PFN)GetProcAddress(hLibrary, "Triangle"); if (pfnTriangle == NULL)
{
MessageBox(hWnd, _T("Функция Triangle не найдена"),
_T("LoadLibrary"), MB_OK | MB_ICONQUESTION); DestroyWindow(hWnd);
return 0;
}
hPen = CreatePen(PS_SOLID, 4, RGB(0, 0, 255));
}
else
{
MessageBox(hWnd, _T("Библиотека не найдена"), _T("LoadLibrary"), MB_OK | MB_ICONQUESTION);
DestroyWindow(hWnd); return 0;
}
break; case WM_SIZE:
sx = LOWORD(lParam); sy = HIWORD(lParam); break;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_EXIT: DestroyWindow(hWnd); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
break; case WM_PAINT:
hdc = BeginPaint(hWnd, &ps); SelectObject(hdc, hPen); pt[0].x = sx/4;
pt[0].y = pt[1].y = sy/4; pt[1].x = sx*3/4;
pt[2].x = sx/2;
pt[2].y = sy/2; pfnTriangle(hdc, pt); EndPaint(hWnd, &ps); break;
case WM_DESTROY: FreeLibrary(hLibrary); DeleteObject(hPen); PostQuitMessage(0); break;
default: return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
При создании окна в сообщении WM_CREATE загружаем библиотеку:
hLibrary = LoadLibrary(_T("Triangle"));
ПРИМЕЧАНИЕВозвращаемое значение сохраняем в переменной hLibrary типа HINSTANCE. В слу- чае успешной загрузки hLibrary отлично от нуля, и мы можем получить адрес библиотечной функции при помощи GetProcAddress():
В аргументе функции LoadLibrary() не следует указывать расширение имени dll, поскольку в этом случае поиск библиотеки будет осуществляться, начиная с каталога "известных DLL", прописанного в разделе KnownDLLs реестра Windows.
FARPROC WINAPI GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
Здесь второй параметр lpProcName — С-строка.
ПРИМЕЧАНИЕДля возвращаемого значения необходимо создать новый тип данных PFN — указа- тель на функцию с двумя параметрами. Воспользуемся оператором typedef и на глобальном уровне определим этот тип:
Адрес библиотечной функции отыскивается по ее имени, поэтому, даже после моди- фикации библиотеки, если имя и параметры функции не изменились, она будет най- дена. Кодировка Unicode в именах функций DLL-библиотеки не используется.
typedef BOOL (WINAPI* PFN) (HDC, POINT*);
Переменную pfnTriangle типа PFN опишем как статическую, это и будет указа- тель на функцию Triangle из динамической библиотеки. Сейчас ее можно исполь- зовать как имя функции, что мы и сделаем в сообщении WM_PAINT:
pfnTriangle(hdc, pt);
Директивой FreeLibrary(hLibrary) выгрузим библиотеку перед закрытием окна.
ПРИМЕЧАНИЕ
Функция LoadLibrary(), перед тем как загрузить библиотеку, проверяет наличие такой библиотеки в памяти, и, если библиотека уже загружена, просто увеличивает счетчик обращений к ней на 1 и отображает ее на адресное пространство приложения.
Загрузка ресурсов из DLL
Помимо функций из DLL-библиотеки можно загрузить и ресурсы. Для примера создадим простенькую DLL, содержащую в ресурсе одну иконку, и рассмотрим два способа ее извлечения при явном и неявном связывании. Создадим проект DLL-библиотеки и импортируем туда иконку IDI_ICON1.
#include "stdafx.h"
#include "resource.h"
declspec(dllexport) HICON hIcon;
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call, LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hIcon = LoadIcon(hModule, MAKEINTRESOURCE(IDI_ICON1)); break;
case DLL_PROCESS_DETACH:
DestroyIcon(hIcon); break;
}
return TRUE;
}
Листинг 5.4. Библиотека динамической компоновки, содержащая ресурсы
Программный код библиотеки не содержит ни одной функции, а только описание глобальной переменной hIcon, объявленной как "экспортируемая":
declspec(dllexport) HICON hIcon;
Функция DllMain(), которую называют функцией входа/выхода, вызывается операци- онной системой при загрузке и выгрузке DLL-библиотеки и имеет три параметра:
HModule — дескриптор библиотеки, присваивается при загрузке;
ul_reason_for_call — код уведомления;
lpReserved — зарезервировано для дальнейшего применения.
Код уведомления ul_reason_for_call может принимать одно из четырех значений:
DLL_PROCESS_ATTACH — при создании нового процесса;
DLL_PROCESS_DETACH — при завершении процесса;
DLL_THREAD_ATTACH — при создании нового потока;
DLL_THREAD_DETACH — при завершении потока.
Обычно при обработке этих уведомлений в DLL-библиотеке осуществляются опе- рации по выделению и освобождению необходимых для работы ресурсов операци- онной системы. Например, при загрузке библиотеки требуется выделить память, а при ее выгрузке — освободить.
Иконку можно загрузить из ресурса библиотеки функцией LoadIcon():
hIcon = LoadIcon(hModule, MAKEINTRESOURCE(IDI_ICON1));
Мы сохраняем в глобальной переменной дескриптор иконки, чтобы при выгрузке
библиотеки из памяти по уведомлению DLL_PROCESS_DETACH освободить память:
DestroyIcon(hIcon);
Поскольку потоков создавать не планируется, то уведомления DLL_THREAD_ATTACH
и DLL_THREAD_DETACH обрабатывать не будем. Рассмотрим два способа загрузки иконки:
Создадим проект демонстрационной задачи. В папку этого проекта скопируем созданные файлы библиотеки DllIcon.dll и DllIcon.lib.
В свойствах проекта добавим имя библиотеки DllIcon.lib. На глобальном уровне опишем импортируемую переменную:
declspec(dllimport) HICON hIcon;
а в сообщении WM_CREATE переопределим малую иконку класса окна:
SetClassLong(hWnd, GCL_HICONSM, (LONG)hIcon);
Поскольку окно прорисовывается после обработки этого сообщения, мы увидим в заголовке окна новую пиктограмму приложения.
Для явной загрузки библиотеки создадим новый проект, нам понадобится лишь dll-файл созданной библиотеки, подключать lib-файл не нужно. В сообщении WM_CREATE необходимо получить дескриптор библиотеки:
hDll = LoadLibrary(_T("DllIcon"));
передавая ей в качестве параметра имя DLL-файла. Далее, функцией GetProcAddress() находим дескриптор иконки, уже загруженной в библиотеке, передавая ей дескриптор иконки как текстовую строку.
hIcon = *((HICON*)GetProcAddress(hDll, "hIcon"));
Переменная hIcon описана как HICON.
После этого мы можем, как и в предыдущем случае, изменить малую иконку класса окна:
SetClassLong(hWnd, GCL_HICONSM, (LONG)hIcon);
Результат работы этого варианта программы будет идентичен предыдущему. lib- файл в проекте нам не нужен, но нужно иметь в виду, что DLL-библиотека должна быть создана с использованием def-файла:
EXPORTS
hIcon
ПРИМЕЧАНИЕ
В Visual Studio имеется специальная утилита dumpbin, которая позволяет просмот- реть содержимое разделов dll-файла. Протокол будет выглядеть так:
dumpbin /exports DllIcon.dll
Section contains the following exports for DllIcon.dll 00000000 characteristics
48F621AA time date stamp Wed Oct 15 23:00:26 2008
0.00 version
1 ordinal base
1 number of functions
1 number of names ordinal hint RVA name
1 0 00017138 hIcon
Весь набор ключей dumpbin можно получить командой: dumpbin /?. Нужно только иметь в виду, что для работы данной утилиты требуется, чтобы в пределах доступных путей поиска находились два модуля: link.exe, mspdb100.dll.
При использовании командного процессора Total — либо Windows Commander, можно вос- пользоваться стандартным просмотрщиком, который "умеет видеть" структуру dll-файла.
DLL, содержащие только ресурсы
Можно использовать DLL-библиотеку, как контейнер для хранения ресурсов, на- пример: курсоров, иконок и пр. В этом случае головная функция DllMain() ничего не делает и будет выглядеть так:
BOOL APIENTRY DllMain( HMODULE, DWORD, LPVOID)
{
return TRUE;
}
Поскольку в такой библиотеке не будет экспортируемых переменных, lib-файл не создается и библиотека может быть загружена только явно. Для доступа к ресурсам необходимо сохранить значение их идентификаторов, которые определены в файле resource.h, например:
#define IDI_ICON1 101
Эти определения лучше всего разместить в h-файле и поставлять вместе с dll- файлом.
Если создать такую библиотеку, то обработчик сообщения WM_CREATE мог бы вы- глядеть так:
HMODULE hDll;
HICON hIcon;
. . .
case WM_CREATE:
hDll = LoadLibrary(_T("DllIcon"));
hIcon = LoadIcon(hDll, MAKEINTRESOURCE(IDI_ICON1));
SetClassLong(hWnd, GCL_HICONSM, (LONG)hIcon); break;
Вопросы к главе
В чем отличие библиотек динамической компоновки от статических библиотек? Какие действия выполняет функция DllMain(), коды уведомлений? Объявление экспортируемых переменных и функций. Проблема "искажения" имен, спецификатор компоновки extern "C" и def-файл. Явное и неявное связывание DLL-библиотек. Пути поиска dll-файла.
Формат функций LoadLibrary(), GetProcAddress(), FreeLibrary().
Как найти адрес функции и переменной в DLL-библиотеке?
Как создать DLL-библиотеку для хранения ресурсов?