April 16th, 2020

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

Начало тут:
1. динамически подключаемые библиотеки;
2. о динамически подключаемых библиотеках подробнее;
3. преимущества динамического связывания;
4. создание динамически подключаемой библиотеки.

Перевод с английского статьи от 31.05.2018 г. «Dynamic-Link Library Entry-Point Function»:
https://docs.microsoft.com/ru-ru/windows/win32/dlls/dynamic-link-library-entry-point-function
(На данный момент на этом сайте нет перевода этой статьи на русский, есть только версия на английском.)

Функция точки входа для DLL необязательна, но может быть определена. Если эта функция присутствует, операционная система вызывает эту функцию точки входа всякий раз, когда процесс или поток выполнения приложения загружает или выгружает DLL. Эта функция может использоваться для выполнения задач простых инициализации и очистки. Например, эта функция может создать локальное хранилище потока [thread local storage] при создании нового потока и ликвидировать его при завершении работы потока.

Если вы компонуете свою DLL с библиотекой времени выполнения для языка Си [C run-time library], то эта библиотека времени выполнения может создать функцию точки входа за вас, при этом вы можете написать свою отдельную функцию инициализации. Для более подробной информации об этом читайте документацию по вашей библиотеке времени выполнения [полезная статья по этому вопросу: «Библиотеки DLL и поведение библиотеки времени выполнения Visual C++»].

Если вы хотите писать свою собственную функцию точки входа, используйте для этого функцию DllMain. Название DllMain является умолчательной заглушкой для функции точки входа, определяемой пользователем. Вы должны указать действительное имя функции точки входа, которое вы используете, при сборке своей DLL. Более подробную информацию об этом ищите в документации, поставляемой с вашими инструментами разработки [полезные статьи по этому вопросу: «Библиотеки DLL и поведение библиотеки времени выполнения Visual C++», «Параметр /ENTRY компоновщика»].

Ситуации, в которых вызывается функция точки входа

Операционная система вызывает функцию точки входа всякий раз, когда происходит одно из следующих событий:
  • процесс загружает DLL. Для процессов, использующих динамическое связывание во время запуска программы, DLL загружается во время инициализации процесса. Для процессов, использующих динамическое связывание во время выполнения программы, DLL загружается перед тем, как функция LoadLibrary или LoadLibraryEx возвратит значение [завершит свою работу];

  • процесс выгружает DLL. DLL выгружается, когда процесс завершается или вызывает функцию FreeLibrary и счетчик ссылок по данной DLL становится равным нулю. Если процесс завершается из-за вызова функций TerminateProcess или TerminateThread, операционная система не вызывает функцию точки входа DLL;

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

  • поток выполнения процесса, загрузившего DLL, завершается нормально (то есть не из-за вызова функций TerminateThread или TerminateProcess). Когда процесс выгружает DLL, функция точки входа вызывается только один раз для всего процесса, а не по разу на каждый существующий поток выполнения процесса. Можно использовать функцию DisableThreadLibraryCalls, чтобы отключить уведомления о завершении потоков выполнения.

Только один поток выполнения за раз может вызвать функцию точки входа.

Операционная система вызывает функцию точки входа в контексте процесса или потока выполнения, ставшего причиной вызова функции. Такой подход разрешает DLL использовать ее функцию точки входа для резервирования памяти в виртуальном адресном пространстве вызывающего процесса или открывать дескрипторы, доступные процессу. Функция точки входа может также резервировать память, которая является собственной [то есть закрытой для других] для нового потока выполнения посредством использования локального хранилища потока [thread local storage (TLS)]. Более подробную информацию о локальном хранилище потока ищите в статье «Локальное хранилище потока».

Написание функции точки входа

Функция точки входа DLL должна быть объявлена с применением соглашения о вызове stdcall [для операционных систем компании Microsoft — __stdcall]. Если точка входа DLL не будет объявлена правильно, DLL не загрузится, а операционная система выдаст сообщение, указывающее, что точка входа DLL должна быть объявлена с применением макроса WINAPI.

В теле функции мы можем управлять в любой комбинации следующими вариантами, в которых вызывается функция точки входа DLL:
  • процесс загружает DLL (эта причина вызова функции точки входа DLL обозначается константой DLL_PROCESS_ATTACH);

  • текущий процесс создает новый поток выполнения (DLL_THREAD_ATTACH);

  • поток выполнения завершается нормально (DLL_THREAD_DETACH);

  • процесс выгружает DLL (DLL_PROCESS_DETACH).

Функция точки входа должна выполнять только задачи простой инициализации. Эта функция не должна вызывать функцию LoadLibrary или LoadLibraryEx (или функцию, которая вызывает эти функции), потому что такие вызовы могут создавать циклические зависимости [по-английски «dependency loops», то есть, например, при загрузке одной DLL начинается загрузка второй DLL, которая пытается загрузить первую] в порядке загрузки библиотек DLL. Это может привести к тому, что DLL будет использована раньше, чем операционная система выполнит код ее инициализации. Равным образом функция точки входа не должна вызывать функцию FreeLibrary (или функцию, которая вызывает функцию FreeLibrary) во время завершения процесса, потому что это может привести к тому, что DLL будет использована после того, как операционная система выполнит код ее завершения.

Из-за того, что системная библиотека Kernel32.dll гарантированно будет загружена в адресное пространство процесса, когда вызывается функция точки входа, вызов функций из Kernel32.dll не приведет к тому, что DLL будет использована раньше, чем выполнится код ее инициализации. Поэтому функция точки входа может создавать объекты синхронизации, такие как критические секции и мьютексы, и использовать TLS [локальное хранилище потока], потому что эти функции размещены в Kernel32.dll. Не является безопасным, к примеру, вызов функций реестра [операционной системы], потому что они размещены в системной библиотеке Advapi32.dll.

Вызов других функций может привести к проблемам, которые трудно диагностировать. Например, вызов функций [компонентов операционной системы Windows] «User» [функции для работы с пользовательским интерфейсом, окнами], «Shell» [функции для работы с командной оболочкой операционной системы] и функций COM [Component Object Model, по-русски «модель компонентного объекта»] может послужить причиной ошибок нарушения доступа из-за того, что некоторые функции в их DLL вызывают функцию LoadLibrary, чтобы загрузить другие компоненты операционной системы. И, наоборот, вызов этих функций во время завершения [процесса или потока выполнения] может послужить причиной ошибок нарушения доступа из-за того, что соответствующий компонент мог уже быть выгружен или не инициализирован.

Следующий пример демонстрирует примерную структуру функции точки входа DLL.
BOOL WINAPI DllMain(
    HINSTANCE hinstDLL,  // дескриптор модуля DLL
    DWORD fdwReason,     // причина вызова функции
    LPVOID lpReserved )  // параметр с дополнительной информацией
{
    // выполнить некие действия в зависимости от причины вызова функции
    switch( fdwReason ) 
    { 
        case DLL_PROCESS_ATTACH:
         // инициализация для каждого нового процесса;
         // возврат значения FALSE, если не получается загрузить DLL
            break;

        case DLL_THREAD_ATTACH:
         // выполнить инициализацию для потока выполнения
            break;

        case DLL_THREAD_DETACH:
         // произвести очистку для потока выполнения
            break;

        case DLL_PROCESS_DETACH:
         // выполнить любую необходимую очистку
            break;
    }
    return TRUE;  // успешная отработка функции по причине DLL_PROCESS_ATTACH
}

Возвращаемое значение функции точки входа

Когда функция точки входа DLL вызывается по причине загрузки процесса, эта функция возвращает значение TRUE, чтобы показать, что она отработала успешно. Для процессов, использующих динамическое связывание во время запуска программы, возврат значения FALSE повлечет за собой неудачу инициализации процесса и его завершение. Для процессов, использующих динамическое связывание во время выполнения программы, возврат значения FALSE повлечет за собой то, что функция LoadLibrary или LoadLibraryEx возвратит значение NULL, указывающее на то, что эта функция свою работу не выполнила по причине некой ошибки. (Операционная система сразу вызовет нашу функцию точки входа с причиной вызова DLL_PROCESS_DETACH и выгрузит DLL.) Возвращаемое значение функции точки входа оставляется без внимания, когда функция вызывается по любой другой причине, кроме DLL_PROCESS_ATTACH.

Динамическое связывание во время запуска

Начало тут:Collapse )
Перевод с английского статьи от 31.05.2018 г. «Load-Time Dynamic Linking»:
https://docs.microsoft.com/ru-ru/windows/win32/dlls/load-time-dynamic-linking
(На данный момент на этом сайте нет перевода этой статьи на русский, есть только версия на английском.)

Когда [операционная] система запускает программу, которая использует динамическое связывание во время запуска, она [система] использует информацию, которую компоновщик поместил в [исполняемый] файл [программы при ее компиляции], чтобы определить названия библиотек DLL, необходимых для работы процесса. Затем система запускает поиск этих DLL. Подробнее об этом читайте в статье «Порядок поиска динамически подключаемой библиотеки».

Если система не может найти нужную DLL, она завершает процесс и показывает пользователю на экране диалоговое окно с сообщением об ошибке. В противном случае система отображает DLL в виртуальное адресное пространство процесса и увеличивает на единицу счетчик ссылок на эту DLL.

Система вызывает функцию точки входа. Функция получает код, указывающий, что процесс загружает DLL. Если функция точки входа не возвращает значение TRUE, система завершает процесс и сообщает об ошибке. Подробнее о функции точки входа читайте в статье «Функция точки входа DLL».

В заключение система корректирует таблицу адресов функций так, чтобы адреса функций соответствовали адресам функций, импортированных из DLL.

DLL отображается в виртуальное адресное пространство процесса во время его инициализации и загружается в физическую память [оперативная память компьютера], только когда в ней [библиотеке DLL] появляется необходимость.

Динамическое связывание во время выполнения

Начало тут:Collapse )
Перевод с английского статьи от 31.05.2018 г. «Run-Time Dynamic Linking»:
https://docs.microsoft.com/ru-ru/windows/win32/dlls/run-time-dynamic-linking
(На данный момент на этом сайте нет перевода этой статьи на русский, есть только версия на английском.)

Когда приложение вызывает функцию LoadLibrary или LoadLibraryEx, [операционная] система пытается найти DLL (подробнее об этом читайте в статье «Порядок поиска динамически подключаемой библиотеки»). Если поиск увенчался успехом, система отображает модуль DLL в виртуальное адресное пространство процесса и увеличивает на единицу счетчик ссылок [на эту DLL]. Если вызов функции LoadLibrary или LoadLibraryEx определяет DLL, чей код уже отображен в виртуальное адресное пространство вызывающего процесса, эта функция просто возвращает дескриптор этой DLL и увеличивает на единицу счетчик ссылок на эту DLL. Отметим, что две библиотеки DLL, имеющие одно и то же название и расширение файла, но находящиеся в разных каталогах, не считаются одной и той же DLL.

Система вызывает функцию точки входа в контексте потока выполнения, который вызвал функцию LoadLibrary или LoadLibraryEx. Функция точки входа не вызывается, если DLL уже была загружена процессом посредством вызова функции LoadLibrary или LoadLibraryEx, но при этом не было соответствующего вызова функции FreeLibrary.

Если система не может найти DLL или если функция точки входа возвратила значение FALSE, то функция LoadLibrary или LoadLibraryEx возвращает значение NULL. Если функция LoadLibrary или LoadLibraryEx отрабатывает успешно, она возвращает дескриптор модуля DLL. Процесс может использовать этот дескриптор, чтобы указать на эту DLL в обращении к функциям GetProcAddress, FreeLibrary или FreeLibraryAndExitThread.

Функция GetModuleHandle возвращает дескриптор, который может использоваться в функциях GetProcAddress, FreeLibrary или FreeLibraryAndExitThread. Функция GetModuleHandle отработает успешно, только если модуль DLL уже отображен в адресное пространство процесса с помощью динамического связывания во время запуска программы или с помощью предыдущего вызова функции LoadLibrary или LoadLibraryEx. В отличие от функции LoadLibrary или LoadLibraryEx функция GetModuleHandle не увеличивает на единицу счетчик ссылок на модуль. Функция GetModuleFileName получает полный путь к модулю, связанному с дескриптором, который возвращают функции GetModuleHandle, LoadLibrary или LoadLibraryEx.

Процесс может использовать функцию GetProcAddress, чтобы получить адрес экспортируемой функции DLL с помощью дескриптора модуля DLL, возвращенного одной из функций LoadLibrary или LoadLibraryEx, GetModuleHandle.

Когда необходимость в модуле DLL отпадает, процесс может вызвать функцию FreeLibrary или функцию FreeLibraryAndExitThread. Эти функции уменьшают на единицу счетчик ссылок на модуль и выгружают код DLL из виртуального адресного пространства процесса, если счетчик ссылок становится равен нулю.

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

Динамическое связывание во время выполнения программы может послужить причиной проблем, если DLL использует функцию DllMain, чтобы выполнить инициализацию для каждого потока выполнения процесса, из-за того, что функция точки входа не вызывалась для потоков выполнения, существовавших перед вызовом функции LoadLibrary или LoadLibraryEx. Чтобы увидеть пример, показывающий, как справиться с этой проблемой, читайте статью «Использование локального хранилища потока в динамически подключаемой библиотеке».