- •Лекция 11
- •Понятие DLL
- •DLL — это библиотека, содержащая код и данные, которые могут использоваться более чем
- •Некоторые из файлов, которые реализованы в виде библиотеки DLL в операционных системах Windows:
- •Преимущества DLL
- •Динамическое связывание во время загрузки
- •Когда система начинает программу, которая использует выполняемую при загрузке динамическую связь, используется информация
- •Если SafeDllSearchMode равно 1, порядок поиска происходит как указано ниже:
- •Динамическое связывание во время выполнения
- •В обращении к функции GetProcAddress следует указывать адрес подпрограммы, которую необходимо задействовать. Допускается
- •Создание динамически компонуемых библиотек
- •Функция AddNumbers определена как экспортируемая (с точки зрения DLL), и ей присвоен порядковый
- •Приложения, реализующие динамическое связывание с DLL во время загрузки, должны включать этот файл,
- •Если DLL не сопровождается библиотекой импорта, для информирования модуля подключения о местонахождении подпрограмм
- •Пример создания простой DLL
- •Использование динамического связывания выполняемого при загрузке
- •Использование динамического связывания периода выполнения
- •//Простая программа, которая использует функции LoadLibrary и GetProcAddress,
- •Доступ к данным в DLL
- •Для файла описания модуля возможно изменение стандартных атрибутов глобальных и статических данных, что
- •Использование уведомляющей точки входа DLL
- •Описание функций динамически
- •GetModuleFileName
Создание динамически компонуемых библиотек
Процесс создания DLL во многом подобен созданию обычных исполняемых программ Windows. Исходный код DLL идентичен исходному коду любого исполняемого модуля. Ниже показан исходный код очень простой DLL-библиотеки.
#include <windows.h> // AddNumbers — это подпрограмма DLL, // которую приложение может вызывать.
int AddNumbers( int nl, int n2 ) { return ( nl + n2 );
}
Этот же код может служить подпрограммой обычного приложения. Различия существуют в файле описания модуля (.DEF). Модуль подключения использует файл описания модуля с целью предоставления информации, необходимой для создания DLL. Следующий фрагмент кода иллюстрирует минимальный файл описания модуля для представленного выше примера.
LIBRARY MyDll
EXPORTS
AddNumbers @1
Функция AddNumbers определена как экспортируемая (с точки зрения DLL), и ей присвоен порядковый номер 1. Задавать порядковый номер необязательно. Когда номер опущен, он присваивается компоновщиком автоматически. Если файл объекта, созданный из предыдущего примера исходного кода, связать с данным файлом описания модуля, получится динамически компонуемая библиотека.
При условии, что для доступа к этой DLL приложение будет использовать динамическое связывание во время выполнения, процесс создания можно считать завершенным. Если планируется использование динамическое связывание во время загрузки, потребуется реализовать еще несколько задач.
Поскольку динамическое связывание во время загрузки требует разрешения всех ссылок на этапе компиляции/компоновки, сначала необходимо создать файл заголовков (.Н), который описывает подпрограммы из DLL. Обычно файл заголовков содержит описания прототипов всех общедоступных точек входа в DLL, а также любые структуры и определения. Для предыдущего фрагмента кода файл заголовков имеет следующий вид:
// Файл заголовков для MYDLL.DLL — описывает точки входа int AddNumbers (int nl, int n2) ;
Приложения, реализующие динамическое связывание с DLL во время загрузки, должны включать этот файл, чтобы иметь описание подпрограммы AddNumbers. Для компилятора этого достаточно. Однако компоновщику необходимо знать местонахождение подпрограммы. Обычно это осуществляется путем связывания приложения с библиотекой импорта (import library), созданной из файла описания модуля.
Библиотека импорта указывает компоновщику, что подпрограмма
AddNumbers находится в файле MYDLL.DLL.
Многие платформы разработки создают библиотеки импорта автоматически. Если же требуется создать такую библиотеку самостоятельно, можно воспользоваться утилитой LIB.EXE:
lib /machine:IX86 /OUT:mydll.lib /DEF:mydll.def
В этом примере создается библиотека импорта, пригодная для платформ на базе процессора х86. Приложение будет включать библиотеку MYDLL.LIB. Она служит входной информацией для компоновщика и обеспечивает корректное разрешение ссылок на DLL.
Если DLL не сопровождается библиотекой импорта, для информирования модуля подключения о местонахождении подпрограмм из DLL приложение должно использовать файл определений модуля. Этой цели служит раздел IMPORTS файла определений модуля. Для рассматриваемого примера приложения файл определений модуля должен содержать следующие строки:
IMPORTS
MyDll.AddNumbers
Пример создания простой DLL
Пример ниже - исходный код, необходимый для создания простой DLL, Myputs.dll. Она определяет простую печатающую строку функцию, называемую myPuts. DLL Myputs не определяет функцию точки входа, потому что она связана с С- библиотекой периода выполнения программы и не имеет каких- либо выполняемых своих собственных функции инициализации или очистки.
Функция myPuts записывает строку с символом конца ('0') в стандартное устройство вывода данных.
Для механизма экспорта здесь используется метод __declspec(export), поддерживаемый Microsoft Visual Studio, однако и любым другим методом экспорта, поддерживаемым вашим разработанным окружением он может быть заменен.
#include <windows.h>
#define EOF (-1)
#ifdef __cplusplus // Если используется код C++, extern "C" { // мы должны экспортировать C-интерфейс
#endif __declspec(dllexport) int myPuts(LPTSTR lpszMsg) {
DWORD cchWritten; HANDLE hStdout; BOOL fRet;
//Получим дескриптор стандартного устройства вывода. hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
if (INVALID_HANDLE_VALUE == hStdout) return EOF;
//Запишем строку с нулем в конце для стандартного устройства вывода данных. while (*lpszMsg != '\0') {
fRet = WriteFile(hStdout, lpszMsg, 1, &cchWritten, NULL);
if( (FALSE == fRet) || (1 != cchWritten) ) return EOF;
lpszMsg++;
}return 1;
}#ifdef __cplusplus
}#endif
Использование динамического связывания выполняемого при загрузке
После того, как Вы создали DLL, то можете использовать функции, которые ee определяют в приложении. Ниже следует простая консольная программа, которая использует функцию myPuts, экспортируемую из Myputs.dll.
Поскольку в этом примере функция DLL вызывается явно, модуль приложения должен быть скомпонован с импортирующей библиотекой Myputs.lib. Дополнительную информацию о разработке DLL, см. в документации, включенной ваши инструментальные средства разработки.
#include <windows.h> |
|
int myPuts(LPTSTR); |
// функция из DLL |
int main(VOID) { int Ret = 1;
Ret = myPuts(TEXT("Message printed using the DLL function\
n"));
return Ret;
}
Использование динамического связывания периода выполнения
Вы можете использовать одну и ту же DLL, динамически связывая ее и в период выполнения загрузки и в период выполнения программы.
Пример ниже использует функцию LoadLibrary, чтобы получить дескриптор MyputsDLL. Если LoadLibrary завершается успешно, программа использует возвращенный дескриптор в функции GetProcAddress, чтобы получить адрес функции myPuts в DLL. После вызова этой функции DLL, программа вызывает функцию FreeLibrary, чтобы выгрузить DLL.
Поскольку программа использует динамическую связь периода выполнения, нет необходимости компоновать модуль с импортируемой библиотекой для DLL.
Этот пример иллюстрирует важное различие между динамической связью периода выполнения и выполняемую при загрузке. Если DLL не доступна, то приложение, использующее выполняемую при загрузке динамическую связь, должно просто завершиться. Однако, пример динамической связи периода
выполнения, может просто отреагировать на ошибку.
//Простая программа, которая использует функции LoadLibrary и GetProcAddress,
//чтобы получить доступ к myPuts из Myputs.dll.
#include <stdio.h> #include <windows.h>
typedef int (*MYPROC)(LPTSTR); VOID main(VOID) {
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;
//Получим дескриптор модуля DLL. hinstLib = LoadLibrary(TEXT("myputs"));
//Если дескриптор допустимый, то попробуем получить адрес функции. if (hinstLib != NULL) {
ProcAdd = (MYPROC) GetProcAddress(hinstLib, TEXT("myPuts"));
// Если адрес функции правильный, то вызываем функцию. if (NULL != ProcAdd) {
fRunTimeLinkSuccess = TRUE;
(ProcAdd) (TEXT("Message via DLL function\n")); } // Освобождаем модуль DLL.
fFreeResult = FreeLibrary(hinstLib);
} // Если вы не в состоянии вызывать функцию DLL, используйте альтернативу. if (! fRunTimeLinkSuccess)
printf("Message via alternative method\n");
}
Доступ к данным в DLL
Когда DLL динамически распределяет память, это выполняется от имени процесса, который обращается к DLL. Доступ к распределенной памяти возможен только для потоков, принадлежащих данному процессу. Это относится к памяти, выделяемой с использованием GlobalAlloc,
LocalAlloc, HeapAlloc, VirtualAlloc или любой из стандартных подпрограмм библиотеки С, таких как malic и new.
Если DLL динамически распределяет память таким образом, что она может быть доступной всем процессам, использующим библиотеку в данный момент, DLL должна применять функции отображения файлов (file-mapping) для создания поименованной обшей памяти.
Глобальные и статические переменные, объявленные в исходном коде DLL, находятся в сегменте данных DLL. Каждому процессу, присоединенному к DLL, предоставляется собственная копия этого сегмента данных.