Использование DllEntryPoint
В данной статье я хотел бы рассмотреть два вопроса — во первых, рассмотреть пример использования точки входа библиотеки (DllEntryPoint), во вторых — продемонстрировать один из способов, как определить версию файла библиотеки из ее самой. Собственно определение версии — это задача достаточно частная — на самом деле используя подобную технологию можно например маппить в память образ длл, модифицировать и. ну впрочем ограничимся получением версии.
И так начнем с точки входа (Dll Entry Point). Хочу обратить внимание, что я рассматриваю сейчас только случай когда загрузка библиотеки происходит динамически, если вам нужна статическия линковка — поэкспериментируйте самостоятельно.
Точка входа (DllEntryPoint) очень удобный иструмент для настройки загрузки и выгрузки вашей библиотеки. Функция точки входа вызывается при каждой загрузке и выгрузке библиотеки и получает три параметра — дескриптор библиотеки (HINSTANCE), флаг причины вызова(DWORD fwdreason) и детализация вызова (LPVOID lpvReserved). Хочу отметить, что если вы не используете VC-стиль для dll, то наименование и типы могут немного отличаться.
Как это использовать? Дескриптор библиотеки может быть использован при вызове различных функций — например той же функции получения версии файла.
Флаг загрузки может принимать следующие значения:
- DLL_PROCESS_ATTACH — При присоединении к адресному пространству текущего процесса, в результате его запуска или вызова функции LoadLibrary
- DLL_THREAD_ATTACH — Данный процесс создает новый поток
- DLL_PROCESS_DETACH — Завершение процесса либо вызов FreeLibrary
- DLL_THREAD_DETACH — Завершение потока
Параметр lpvReserved позволяет определить каким образом загружена библиотека — статически или динамически. Если параметр имеет значение отличное от NULL — библиотека загружена статически, если же NULL — то динамически (с использованием LoadLibrary/FreeLibrary).
Как это использовать? В качестве примера я приведу код, который позволяет получить номер версии библиотеки и вывести его в заголовке модального окна. Для этого вы должны создать новый проект типа DllWizard, Source Type — C++, Use VCL, VC++Style Dll. В проект добавьте юнит (File/New/Unit), сохраните его под названием loadforms.cpp. Так же добавьте форму, дайте ей название fmMain, файл сохраните как main.cpp. Из файла с DllEntryPoin можете прочесть и удалить обширный комментарий и сохранить файл как maindll.cpp, а сам проект — так как нравится вам — например versiontest. В принципе наименования файлов могут быть такими как нравится вам, в коде я буду придерживаться указанной схемы. В прикрепленном файле вы найдете исходные коды проекта.
Для получения версии файла используется функция, получающая в качестве параметра путь к файлу библиотеки и ссылку на строку, куда нужно записать результат. Код функции:
Функция Entry-Point библиотеки Dynamic-Link
DLL может дополнительно указывать функцию точки входа. При наличии система вызывает функцию точки входа каждый раз, когда процесс или поток загружает или выгружает библиотеку DLL. Его можно использовать для выполнения простых задач инициализации и очистки. Например, он может настроить локальное хранилище потока при создании нового потока и очистить его после завершения потока.
Если библиотека DLL связывается с библиотекой времени выполнения C, она может предоставить функцию точки входа и предоставить отдельную функцию инициализации. Дополнительные сведения см. в документации по библиотеке времени выполнения.
Если вы предоставляете собственную точку входа, см. раздел Функция DllMain . Имя DllMain — это заполнитель для определяемой пользователем функции. Необходимо указать фактическое имя, используемое при сборке библиотеки DLL. Дополнительные сведения см. в документации, входящей в состав средств разработки.
Вызов функции Entry-Point
Система вызывает функцию точки входа каждый раз, когда происходит одно из следующих событий:
- Процесс загружает библиотеку DLL. Для процессов, использующих динамическую компоновку во время загрузки, Библиотека DLL загружается во время инициализации процесса. Для процессов, использующих связывание во время выполнения, Библиотека DLL загружается до возврата LoadLibrary или LoadLibraryEx .
- Процесс выгружает библиотеку DLL. Библиотека DLL выгружается, когда процесс завершается или вызывается функция FreeLibrary , а счетчик ссылок становится нулевым. Если процесс завершается в результате выполнения функции терминатепроцесс или TerminateThread , система не вызывает функцию точки входа DLL.
- Новый поток создается в процессе, который загрузил библиотеку DLL. Функцию дисаблесреадлибрарикаллс можно использовать для отключения уведомлений при создании потоков.
- Поток процесса, который загрузил библиотеку DLL, завершается нормально, но не использует TerminateThread или терминатепроцесс. Когда процесс выгружает библиотеку DLL, функция точки входа вызывается только один раз для всего процесса, а не один раз для каждого существующего потока процесса. Дисаблесреадлибрарикаллс можно использовать для отключения уведомлений о завершении потоков.
Только один поток за раз может вызвать функцию точки входа.
Система вызывает функцию точки входа в контексте процесса или потока, который вызвал вызов функции. Это позволяет библиотеке DLL использовать свою функцию точки входа для выделения памяти в виртуальном адресном пространстве вызывающего процесса или для открытия дескрипторов, доступных процессу. Функция точки входа также может выделить память, которая является закрытой для нового потока, используя локальное хранилище потока (TLS). Дополнительные сведения о локальном хранилище потоков см. в разделе Локальное хранилище потока.
Определение функции Entry-Point
Функция точки входа DLL должна быть объявлена с соглашением о вызовах Standard-Call. Если точка входа DLL не объявлена должным образом, Библиотека DLL не загружается, а система отображает сообщение, указывающее, что точка входа DLL должна быть объявлена с помощью WINAPI.
В теле функции можно выполнять любое сочетание следующих сценариев, в которых была вызвана точка входа DLL:
- Процесс загружает библиотеку DLL (_ _ Подключение к процессу DLL).
- Текущий процесс создает новый поток (_ _ присоединение потока DLL).
- Поток завершается обычным образом ( _ _ Отключение потока DLL).
- Процесс выгружает библиотеку DLL (_ _ отсоединение процесса DLL).
Функция точки входа должна выполнять только простые задачи инициализации. Он не должен вызывать функцию LoadLibrary или LoadLibraryEx (или функцию, которая вызывает эти функции), так как это может создавать циклы зависимостей в порядке загрузки DLL. Это может привести к тому, что библиотека DLL будет использоваться до того, как система выполнила свой код инициализации. Аналогичным образом функция точки входа не должна вызывать функцию FreeLibrary (или функцию, которая вызывает FreeLibrary) во время завершения процесса, так как это может привести к тому, что библиотека DLL будет использоваться после того, как система выполнит свой код завершения.
Поскольку Kernel32.dll гарантированно загружается в адресное пространство процесса при вызове функции точки входа, вызов функций в Kernel32.dll не приводит к тому, что библиотека DLL будет использоваться до выполнения кода инициализации. Поэтому функция точки входа может создавать объекты синхронизации , такие как критические разделы и мьютексы, и использовать TLS, так как эти функции находятся в Kernel32.dll. Не рекомендуется вызывать функции реестра, например, так как они находятся в Advapi32.dll.
Вызов других функций может привести к проблемам, которые трудно диагностировать. Например, вызов функций User, Shell и COM может вызвать ошибки нарушения прав доступа, так как некоторые функции в их библиотеках DLL вызывают функцию LoadLibrary для загрузки других компонентов системы. И наоборот, вызов этих функций во время завершения может вызвать ошибки нарушения прав доступа, так как соответствующий компонент может быть уже выгружен или неинициализирован.
В следующем примере показано, как структурировать функцию точки входа DLL.
Entry-Point возвращаемое значение функции
Когда вызывается функция точки входа библиотеки DLL из-за загрузки процесса, функция возвращает значение true , чтобы указать на успешное выполнение. Для процессов, использующих связывание во время загрузки, возвращаемое значение false приводит к сбою инициализации процесса и завершению процесса. Для процессов, использующих связывание во время выполнения, возвращаемое значение FALSE приводит к тому, что функция LoadLibrary или LoadLibraryEx возвращает значение NULL, означающее сбой. (Система немедленно вызывает функцию точки входа с _ _ отсоединением процесса DLL и выгружает библиотеку DLL.) Возвращаемое значение функции точки входа не учитывается, если функция вызывается по какой-либо другой причине.
DllMain entry point
An optional entry point into a dynamic-link library (DLL). When the system starts or terminates a process or thread, it calls the entry-point function for each loaded DLL using the first thread of the process. The system also calls the entry-point function for a DLL when it is loaded or unloaded using the LoadLibrary and FreeLibrary functions.
Example
There are significant limits on what you can safely do in a DLL entry point. See General Best Practices for specific Windows APIs that are unsafe to call in DllMain. If you need anything but the simplest initialization then do that in an initialization function for the DLL. You can require applications to call the initialization function after DllMain has run and before they call any other functions in the DLL.
Syntax
Parameters
A handle to the DLL module. The value is the base address of the DLL. The HINSTANCE of a DLL is the same as the HMODULE of the DLL, so hinstDLL can be used in calls to functions that require a module handle.
The reason code that indicates why the DLL entry-point function is being called. This parameter can be one of the following values.
Value | Meaning |
---|---|
DLL_PROCESS_ATTACH 1 | The DLL is being loaded into the virtual address space of the current process as a result of the process starting up or as a result of a call to LoadLibrary. DLLs can use this opportunity to initialize any instance data or to use the TlsAlloc function to allocate a thread local storage (TLS) index. The lpReserved parameter indicates whether the DLL is being loaded statically or dynamically. |
DLL_PROCESS_DETACH 0 | The DLL is being unloaded from the virtual address space of the calling process because it was loaded unsuccessfully or the reference count has reached zero (the processes has either terminated or called FreeLibrary one time for each time it called LoadLibrary). The lpReserved parameter indicates whether the DLL is being unloaded as a result of a FreeLibrary call, a failure to load, or process termination. The DLL can use this opportunity to call the TlsFree function to free any TLS indices allocated by using TlsAlloc and to free any thread local data. Note that the thread that receives the DLL_PROCESS_DETACH notification is not necessarily the same thread that received the DLL_PROCESS_ATTACH notification. |
DLL_THREAD_ATTACH 2 | The current process is creating a new thread. When this occurs, the system calls the entry-point function of all DLLs currently attached to the process. The call is made in the context of the new thread. DLLs can use this opportunity to initialize a TLS slot for the thread. A thread calling the DLL entry-point function with DLL_PROCESS_ATTACH does not call the DLL entry-point function with DLL_THREAD_ATTACH. Note that a DLL’s entry-point function is called with this value only by threads created after the DLL is loaded by the process. When a DLL is loaded using LoadLibrary, existing threads do not call the entry-point function of the newly loaded DLL. |
DLL_THREAD_DETACH 3 | A thread is exiting cleanly. If the DLL has stored a pointer to allocated memory in a TLS slot, it should use this opportunity to free the memory. The system calls the entry-point function of all currently loaded DLLs with this value. The call is made in the context of the exiting thread. |
If fdwReason is DLL_PROCESS_ATTACH, lpvReserved is NULL for dynamic loads and non-NULL for static loads.
If fdwReason is DLL_PROCESS_DETACH, lpvReserved is NULL if FreeLibrary has been called or the DLL load failed and non-NULL if the process is terminating.
Return value
When the system calls the DllMain function with the DLL_PROCESS_ATTACH value, the function returns TRUE if it succeeds or FALSE if initialization fails. If the return value is FALSE when DllMain is called because the process uses the LoadLibrary function, LoadLibrary returns NULL. (The system immediately calls your entry-point function with DLL_PROCESS_DETACH and unloads the DLL.) If the return value is FALSE when DllMain is called during process initialization, the process terminates with an error. To get extended error information, call GetLastError.
When the system calls the DllMain function with any value other than DLL_PROCESS_ATTACH, the return value is ignored.
Remarks
DllMain is a placeholder for the library-defined function name. You must specify the actual name you use when you build your DLL. For more information, see the documentation included with your development tools.
During initial process startup or after a call to LoadLibrary, the system scans the list of loaded DLLs for the process. For each DLL that has not already been called with the DLL_PROCESS_ATTACH value, the system calls the DLL’s entry-point function. This call is made in the context of the thread that caused the process address space to change, such as the primary thread of the process or the thread that called LoadLibrary. Access to the entry point is serialized by the system on a process-wide basis. Threads in DllMain hold the loader lock so no additional DLLs can be dynamically loaded or initialized.
If the DLL’s entry-point function returns FALSE following a DLL_PROCESS_ATTACH notification, it receives a DLL_PROCESS_DETACH notification and the DLL is unloaded immediately. However, if the DLL_PROCESS_ATTACH code throws an exception, the entry-point function will not receive the DLL_PROCESS_DETACH notification.
There are cases in which the entry-point function is called for a terminating thread even if the entry-point function was never called with DLL_THREAD_ATTACH for the thread:
- The thread was the initial thread in the process, so the system called the entry-point function with the DLL_PROCESS_ATTACH value.
- The thread was already running when a call to the LoadLibrary function was made, so the system never called the entry-point function for it.
When a DLL is unloaded from a process as a result of an unsuccessful load of the DLL, termination of the process, or a call to FreeLibrary, the system does not call the DLL’s entry-point function with the DLL_THREAD_DETACH value for the individual threads of the process. The DLL is only sent a DLL_PROCESS_DETACH notification. DLLs can take this opportunity to clean up all resources for all threads known to the DLL.
When handling DLL_PROCESS_DETACH, a DLL should free resources such as heap memory only if the DLL is being unloaded dynamically (the lpReserved parameter is NULL). If the process is terminating (the lpvReserved parameter is non-NULL), all threads in the process except the current thread either have exited already or have been explicitly terminated by a call to the ExitProcess function, which might leave some process resources such as heaps in an inconsistent state. In this case, it is not safe for the DLL to clean up the resources. Instead, the DLL should allow the operating system to reclaim the memory.
If you terminate a process by calling TerminateProcess or TerminateJobObject, the DLLs of that process do not receive DLL_PROCESS_DETACH notifications. If you terminate a thread by calling TerminateThread, the DLLs of that thread do not receive DLL_THREAD_DETACH notifications.
The entry-point function should perform only simple initialization or termination tasks. It must not call the LoadLibrary or LoadLibraryEx function (or a function that calls these functions), because this may create dependency loops in the DLL load order. This can result in a DLL being used before the system has executed its initialization code. Similarly, the entry-point function must not call the FreeLibrary function (or a function that calls FreeLibrary) during process termination, because this can result in a DLL being used after the system has executed its termination code.
Because Kernel32.dll is guaranteed to be loaded in the process address space when the entry-point function is called, calling functions in Kernel32.dll does not result in the DLL being used before its initialization code has been executed. Therefore, the entry-point function can call functions in Kernel32.dll that do not load other DLLs. For example, DllMain can create synchronization objects such as critical sections and mutexes, and use TLS. Unfortunately, there is not a comprehensive list of safe functions in Kernel32.dll.
Calling functions that require DLLs other than Kernel32.dll may result in problems that are difficult to diagnose. For example, calling User, Shell, and COM functions can cause access violation errors, because some functions load other system components. Conversely, calling functions such as these during termination can cause access violation errors because the corresponding component may already have been unloaded or uninitialized.
Because DLL notifications are serialized, entry-point functions should not attempt to communicate with other threads or processes. Deadlocks may occur as a result.
If your DLL is linked with the C run-time library (CRT), the entry point provided by the CRT calls the constructors and destructors for global and static C++ objects. Therefore, these restrictions for DllMain also apply to constructors and destructors and any code that is called from them.
Consider calling DisableThreadLibraryCalls when receiving DLL_PROCESS_ATTACH, unless your DLL is linked with static C run-time library (CRT).