Удаление статических объектов в dll

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Rycharg
Сообщения: 28
Зарегистрирован: 15 апр 2009, 14:23
Откуда: SPb

Приветствую.
Ситуация такая:
Приложение загружает x.dll в которой создаётся статический объект А. Метод А.Load() загружает y.dll в которой создаётся статический объект В. y.dll выгружается в деструкторе А, а деструктор В вызывает A.DoSmth(). Проблема в том, что если приложении вызовет FreeLibrary для x.dll, то к моменту, когда деструктор В получит управление, объект А будет уже уничтожен. С чем это может быть связано?
Я полагал, когда FreeLibrary возвращает управление, все статические объекты уже уничтожены. Однако, в данном случае, это не так. Есть подозрение, что это как-то связано с “рекурсивным” вызовом FreeLibrary.
Если кто-нибудь пояснит, чем обусловлено такое поведение – буду благодарен.
BBB
Сообщения: 1298
Зарегистрирован: 27 дек 2005, 13:37

Не может такого быть, что LoadLibrary возвращает управление в вызвавший ее модуль еще до того, как библиотека окончательно выгрузится? Т.е. LoadLibrary только НАЧИНАЕТ выгрузку указанной DLL, но не дожидается ее окончания. Т.е. выгрузка DLL происходит параллельно с работой вызвавшего LoadLibrary процесса.
Rycharg
Сообщения: 28
Зарегистрирован: 15 апр 2009, 14:23
Откуда: SPb

Вот пример кода.
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;
}
x.cpp

Код: Выделить всё

#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; }
y.cpp

Код: Выделить всё

#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;
}
head.h

Код: Выделить всё

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 формирует очередь объектов на удаление. И если в ходе удаления FreeLibrary запускается снова, то очередь сформированная ею добавляется в конец уже существующей, а не удаляется сразу. Но это лишь подозрение.
Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

FreeLibrary() только "говорит" системе, что библиотека (dll) больше не нужна. Выгружает её система. Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё. Поставь там Sleep на несколько минут и дождись его завершения.
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
Rycharg
Сообщения: 28
Зарегистрирован: 15 апр 2009, 14:23
Откуда: SPb

Поспал 5 мин. Ошибки как небыло, так и нет. Может, у меня система ленивая? ;) Но нет. Если в main.cpp после строки

Код: Выделить всё

   FreeLibrary( lib );
добавить

Код: Выделить всё

   A->DoSmth("Bla-bla");
( предварительно убрав из деструктора 'а' всё, кроме Sleep ),
то ошибка не заставит себя ждать.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

&quot писал(а):FreeLibrary() только "говорит" системе, что библиотека (dll) больше не нужна. Выгружает её система. Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё. Поставь там Sleep на несколько минут и дождись его завершения.
Airhand, где ты такое вычитал? Или сам придумал? Вообще-то, если бы FreeLibrary работала асинхронно, то это бы крайне усложнило работу с ней. На самом деле все WinAPI функции работают синхронно. В случае FreeLibrary это доказывается хотя бы тем фактом, что функция имеет возвращаемое значение.

Вот что написано об этой функции в 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" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Rycharg
Сообщения: 28
Зарегистрирован: 15 апр 2009, 14:23
Откуда: SPb

Romeo, ошибку я отследил давно. Меня интересует причина этой ошибки. Как и почему FreeLibrary это делает. У меня только гипотезы с элементами научной фантастики.
Момент, который я не могу понять - почему FreeLibrary вызванная из ~a возвращает управление( и true!) не заходя в DllMain освобождаемой библиотеки и только после выходе из деструктора DllMain полуачет управление. Но к этому моменту вызвавшая библиотека уже удалена. Вот тут-то и получается вызов непойми-чего, что лежит там, где когда-то была таблица виртуальных функций A. Результат - полный вылет без пояснения причин.
Аватара пользователя
Airhand
Сообщения: 239
Зарегистрирован: 06 окт 2005, 16:21
Откуда: Dnepropetrovsk

Romeo писал(а):Ни одного упоминания о том, что функция работает асинхронно я не вижу.
Где ты в моём посте вычитал про синхронность или асинхранность ? Лишь бы придраться. Я написал то, что сказано в MSDN. Только своими словами и по-русский. Или ты будешь спорить с тем, что FreeLibrary() не выгружает dll-ку, а это делает система ?
Оптимизация по скорости:
#define while if
Оптимизация по размеру:
#define struct union
Rycharg
Сообщения: 28
Зарегистрирован: 15 апр 2009, 14:23
Откуда: SPb

Airhand, а что Вы подразумеваете под "система"?
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

&quot писал(а):Поэтому у тебя в примере нет ошибки в x.cpp: библиотека не выгрузилась ещё
Из этих слов я понял, что вызовом FreeLibrary мы сказали системе, что пора выгружать, затем FreeLibrary возвратилась и наша программа продолжила исполнение, а DLL выгрузилась на самом деле позже, не во время вызова FreeLibrary. Отсюда мы делаем вывод, что наш код, и код, который выгружает DLL, работают параллельно, то есть асинхронно.

Я не придираюсь, я лишь делаю выводы из твоих слов.

Rycharg, статики - зло. А статики, расположенные в динамических билиотеках, которые ещё и грузяться/выгружаются в конструкторе другого статика - это вообще убийство. Предлагаю просто сделать рефакторинг. Это единственная архитектура, которая удовлетворяет твоим нуждам?
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Ответить