Лабораторная работа. Динамически загружаемые библиотеки
Цель работы
Изучить связывание процесса с динамически загружаемыми библиотеками на этапе загрузки и на этапе выполнения.
Список используемых системных вызовов
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);
}
Вывод: изучили связывание процесса с динамически загружаемыми библиотеками на этапе загрузки и на этапе выполнения.