Главная

Категории:

ДомЗдоровьеЗоологияИнформатикаИскусствоИскусствоКомпьютерыКулинарияМаркетингМатематикаМедицинаМенеджментОбразованиеПедагогикаПитомцыПрограммированиеПроизводствоПромышленностьПсихологияРазноеРелигияСоциологияСпортСтатистикаТранспортФизикаФилософияФинансыХимияХоббиЭкологияЭкономикаЭлектроника






Пояснения к схеме. Создание DLL


• 1) Заголовочный файл с экспортируемыми прототипами, структурами и идентификаторами (символьными именами)

• 2) Исходные файлы С/С++ в которых реализованы функции и определены переменные

• 3) Компилятор создаэт OBJ-файл из каждого исходного файла С/С++

• 4) Компоновщик собирает DLL из OBJ-модулей

• 5) Если DLL экспортирует хотя бы одну переменную или функцию, компоновщик создает и LIB-файл

• 6) Заголовочный файл с импортируемыми прототипами структурами и идентификаторами

• 7) Исходные файлы С/С++, из которых вызываются импортируемые функции и переменные

• 8) Компилятор создает OBJ-файл из каждого исходного файла С/С++

• 9) Используя OBJ модули и LIB-файл и учитывая ссылки на импортируемые идентификаторы компоновщик собирает ЕХЕ-модуль (в котором также размещается таблица импорта — список необходимых DLL и импортируемых идентификаторов)

• 10) Создается адресное пространство процесса, проецируется исполняемый exe модуль

• 11) Используя записи в таблице импорта, загрузчик производит поиск библиотек, от которых зависит исполняемый модуль, проецирует их в память.

• 12) Шаг 11) повторяется для каждой из загруженных библиотек.

• 13) В случае успешного разрешения всех зависимостей создается первичный поток, приложение начинает выполняться

Последовательность поиска DLL

• Каталог, содержащий ЕХЕ-файл.

• Текущий каталог процесса.

• Системный каталог Windows

• Основной каталог Windows

• Каталоги, указанные в переменной окружения PATH.

Искажение имен

• Обычно компиляторы С++ искажают (mangle) имена функций и переменных, что может приводить к серьезным ошибкам при компоновке.

• Представьте, что DLL написана на С++, а исполняемый код — на стандартном С. При сборке DLL имя функции будет искажено, но при сборке исполняемого модуля — нет.

• Пытаясь скомпоновать исполняемый модуль, компоновщик сообщит об ошибке исполняемый модуль обращается к несуществующему идентификатору.

• Модификатор extern не дает компилятору искажать имена переменных или функций, и они становятся доступными исполняемым модулям, написанным на С, С++ или любом другом языке программирования

• Пользуйтесь этим модификатором только в коде на С++, но ни в коем случае не в коде на стандартном С.

Раздел экспорта

• Если перед переменной, прототипом функции или С++-классом указан модификатор __declspec(dllexport) , компилятор Microsoft С/С++ встраивает в конечный OBJ-файл дополнительную информацию. Она понадобится компоновщику при сборке DLL из OBJ-файлов.

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

• Компоновщик также вставляет в конечный DLL-файл таблицу экспортируемых идентификаторов - раздел экспорта,в котором содержится список (в алфавитном порядке) идентификаторов экспортируемых функций, переменных и классов. Туда же помещается относительный виртуальный адрес (relative virtual address, RVA) каждого идентификатора внутри DLL модуля.

Создание DLL для использования другими языками программирования

• При использовании соглашения __stdcall компилятор Microsoft искажает имя С-функции. впереди ставит знак подчеркивания, а к концу добавляет суффикс, состоящий из символа @ и числа байтов, передаваемых функции в качестве параметров. Например, следующая функция экспортируется в таблицу экспорта DLL как _MyFunc@8:

__declspec(dllexport) LONG __stdcall MyFunc(int a, int b);

• Если Вы решите создать ЕХЕ-файл с помощью средств разработки от другого поставщика, то компоновщик попытается скомпоновать функцию MyFunc, которой нет в файле DLL, созданном компилятором Microsoft, и, естественно, произойдет ошибка

• Чтобы средствами Microsoft собрать DLL, способную работать с инструментарием от другого поставщика, нужно указать компилятору Microsoft экспортировать имя функции бсз искажений. Сделать это можно двумя способами:

– Первый — создать DEFфайл для Вашего проекта и включить в него раздел EXPORTS.

EXPORTS MyFunc

– Второй – в один из файлов исходного кода добавить
#pragma comment(linker, "/export:MyFunc=_MyFunc@8")

Компоновщик от Microsoft, анализируя этот DEF-файл, увидит, что экспортировать надо обе функции: __MyFunc@8 и MyFunc. Поскольку их имена идентичны (не считая вышеописанных искажений), компоновщик на основе информации из DEF-файла экспортирует только функцию с именем MyFunc, а функцию _MуFипс@8 не экспортирует вообще.

При использовании #pragma компилятор потребует от компоновщика экспортировать функцию MyFunc с той же точкой входа, что и _MyFunc@8. Этот способ менее удобен, чем первый, так как здесь приходится самостоятельно вставлять дополнительную директиву с искаженным именем функции. И еще один минус этого способа в том, что из DLL экспортируется два идентификатора одной и той же функции MyFunc и _МуFипс@8, тогда как при первом способе — только идентификатор MyFunc. По сути, второй способ не имеет особых преимуществ перед первым — он просто избавляет от DEF-файла

Явная загрузка библиотеки

 

• В любой момент поток может спроецировать DLL на адресное пространство процесca, вызвав одну из двух функций:

HINSTANCE LoadLibrary(
PCTSTR pszDLLPathName
);
HINSTANCE LoadLibraryEx(
PCTSTR pszDLLPathName,
HANDLE hFile,
DWORD dwFlags
);

• Значение типа HINSTANCE, возвращаемое этими функциями, сообщает адрес виртуальной памяти, но которому спроецирован образ файла. Если спроецировать DLL на адресное пространство процесса не удалось, функции возвращают NULL Дополнительную информацию об ошибке можно получить вызовом GetLastError

Явная выгрузка DLL

• Если необходимость в DLL отпадает, ее можно выгрузить из адресного пространства процесса, вызвав функцию.

BOOL FreeLibrary(HINSTANCE hinstDll);

• Вы должны передать в FreeLibrary значение типа HINSTANCE, которое идентифицирует выгружаемую DLL. Это значение Вы получаете в результате вызова LoadLibrary(Ex).

Подсчет ссылок

• Операционная система хранит количество ссылок на каждый модуль

• В случае неоднократного вызова функции LoadLibrary(Ex) для загрузки одной и той же библиотеки переменная, хранящая количество ссылок для данного модуля, увеличивается на 1

• При вызове FreeLibrary переменная, содержащая количество ссылок, уменьшается на 1. Если количество ссылок становится 0, библиотека выгружается

Поиск функции

• Поток получает адрес экспортируемого идентификатора из явно загруженной DLL вызовом GetProcAddress:

FARPROC GetProcAddress(
HINSTANCE hinstDll,
PCSTR pszSymbolName
);

Пример:

FARPROC pfn = GetProcAddress(hinstDll, "SomeFuncInDll");

FARPROC pfn = GetProcAddress(hinstDll, MAKEINTRESOURCE(2));

Функция входа

BOOL WINAPI DllMain(HINSTANCE hinstDll, DWORD fdwReason, PVOID fImpLoad)

{

switch (fdwReason)

{

case DLL_PROCESS_ATTACH:

// DLL проецируется на адресное пространство процесса

break;

case DLL_THREAD_ATTACH:

// создается поток

break;

case DLL_THREAD_DETACH:

// поток корректно завершается

break;

case DLL PROCESS_DETACH

// DLL отключается от адресного пространства процесса

break;

}

return(TRUE);

// используется только для DLL_PROCESS_ATTACH

}

• Параметр hinstDll содержит описатель экземпляра DLL. Это значение — виртуальный адрес проекции файла DLL на адресное пространство процесса.

• Параметр fdwReason сообщает о причине, по которой система вызвала эту функцию. Он принимает одно из четырех значений: DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH или DLL_THREAD_DETACH.

• Последний параметр, fImpLoad, отличен от 0, если DLL загружена неявно, и равен 0, если она загружена явно.

ВНИМАНИЕ!

• Не вызывайте из DllMain функции LoadLibrary(Ex) и FreeLibrary, так как это может привести к взаимной блокировке.

• Не используйте функции из других библиотек (кроме kernel32), так как их в памяти может не оказаться.

• Вышеперечисленные правила относятся и к конструкторам глобальных и статических объектов

TerminateProcess() & TerminateThread()

• Если процесс завершается в результате вызова TerminateProcess, система НЕвызывает DllMain со значением DLL_PROCESS_DETACH.

• А значит, ни одна DLL, спроецированная на адресное пространство процесса, не получит шанса на очистку до завершения процесса. Последствия могут быть плачевны – вплоть до потери данных.

• Аналагично с TerminateThread() - ни одна DLL, спроецированная на адресное пространство процесса, НЕ получит шанса на выполнение очистки до завершения потока

• Вызывайте TerminateProcess и TerminateThread() только в самом крайнем случае!



Последнее изменение этой страницы: 2016-06-09

headinsider.info. Все права принадлежат авторам данных материалов.