Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
3 семестр, WinAPI, MFC.pdf
Скачиваний:
393
Добавлен:
15.06.2014
Размер:
6 Мб
Скачать
☆

Экспорт функций и переменных из DLL

Создавая DLL, Вы создаете набор функций и хотите, чтобы они были доступны другим ЕХЕ - или DLL-модулям. Если DLL-функция доступна для вызова из других программ, то говорят, что это экспортируемая (exported) функция.

•Кроме функций, Win32 позволяет экспортировать и глобальные переменные.

Вот как экспортировать из DLL функцию Add и глобальную целочисленную переменную g_nUsageCount

__declspec(dllexport)

int

Add(int

nLeft,

int

nRight)

{

return(nLeft + nRight);

 

 

 

 

 

}

 

 

 

 

 

 

__declspec(dllexport)

int g_nUsageCount = 0;

 

 

 

 

Конструкцию __dedspec(dllexport) компилятор C/C++ распознает как новое ключевое слово. Компилируя функцию Add и g_nUsageCount, он встраивает в конечный OBJ-файл дополнительную информацию, необходимую компоновщику при сборке DLL из OBJ-файлов.

Обнаружив такую информацию, компоновщик создает LIB-файл со списком иден-

тификаторов, экспортируемых из DLL. Этот LIB-файл нужен при сборке любого ЕХЕ - файла, вызывающего функции из данной DLL. Кроме того, компоновщик вставляет в

конечный файл DLL и таблицу экспортируемых идентификаторов (exported symbols).

Каждый элемент в этой таблице содержит имя экспортируемой функции или переменной, а также адрес этой функции или переменной внутри DLL-файла. Все списки сортируются по алфавиту.

Импорт функций и переменных из DLL

Чтобы ЕХЕ - файл мог вызывать функции или получать доступ к переменным из DLL, нужно сообщить компилятору, что они находятся именно в DLL. Следующий фрагмент кода показывает, как импортировать функцию Add и переменную g_nUsageCount,

экспортируемые DLL-модулем:

_declspec(dllimport) int Add(int nLeft, int nRight); _declspec(dlliinport) int g_nUsageCount;

Как и _dedspec(dllexport), _dedspec(dllimport) — новое ключевое слово, распознаваемое компилятором C/C++. Оно сообщает компилятору, что функция Add и переменная g_nUsageCount находятся в DLL, доступ к которой ЕХЕ - файл должен получить при загрузке.

•Это заставит компилятор сгенерировать специальный код для импортируемых идентификаторов (imported symbols).

•Еще компилятор встроит специальную информацию в конечный объектный файл.

Она используется при компоновке ЕХЕ - файла, подсказывая компоновщику, какие функции следует искать в LIB-файлах. Собирая ЕХЕ - файл, компоновщик отыскивает импортируемые функции и переменные. Затем, определив, какой LIB-файл содержит эти идентификаторы, добавляет в таблицу импорта новые элементы. Каждый элемент содержит имя соответствующего DLL-файла, а также имя самого идентификатора. Таблица импорта вносится в конечный ЕХЕ - файл при его записи на жесткий диск.

При запуске ЕХЕ - файла загрузчик операционной системы просматривает таблицу импорта, содержащуюся в этом файле, и пытается найти и спроецировать на адресное пространство нового процесса все необходимые DLL. Затем он сохраняет адреса идентификаторов, используемых ЕХЕ - файлом, в особой таблице.

Импортируя идентификатор, ключевое слово _declspec(dllimport) использовать не обязательно. Вместо него можно взять стандартное ключевое слово extern. Но компилятор генерирует более эффективный код, если ему заранее известно, что идентификатор будет импортирован из DLL.

Поток получает адрес экспортируемой из DLL функции или переменной вызовом

GetProcAddress:

FARPROC GetProcAddress(HINSTANCE hinstDll, LPCSTR IpszProc);

•Параметр hinstDll— описатель DLL. Это значение можно получить с помощью

LoadLibrary или LoadLibraryEx. Параметр IpszProc разрешается указывать в двух формах. Во-первых, как адрес строки с нулевым символом в конце, содержащей

имя интересующей Вас функции:

Ipfn = GetProcAddress(hinstDll, "SomeFuncInDll");

Вторая форма параметра IpszProc позволяет указывать порядковый номер нужной функции:

Ipfn = GetProcAddress(hinstDll, MAKEINTRESOURCE(2));

Здесь подразумевается, что нам известен порядковый номер (2) функции SomeFuncInDll, присвоенный ей в данной DLL.

Оба способа дают адрес содержащейся в DLL функции SomeFuncInDll. Если функция не найдена, GetProcAddress возвращает NULL. Однако и тот, и другой способ не без недостатков.

•Первый медленнее, так как системе приходится проводить поиск и сравнивать строки.

•Во втором, если Вы передаете порядковый номер, не присвоенный ни одной из экспортируемых функций, GetProcAddress может вернуть значение, отличное от NULL. (Это относится и к 16-разрядной Windows.)

Витоге Ваша программа, ничего не “подозревая”, получит неправильный адрес.

Заголовочный файл DLL

При создании DLL создается и ее заголовочный файл, содержащий прототипы всех экспортируемых из DLL функций и переменных. Он понадобится Вам при компиляции ЕХЕ-файлов. Часто его включают и при компиляции исходных файлов самой DLL.

 

Чтобы один и тот же заголовочный файл можно было использовать при компиляции

исходных файлов как ЕХЕ, так и DLL-модулей, он должен выглядеть так:

 

 

//

MYLIBAPI

определена

в

файле

реализаций MyLib.C как

_declspec(dllexport)

//

Поэтому

при включении

заголовочного файла в MyLib.C,

функции

будут

// экспортироваться, а не импортироваться.

 

 

 

#ifndef

MYLIBAPI

 

 

 

 

 

#define

MYLIBAPI _declspec(dllimport)

 

 

 

 

#endif

 

 

 

 

 

 

 

//////////////////////////////////////////////////////////////////////////////////

 

 

MYLIBAPI

 

int

Add(int

nLeft,

int

riRight);

MYLIBAPI int g_nUsageCount;

 

 

 

 

 

Этот заголовочный файл надо включать в самое начало исходных файлов Вашей DLL следующим образом:

// Мы должны определить MYLIBAPI как _declspec(dllexport) до включения

// файла MyLib.H. Тогда MyLib.H, "увидев", что мы уже определили //MYLIBAPI, не станет (пере)определять ее как _declspec(dlliinport).

#define MYLIBAPI __declspec(dllexport) #include "MyLib.H"

///////////////////////////////////////////////////////////////////

int Add(int nLeft, int nRight) { return(nLeft + nRight);

}

MYLIBAPI int g_nUsageCount;

Поскольку MYLIBAPI определена как _declspec(dllexport) явно, то при компиляции исходного кода Вашей программы компилятор узнает, что функции экспортируются. А

это позволяет не повторять _declspec(dllexport) перед каждой процедурой или переменной.

Заголовочный файл нужно включить в исходный код, импортирующий из DLL идентификаторы. MYLIBAPI будет определена как _declspec(dllimport), и компилятор “поймет”, какие идентификаторы содержатся в DLL.

Совместное использование дaнных несколькими проекциями ЕХЕ или DLL

Как уже известно, система создает отдельные экземпляры любых глобальных и

статических переменных, содержащихся в образе ЕХЕили DLL-файла для каждой его проекции. Иначе говоря, если в ЕХЕ есть глобальная переменная и Вы запускаете 2 или более копии приложения, каждый процесс получает свою копию переменной, т. е. она не разделяется

несколькими экземплярами ЕХЕ. Например, в Win32 нет простого способа, позволяющего определить, запущено ли несколько копий приложения. А если бы у Вас была переменная, доступная всем копиям приложения, она могла бы отражать число запущенных копий. Тогда при запуске нового экземпляра приложения его поток просто проверил бы значение глобальной переменной (обновленное другой копией приложения) и, будь оно больше 1, сообщил бы пользователю, что запустить можно лишь одну копию, после чего этот экземпляр приложения благополучно бы и завершился.

Разделы в ЕХЕ- и DLL-файлах

Любой образ ЕХЕили DLL-файла состоит из группы разделов (sections). По соглашению имя каждого стандартного раздела начинается с точки.

Например, при компиляции программы исполняемый код помещается в раздел .text, неинициализированные данные — в раздел .bss, а инициализированные — в раздел .data.

С каждым разделом связана комбинация следующих атрибутов:

•READ – разрешает чтение из раздела

•WRITE – разрешает запись в раздел

•EXECUTE – содержимое раздела можно исполнять

•SHARED – раздел доступен нескольким экземплярам приложения

(!!!)Вот некоторые из часто встречающихся разделов:

1. .text – код приложения из DLL

2. .bss – неинициализированные данные

3. .rdata – неизменяемые данные периода выполнения

4. .rsrc - ресурсы

5. .edata – таблица экспортируемых имен

6. .data – инициализированные данные

7. .xdata – таблица для обработки исключений