Лабораторная работа. Динамически загружаемые библиотеки
Цель работы
Изучить связывание процесса с динамически загружаемыми библиотеками на этапе загрузки и на этапе выполнения.
Список используемых системных вызовов
LoadLibrary, FreeLibrary, FreeLibraryAndExitThread, GetModuleHandle, GetProcAddress, DllMain.
Методические указания
Динамически загружаемые библиотеки (DLL) связываются с программами и между собой с помощью специальных таблиц экспорта и импорта, находящихся внутри файлов библиотек и программ. Просмотреть содержимое этих таблиц можно с помощью утилиты dumpbin.exe. Ниже приведены примеры использования этой утилиты для библиотеки kernel32.dll:
dumpbin.exe kernel32.dll \exports > kernel32.exports
dumpbin.exe kernel32.dll \imports > kernel32.imports
Таблица импорта содержит запрашиваемые ресурсы, таблица экспорта – предоставляемые ресурсы библиотеки.
Для создания динамически загружаемой библиотеки необходимо указать при создании тип проекта – DLL. У DLL отсутствует функция WinMain. Вместо нее используется функция DllMain, вызываемая в четырех случаях:
- при загрузке библиотеки процессом (отображении в виртуальное адресное пространство процесса);
- при создании новой нити;
- при завершении созданной нити;
- при завершении процесса или при выгрузке библиотеки.
Любую функцию, переменную или класс библиотеки можно сделать экспортируемыми, т.е. подключаемыми извне с помощью таблицы экспорта. Чтобы адрес ресурса был помещен в таблицу экспорта, необходимо указать непосредственно перед определением ресурса ключевые слова __declspec(dllexport). Примеры экспортирования ресурсов:
__declspec(dllexport) int i;
__declspec(dllexport) void func();
class __declspec(dllexport) Class;
DLL могут загружаться процессом при старте программы (динамическое связывание) или явно с помощью функции LoadLibrary. После компиляции библиотеки компоновщик создает два файла для каждой динамически загружаемой библиотеки – с расширениями .lib и .dll. Файл с расширением .lib необходимо подключить при компоновке программы, использующей динамическое связывание. Это делается в опциях проекта для компоновщика «additionaldependencies». Программа может импортировать ресурсы из DLL с помощью ключевых слов __declspec(dllimport). Примеры импортирования ресурсов:
__declspec( dllimport ) int i;
__declspec(dllimport) void func();
class __declspec(dllimport) Class;
При запуске программы файл библиотеки с расширением .dll должен находиться в одном каталоге с файлом программы или быть доступен по путям поиска.
Второй способ загрузки динамической библиотеки основывается на вызове функции LoadLibrary. В параметре lpFileName явно указывается путь до файла библиотеки. Результатом вызова этой функции является загрузка библиотеки в виртуальное адресное пространство процесса. Процессу становится доступен дескриптор (HMODULE) библиотеки. Для получения адреса ресурса, находящегося в библиотеке, необходимо вызвать функцию GetProcAddress. Параметр lpProcName должен содержать указатель на верное имя ресурса. Следует отметить, что имя ресурса в таблице экспорта отличается от имени ресурса, определенного в библиотеке. Его следует получать с помощью утилиты dumpbin.exe.
Для выгрузки DLL из виртуального адресного пространства процесса используется функция FreeLibrary. В параметре hModule следует указать дескриптор библиотеки, полученный с помощью функции LoadLibrary.
Задания
Необходимо разработать программу состоящую из головной программы и двух динамически загружаемых библиотек. Одна библиотека должна загружаться с использованием динамического связывания, а другая – с использованием функции LoadLibrary.
Листинг программы.
laba6.cpp
laba6.cpp #include #include #include #include using namespace std; #include "laba3_1.h" typedef void (WINAPI *cFunc)(); void PrepareStatic(); void main() { //явный вызов динамической библиотеки printf("\nWork functoin from dll.dll!!!"); //*************************************************************** HINSTANCE hModule=NULL; hModule=::LoadLibrary("dll.dll"); if (hModule!=NULL) { cFunc Schet = (cFunc)::GetProcAddress((HMODULE)hModule,"Schet"); if (Schet!=NULL) { Schet(); } else cout << "Error Load function" << endl; ::FreeLibrary(hModule); } else cout << "error load Dll" << endl; PrepareStatic(); Schet(); getch(); } void PrepareStatic() { #pragma comment(lib, "dll.lib") }
laba3_1.h
#ifndef _DLLTEST_H_ #define _DLLTEST_H_ #include <iostream> #include <stdio.h> #include <windows.h> extern "C" __declspec(dllexport) void Schet(); #endif
laba3.h
#ifndef _DLLTEST_H_ #define _DLLTEST_H_ #include <iostream> #include <stdio.h> #include <windows.h> extern "C" __declspec(dllexport) void Schet(); #endif
laba3.cpp
extern "C" __declspec(dllexport) void Schet() { HANDLE hFile; OVERLAPPED over; DWORD dByte,dwError; int iByteToRead=1; BOOL bResult; int iWord=0,tek=0,pred=0,iBreak=0,iBegin=1,iEnd=1; //новое слово - tek=1,pted=0; over.Offset=0; over.OffsetHigh=0; char cBuffer[1]; // Создаём событие для контроля за асинхронным чтением over.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if(over.hEvent==NULL) { printf("\nError create event!!!"); exit(0); // Ошибка создания события … } hFile=CreateFile("test.txt",GENERIC_READ,FILE_SHARE_READ, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,NULL); if(hFile==INVALID_HANDLE_VALUE) { printf("\nError opening file!!!"); exit(0); } while(1) { bResult = ReadFile(hFile,cBuffer,iByteToRead,&dByte,&over); // если возникает проблема или асинхронная операция // все еще ожидает обработки ... if (!bResult) { // решаем что делать с кодом ошибки switch (dwError = GetLastError()) { case ERROR_HANDLE_EOF: { // мы достигли конца файла // в течение вызова к ReadFile iBreak=1; break; } case ERROR_IO_PENDING: { // асинхронный ввод-вывод все еще происходит // сделаем кое-что пока он идет // GoDoSomethingElse() ; over.Offset++; pred=tek; if(cBuffer[0]==' '|| cBuffer[0]=='\x0D' || cBuffer[0]=='\n')tek=0; else tek=1; if(tek==1 && pred==0)iWord++; // проверим результат работы асинхронного чтения bResult = GetOverlappedResult(hFile, &over, &dByte, FALSE); // если возникла проблема ... if (!bResult) { // решаем что делать с кодом ошибки switch (dwError = GetLastError()) { case ERROR_HANDLE_EOF: { // мы достигли конца файла // в ходе асинхронной // операции iBreak=1; break; } default: { printf("\nError work to file!!!"); iBreak=1; break; // решаем что делать с другими случаями ошибок } }// конец процедуры switch (dwError = GetLastError()) } break; } // конец процедуры case default: { printf("\nError work to file!!!"); iBreak=1; break; // решаем что делать с другими случаями ошибок } } // конец процедуры switch (dwError = GetLastError()) } // конец процедуры if if(iBreak==1)break; over.Offset++; pred=tek; if(cBuffer[0]==' ' || cBuffer[0]=='\x0D' || cBuffer[0]=='\n')tek=0; else tek=1; if(tek==1 && pred==0)iWord++; } printf("\nIn text %d words!!!\n", iWord); }
Вывод: изучили связывание процесса с динамически загружаемыми библиотеками на этапе загрузки и на этапе выполнения.