Удаление статических объектов в dll
Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain
Приветствую.
Ситуация такая:
Приложение загружает x.dll в которой создаётся статический объект А. Метод А.Load() загружает y.dll в которой создаётся статический объект В. y.dll выгружается в деструкторе А, а деструктор В вызывает A.DoSmth(). Проблема в том, что если приложении вызовет FreeLibrary для x.dll, то к моменту, когда деструктор В получит управление, объект А будет уже уничтожен. С чем это может быть связано?
Я полагал, когда FreeLibrary возвращает управление, все статические объекты уже уничтожены. Однако, в данном случае, это не так. Есть подозрение, что это как-то связано с “рекурсивным” вызовом FreeLibrary.
Если кто-нибудь пояснит, чем обусловлено такое поведение – буду благодарен.
Ситуация такая:
Приложение загружает x.dll в которой создаётся статический объект А. Метод А.Load() загружает y.dll в которой создаётся статический объект В. y.dll выгружается в деструкторе А, а деструктор В вызывает A.DoSmth(). Проблема в том, что если приложении вызовет FreeLibrary для x.dll, то к моменту, когда деструктор В получит управление, объект А будет уже уничтожен. С чем это может быть связано?
Я полагал, когда FreeLibrary возвращает управление, все статические объекты уже уничтожены. Однако, в данном случае, это не так. Есть подозрение, что это как-то связано с “рекурсивным” вызовом FreeLibrary.
Если кто-нибудь пояснит, чем обусловлено такое поведение – буду благодарен.
Не может такого быть, что LoadLibrary возвращает управление в вызвавший ее модуль еще до того, как библиотека окончательно выгрузится? Т.е. LoadLibrary только НАЧИНАЕТ выгрузку указанной DLL, но не дожидается ее окончания. Т.е. выгрузка DLL происходит параллельно с работой вызвавшего LoadLibrary процесса.
Вот пример кода.
main.cpp
x.cpp
y.cpp
head.h
У меня сложилось подозрение, что FreeLibrary формирует очередь объектов на удаление. И если в ходе удаления FreeLibrary запускается снова, то очередь сформированная ею добавляется в конец уже существующей, а не удаляется сразу. Но это лишь подозрение.
main.cpp
Код: Выделить всё
#include "head.h"
int main(int argc, char* argv[])
{
HANDLE lib;
a_base *A;
lib = LoadLibrary( "x.dll" );
A = ( ( a_base* __export __stdcall (*)() )
GetProcAddress( lib, "GetA" ) )();
A->Load();
cout << "A was loaded."<< endl;
char a;
cin >> a;
FreeLibrary( lib );
cin >> a;
return 0;
}
Код: Выделить всё
#include "head.h"
//---------------------------------------------------------------------------
class a: public a_base{
private:
HANDLE lib;
b_base* B;
public:
a() : a_base(1), lib(0){}
virtual ~a();
virtual void DoSmth(char* text);
virtual void* Load ();
} A;
//---------------------------------------------------------------------------
a::~a(){
this->DoSmth("~loader()");
if( lib ) FreeLibrary( lib );
this->DoSmth("x.dll was unloaded.");
//
B->AreYouAlive(); // В этом месте должна быть ошибка, но её не происходит.
Sleep(1000); // Если бы удаление происходило параллельно, то
B->AreYouAlive(); // здесь должна бы быть оштбка. Или я ошибаюсь?
//
}
//---------------------------------------------------------------------------
void* a::Load(){
if( !lib ) lib = LoadLibrary( "y.dll" );
if(!B) B = (b_base*) ( ( void* __import __stdcall (*)(a*) )
GetProcAddress( lib, "get" ) )( &A );
return B;
}
//---------------------------------------------------------------------------
void a: :D oSmth(char* text){ cout << text <<endl; }
//---------------------------------------------------------------------------
extern "C" a_base* __export __stdcall GetA(){ return &A; }
Код: Выделить всё
#include "head.h"
//---------------------------------------------------------------------------
class b : public b_base{
private:
a_base* Owner;
char Status[20];
public:
b(){ memcpy( Status, "A alive!", 9); }
~b();
virtual void AreYouAlive();
void Init(a_base* owner){ Owner = owner; Owner->DoSmth("b::Init()");}
} B;
//---------------------------------------------------------------------------
b::~b(){
memcpy( Status, "A was unloaded.", 16);
Owner->PrintFlag(); // Печатает 0 или что угодно, значит память отдана системе.
Owner->DoSmth("~b()"); // В этом месте программа рушится без пояснения причин.
}
void b::AreYouAlive(){ Owner->DoSmth( Status ); }
extern "C" void* __export __stdcall get(a_base* owner){
B.Init(owner);
return &B;
}
Код: Выделить всё
class a_base{
private:
int flag;
public:
a_base(int Flag) : flag( Flag ){}
virtual ~a_base(){ flag = 0; }
virtual void DoSmth(char* text) = 0;
virtual void* Load ( ) = 0;
void PrintFlag(){ cout << flag << endl; }
};
//---------------------------------------------------------------------------
class b_base{
public:
virtual ~b_base(){}
virtual void AreYouAlive() = 0;
};
FreeLibrary() только "говорит" системе, что библиотека (dll) больше не нужна. Выгружает её система. Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё. Поставь там Sleep на несколько минут и дождись его завершения.
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
#define while if
Оптимизация по размеру:
#define struct union
Поспал 5 мин. Ошибки как небыло, так и нет. Может, у меня система ленивая?
Но нет. Если в main.cpp после строки
добавить
( предварительно убрав из деструктора 'а' всё, кроме Sleep ),
то ошибка не заставит себя ждать.

Код: Выделить всё
FreeLibrary( lib );
Код: Выделить всё
A->DoSmth("Bla-bla");
то ошибка не заставит себя ждать.
- Romeo
- Сообщения: 3126
- Зарегистрирован: 02 мар 2004, 17:25
- Откуда: Крым, Севастополь
- Контактная информация:
Airhand, где ты такое вычитал? Или сам придумал? Вообще-то, если бы FreeLibrary работала асинхронно, то это бы крайне усложнило работу с ней. На самом деле все WinAPI функции работают синхронно. В случае FreeLibrary это доказывается хотя бы тем фактом, что функция имеет возвращаемое значение." писал(а):FreeLibrary() только "говорит" системе, что библиотека (dll) больше не нужна. Выгружает её система. Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё. Поставь там Sleep на несколько минут и дождись его завершения.
Вот что написано об этой функции в MSDN:
Each process maintains a reference count for each loaded library module. This reference count is incremented each time LoadLibrary is called and is decremented each time FreeLibrary is called. A DLL module loaded at process initialization due to load-time dynamic linking has a reference count of one. This count is incremented if the same module is loaded by a call to LoadLibrary.
Before unmapping a library module, the system enables the DLL to detach from the process by calling the DLL's DllMain function, if it has one, with the DLL_PROCESS_DETACH value. Doing so gives the DLL an opportunity to clean up resources allocated on behalf of the current process. After the entry-point function returns, the library module is removed from the address space of the current process.
It is not safe to call FreeLibrary from DllMain. For more information, see the Remarks section in DllMain.
Calling FreeLibrary does not affect other processes using the same library module.
Ни одного упоминания о том, что функция работает асинхронно я не вижу.
Rycharg, попробуй расставить брекпоинты в ключевых местах, а также в функциях DllMain у двух твоих библиотек. После этого запусти программу в дебаге и посмотри в каком порядке происходит прерывание по точкам останова. Возможно, это тебе поможет отследить ошибку.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Romeo, ошибку я отследил давно. Меня интересует причина этой ошибки. Как и почему FreeLibrary это делает. У меня только гипотезы с элементами научной фантастики.
Момент, который я не могу понять - почему FreeLibrary вызванная из ~a возвращает управление( и true!) не заходя в DllMain освобождаемой библиотеки и только после выходе из деструктора DllMain полуачет управление. Но к этому моменту вызвавшая библиотека уже удалена. Вот тут-то и получается вызов непойми-чего, что лежит там, где когда-то была таблица виртуальных функций A. Результат - полный вылет без пояснения причин.
Момент, который я не могу понять - почему FreeLibrary вызванная из ~a возвращает управление( и true!) не заходя в DllMain освобождаемой библиотеки и только после выходе из деструктора DllMain полуачет управление. Но к этому моменту вызвавшая библиотека уже удалена. Вот тут-то и получается вызов непойми-чего, что лежит там, где когда-то была таблица виртуальных функций A. Результат - полный вылет без пояснения причин.
Где ты в моём посте вычитал про синхронность или асинхранность ? Лишь бы придраться. Я написал то, что сказано в MSDN. Только своими словами и по-русский. Или ты будешь спорить с тем, что FreeLibrary() не выгружает dll-ку, а это делает система ?Romeo писал(а):Ни одного упоминания о том, что функция работает асинхронно я не вижу.
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
#define while if
Оптимизация по размеру:
#define struct union
Airhand, а что Вы подразумеваете под "система"?
- Romeo
- Сообщения: 3126
- Зарегистрирован: 02 мар 2004, 17:25
- Откуда: Крым, Севастополь
- Контактная информация:
Из этих слов я понял, что вызовом FreeLibrary мы сказали системе, что пора выгружать, затем FreeLibrary возвратилась и наша программа продолжила исполнение, а DLL выгрузилась на самом деле позже, не во время вызова FreeLibrary. Отсюда мы делаем вывод, что наш код, и код, который выгружает DLL, работают параллельно, то есть асинхронно." писал(а):Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё
Я не придираюсь, я лишь делаю выводы из твоих слов.
Rycharg, статики - зло. А статики, расположенные в динамических билиотеках, которые ещё и грузяться/выгружаются в конструкторе другого статика - это вообще убийство. Предлагаю просто сделать рефакторинг. Это единственная архитектура, которая удовлетворяет твоим нуждам?
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.