Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
VC_Lect.doc
Скачиваний:
6
Добавлен:
01.07.2025
Размер:
15 Мб
Скачать
☆

14.1.Основы dll

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

В общем, DLL-модуль — это файл на диске (обычно, но не обязательно, с расширением DLL), который состоит из глобальных данных, откомпилированных и готовых к исполнению функций и ресурсов и который становится частью вашего процесса. Он компилируется так, чтобы его можно было загружать по определенному базовому адресу; при отсутствии конфликтов с другими DLL модуль будет спроецирован на этот же виртуальный адрес в процессе. В DLL содержатся так называемые экспортируемые (exported) функции, которые импортирует (import) клиентская программа (программа, загружающая DLL).

Примечание. DLL-модули в Win32 позволяют экспортировать и глобальные переменные.

В Win32 каждый процесс получает свою копию глобальных переменных DLL для чтения и записи. Если Вы хотите, чтобы несколько процессов совместно использовали какой-то участок памяти, надо либо прибегнуть к файлу, проецируемому в память (file-mapping), либо объявить общий раздел данных (shared data section), как описано в книге «Джеффри Рихтер. Windows для профессионалов – M.: Русская Редакция, 1997). Всякий раз, когда DLL запрашивает память из кучи, эта память выделяется из кучи, принадлежащей клиентскому процессу.

14.1.1.Согласование импортируемых элементов с экспортируемыми

DLL содержит таблицу экспортируемых функций. Эти функции идентифицируются извне по их символьному имени и (что необязательно) по целому числу — порядковому номеру (ordinal number). В таблице функций хранятся также адреса функций в пределах DLL. Впервые загружая DLL, клиентская программа не знает адресов нужных ей функций, зато знает их символьные имена или порядковые номера. Процесс динамической компоновки формирует таблицу, связывающую вызовы из клиентской программы с адресами функций в DLL. После модификации DLL собирать заново клиентскую программу не надо, если Вы не изменили имена функций или последовательности и типы их параметров.

В коде DLL экспортируемые функции надо объявлять явным образом, например:

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

__declspec(dllexport) int MyFunction (int n);

(Альтернатива — перечислить экспортируемые функции в файле определения модуля (DEF) — гораздо хлопотнее). В клиентской программе необходимо указывать, что данная функция импортируется:

__declspec(dllimport) int MyFunction (int n);

Однако компилятор C++ сгенерирует для MyFunction() так называемое расширенное имя (decorated name), которое нельзя использовать в других языках. Это длинное имя, составляемое компилятором из имен класса и функции, а также типов параметров. Чтобы у функции было простое имя, например MyFunction, объявления функций следует писать так:

extern "С" __declspec(dllexport) int MyFunction (int n);

extern "С" __declspec(dllimport) int MyFunction (int n);

Примечание. По умолчанию компилятор формирует передачу параметров по соглашению __cdecl, а это значит, что параметры из стека выталкивает вызывающая программа. Программы на некоторых языках требуют придерживаться соглашения __stdcall (как в Паскале), когда стек очищает вызванная функция. Тогда в объявлении функции, экспортируемой из DLL, понадобится модификатор __stdcall.

Одних только объявлений импортируемых элементов недостаточно, чтобы клиент установил связь с DLL. В проекте клиентской программы надо определить библиотеку импорта (LIB) для компоновщика, а сама программа должна содержать как минимум один явный вызов какой-то функции, импортируемой из DLL. Оператор вызова должен находится на одном из путей выполнения программы. Другими словами, оператор вызова может и не выполняться, но должен находиться в таком месте программы, куда (пусть теоретически) может перейти управление. Только тогда компилятор гарантированно сгенерирует машинный код, реализующий вызов.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]