Учебник

Теоретический материал.

Глава 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, как и EXE-файл, имеет несколько разделов, в частности разделы экспорта и им- порта, которые используются при его загрузке для поиска экспортируемых из библио- теки функций, а также функций, импортируемых из других библиотек.
    При обнаружении хотя бы одного экспортируемого элемента компоновщик кроме 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

    Итак мы создали 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*);
    				
    ПРИМЕЧАНИЕ
    К сожалению, имеющиеся в настоящее время компиляторы фирмы Microsoft поддер- живают спецификатор компоновки только для языка С.
    Обратите внимание, что в этом случае функция Triangle() использует стандарт- ный для классического С порядок передачи параметров. Если необходимо создать библиотеку функций обратного вызова CALLBACK, необходимо использовать иной подход. Более приемлемый путь решения данной задачи заключается в использовании def- файла (файла определений с расширением def — от англ. definition). Его можно до- бавить к проекту в меню Project | Add New Item… | Code | Module-Definition File (.def) (рис. 5.2).

    Рис. 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 при успешном завершении.
    ПРИМЕЧАНИЕ
    На самом деле функция FreeLibrary() лишь уменьшает счетчик пользователей библиотеки на 1, а выгружается библиотека из памяти тогда, когда счетчик пользова- телей становится равным 0.
    Рассмотрим реализацию библиотеки для загрузки ее явно. Мы должны обеспечить неизменность имен функций, входящих в библиотеку. Этого проще всего добиться использованием def-файла. Добавим к проекту библиотеки def-файл и перекомпи- лируем проект. Создадим новый проект ExplicitLinking (листинг 5.3) для использования этой биб- лиотеки и скопируем в этот проект единственный файл Triangle.dll. При явной загрузке lib-файл нам не понадобится, и не нужно включать его в проект. Листинг 5.3. Явная загрузка DLL-функций
    
    
    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"));
    				
    ПРИМЕЧАНИЕ
    В аргументе функции LoadLibrary() не следует указывать расширение имени dll, поскольку в этом случае поиск библиотеки будет осуществляться, начиная с каталога "известных DLL", прописанного в разделе KnownDLLs реестра Windows.
    Возвращаемое значение сохраняем в переменной hLibrary типа HINSTANCE. В слу- чае успешной загрузки hLibrary отлично от нуля, и мы можем получить адрес библиотечной функции при помощи GetProcAddress():
    
    FARPROC WINAPI GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
    				
    Здесь второй параметр lpProcName — С-строка.
    ПРИМЕЧАНИЕ
    Адрес библиотечной функции отыскивается по ее имени, поэтому, даже после моди- фикации библиотеки, если имя и параметры функции не изменились, она будет най- дена. Кодировка Unicode в именах функций DLL-библиотеки не используется.
    Для возвращаемого значения необходимо создать новый тип данных PFN — указа- тель на функцию с двумя параметрами. Воспользуемся оператором typedef и на глобальном уровне определим этот тип:
    
    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-библиотеку для хранения ресурсов?
  • Задания для самостоятельной работы

    Создайте DLL-библиотеку UserString для работы с С-строкой, включив в нее аналоги стандартных функций: strlen(), strcpy(), strcat(), strrev()… Постройте демонстрационную задачу для использования созданной библиотеки при неявном и явном связывании. Создайте библиотеку, содержащую набор ресурсов: иконку, курсор, растровое изображение. Постройте демонстрационную задачу, использующую ресурсы этой DLL-библиотеки. Создайте DLL-библиотеку с галереей рисунков одинакового размера. В число экспортируемых переменных включите название галереи и количество изобра- жений. Окно создаваемого приложения заполните этими рисунками. При помощи утилиты dumpbin просмотрите разделы экспорта и импорта создан- ных библиотек.