Что происходит с глобальными переменными, объявленными в DLL?
Вопрос
Допустим, я пишу DLL на C++ и объявляю глобальный объект класса с нетривиальным деструктором.Будет ли вызываться деструктор при выгрузке DLL?
Решение
В Windows C++ DLL все глобальные объекты (включая статические члены классов) будут созданы непосредственно перед вызовом DllMain с помощью DLL_PROCESS_ATTACH и будут уничтожены сразу после вызова DllMain с помощью DLL_PROCESS_DETACH.
Теперь вам необходимо рассмотреть три проблемы:
0 — Конечно, глобальные неконстантные объекты — это зло (но вы это уже знаете, поэтому я не буду упоминать многопоточность, блокировки, объекты-боги и т. д.)
1 — Порядок построения объектов или различных единиц компиляции (т.е.CPP-файлы) не гарантируется, поэтому вы не можете надеяться, что объект A будет создан раньше B, если два объекта создаются в двух разных CPP.Это важно, если B зависит от A.Решение состоит в том, чтобы переместить все глобальные объекты в один и тот же файл CPP, поскольку внутри одной и той же единицы компиляции порядок создания объектов будет порядком создания (и обратным порядку уничтожения).
2 - Есть вещи, которые запрещено делать в DllMain.Эти вещи, вероятно, тоже запрещены в конструкторах.Поэтому избегайте блокировки чего-либо.См. превосходный блог Рэймонда Чена на эту тему:
http://blogs.msdn.com/oldnewthing/archive/2004/01/27/63401.aspx
http://blogs.msdn.com/oldnewthing/archive/2004/01/28/63880.aspx
В этом случае может быть интересна ленивая инициализация:Классы остаются в «неинициализированном» состоянии (внутренние указатели имеют значение NULL, логические значения являются ложными и т. д.) до тех пор, пока вы не вызовете один из их методов, после чего они инициализируются сами.Если вы используете эти объекты внутри функции main (или одной из функций-потомков функции main), все будет в порядке, поскольку они будут вызываться после выполнения DllMain.
3. Конечно, если некоторые глобальные объекты в DLL A зависят от глобальных объектов в DLL B, вам следует очень внимательно относиться к порядку загрузки DLL и, следовательно, к зависимостям.В этом случае библиотеки DLL с прямыми или косвенными циклическими зависимостями вызовут у вас безумное количество головной боли.Лучшее решение — разорвать циклические зависимости.
P.S.:Обратите внимание, что в C++ конструктор может выдавать исключение, и вам не нужно исключение в середине загрузки DLL, поэтому убедитесь, что ваши глобальные объекты не будут использовать исключение без очень и очень веской причины.Поскольку правильно написанные деструкторы не имеют права выдавать исключение, выгрузка DLL в этом случае должна пройти нормально.
Другие советы
На этой странице Microsoft подробно описаны инициализация DLL и уничтожение глобальных переменных:
http://msdn.microsoft.com/en-us/library/988ye33t.aspx
Если вы хотите увидеть фактический код, который выполняется при связывании .dll, взгляните на %ProgramFiles%\Visual Studio 8\vc\crt\src\dllcrt0.c
.
После проверки деструкторы будут вызываться через _cexit()
когда внутренний счетчик ссылок, поддерживаемый dll CRT, достигает нуля.
Его следует вызывать, когда приложение завершает работу или выгружается DLL, в зависимости от того, что наступит раньше.Обратите внимание, что это в некоторой степени зависит от фактической среды выполнения, с которой вы компилируете.
Кроме того, остерегайтесь нетривиальных деструкторов, поскольку здесь возникают проблемы как со временем, так и с порядком.Ваша DLL может быть выгружена после DLL, на которую опирается ваш деструктор, что, очевидно, вызовет проблемы.
Когда вызывается DllMain с параметром fdwReason = DLL_PROCESS_DETACH, это означает, что DLL выгружается приложением.Это время до вызова деструктора глобальных/статических объектов.
В Windows находятся бинарные файлы изображений с расширением *.exe, *.dll. PE-форматТакие файлы имеют Entry Point.Вы можете просмотреть его с помощью инструмента дампа, например
dumpbin /headers имя_dll.dll
Если вы используете время выполнения C от Microsoft, то ваша точка входа будет чем -то вроде *CRTStartup или *dllmaincrtStartup
Такие функции выполняют инициализацию среды выполнения C и C++ и делегируют выполнение (main, WinMain) или DllMain соответственно.
Если вы используете компилятор Microsoft VC, вы можете посмотреть исходный код этой функции в вашем каталоге VC:
- crt0.c
- dllcrt0.c
DllMainCRTStartup обрабатывает все, что необходимо для инициализации/деинициализации глобальных переменных из разделов .data в обычном сценарии, когда он получает уведомление DLL_PROCESS_DETACH во время выгрузки dll.Например:
- main или WinMain потока запуска программы возвращает поток управления
- вы явно вызываете FreeLibrary, а счетчик использования dll равен нулю