Иллюстрированный самоучитель по Visual Studio .NET

Как работает DLL

Вы уже знаете, что созданный и подключенный компоновщиком динамический модуль система интегрирует в пространство другого (клиентского) процесса, загрузив его по определенному базовому адресу. Любая динамически загружаемая библиотека экспортирует функции, которые пишутся в расчете на то, что их будет вызывать клиентское приложение или другая DLL. Глобальная функция DllMain представляет собой точку входа в динамически подключаемую библиотеку. Она является некоторого рода заглушкой (placeholder) для реального, определяемого библиотекой имени функции. Первый параметр DllMain подан операционной системой и представляет собой Windows-описатель DLL. Его можно использовать при вызове функций, требующих этот описатель, например при вызове GetModuleFileName. Второй параметр указывает причину вызова DLL. Он может принимать одно из четырех значений:

  • DLL_PROCESS_ATTACH – указывает на то, что DLL загружается в виртуальное адресное пространство процесса, так как стартовал сам процесс (неявный вызов DLL) или была вызвана функция LoadLibrary (явный вызов DLL).
  • DLL_THREAD_ATTACH – указывает на то, что текущий процесс создает новый поток (thread). В этот момент система вызывает все DLL, которые уже загружены в пространстве процесса, с тем чтобы они учли новый поток в TLS-сло-тах (Thread Local Storage).
  • DLL_THREAD_DETACH – указывает на то, что поток завершается и DLL может освободить динамические ресурсы, связанные с данным потоком, если они были.
  • DLL_PROCESS_DETACH – указывает на то, что DLL выгружается из адресного пространства процесса либо в результате завершения процесса, либо потому, что процесс вызвал функцию FreeLibrary. В этом случае DLL может освободить память (TLS).

Если DllMain вернет FALSE или 0, то клиентское приложение завершится с кодом ошибки. Характерно, что стратегия работы с СОМ-объектами сходна со стратегией, используемой при работе с DLL. Последняя заключается в том, что каждый вызов функции LoadLibrary увеличивает на единицу счетчик числа пользователей библиотеки. Вызов функции FreeLibrary уменьшает значение счетчика. Обнаружив, что счетчик числа пользователей равен нулю, операционная система автоматически выгрузит ее. Если после этого вызвать какую-либо экспортируемую DLL функцию, то возникнет исключительная ситуация Access Violation, так как код по указанному адресу уже не отображается на адресное пространство процесса.

Возвращаясь к коду, созданному мастером ATL Project wizard, отметим, что кроме DllMain, модуль экспортирует еще 4 функции: DllRegisterServer, DllUnregisterServer, DllCanUnloadNow, DllGetClassObject. Полезно открыть, с помощью окна Solution Explorer файл ATLGL.def, который создал и поместил в папку проекта мастер. Этот файл используется компоновщиком при создании lib-файлов и ехр-файлов, содержащих информацию о DLL и экспортируемых ею функциях. Все эти функции имеют тип STDAPI. На самом деле STDAPI – это макроподстановка, заданная в файле заголовков WinNT.h. С помощью этого файла вы можете самостоятельно расшифровать макрос STDAPI. Он разворачивается (expanded) в такой комплексный описатель:

extern "С" HRESULT _ stdcall

Описатель extern "С" означает, что при вызове функция будет использовать имя в стиле языка С, а не C++, то есть описатель отменяет декорацию имен, производимую компилятором C++ по умолчанию.

Примечание
Компилятор C++ использует специальную декорацию имен, для того чтобы отличать overloaded-функции, имеющие одинаковые имена, но разные прото-. типы. Например, вызов: int func(int a, double b); в результате декорации становится: _func@12. Число 12 описывает количество байт, занимаемых списком аргументов. Такая условность называется naming convention (соглашение об именах). Есть и другая конвенция – calling convention (соглашение о связях), которая определяет договоренность о передаче параметров при вызове Win32 API-функций. Описатель _stdcall относится к этой группе. Он определяет: порядок передачи аргументов (справа налево): то, что аргументы передаются по значению (by value), что вызываемая функция должна сама выбирать аргументы из стека и что трансляция регистра символов, верхнего или нижнего, не производится
.

Функция DllCanUnloadNow определяет, используется ли данная DLL в данный момент. Если нет, то вызывающий процесс может безопасно выгрузить ее из памяти. Функция DllGetClassObject с помощью третьего параметра (LPVOID* ppv) возвращает адрес так называемой фабрики классов, которая умеет создавать СОМ-объекты, по известному CLSID – уникальному идентификатору объекта.

Откройте файл ATLGLJ.c и убедитесь, что он пуст. Этот файл будет заполнен кодами компилятором MIDL, о котором мы уже говорили ранее. Запустите приложение (CTRL + F5). Компилятор и компоновщик создадут исполняемый модуль типа DLL, но загрузчик не будет знать в рамках какого процесса (контейнера) следует запустить его на отладку.

Примечание
В этот момент Studio .NET запросит имя ехе-файла, то есть модуля или процесса в пространство которого должна быть загружена созданная компоновщиком DLL. Вы можете воспользоваться выпадающим списком для выбора строки Browse, которая даст диалог по выбору файла. Найдите с его помощью стандартный контейнер для отладки элементов ActiveX (tstcon32.exe), поставляемый вместе со Studio .NET по адресу: …\MicrosoftVisualStudio .NET\Common7\Tools и нажмите Open, а затем ОК
.

В рамках тестового контейнера можно отлаживать работу элементов ActiveX, OLE-controls и других СОМ-объектов. Но сейчас наш объект еще не готов к этому, так как мы не создали СОМ-класса, инкапсулирующего желаемые интерфейсы. Поэтому закройте тестовый контейнер, вновь откройте в рамках Studio .NET уже существующий IDL-файл (Interface Description Language file) ATLGLidl и просмотрите коды, описывающие интерфейс, СОМ-класс и библиотеку типов. Вы помните, что этот файл обрабатывается компилятором MIDL, который на его основе создает другие файлы. Откройте файл ATLGM.c и убедитесь, что теперь он не пуст. Его содержимое было создано компилятором MIDL. В данный момент файл ATLGM.c содержит только один идентификатор библиотеки, который регистрируется с помощью макроподстановки MIDL_DEFINE_GUID.

Если Вы заметили ошибку, выделите, пожалуйста, необходимый текст и нажмите CTRL + Enter, чтобы сообщить об этом редактору.