Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Для новичков > Корректная выгрузка пакеа bpl


Автор: Avers 16.12.2008, 17:32
У меня имеется пакет bpl (Пакет1) и приложение, загружающее ("вручную") Пакет1.
Получение интерфейса происходит через экспортируемую процедуру, как описано http://forum.vingrad.ru/forum/topic-238685.html (сама тема особого интереса не представляет и к делу не имеет отношения).
В пакете хранится переменная-указатель на создаваемый экземпляр некоторой формы. В модуле пакета (единственном) есть код:
Код

...
initialization

RegisterClass(TMyForm);

finalization

MyForm.Free;
UnRegisterClass(TMyForm);


При выгрузке пакета (вручную из приложения) 
Код

  UnloadPackage(MyPackage);

Все нормально. Но при завершении программы вознкает ошибка.... 
Воспользовался EuricaLog - выяснил, что ошибка из RTL100. В графе "модуль/процедура" написано _IntfClear
В чем может быть причина ошибки???
И как поступить со следующей функцией, при выгрузке пакета:
Код

function GetMyFormInt: IMyFormInt;
begin
  if MyFormInterface = nil then
    begin
      MyForm := TMyForm.Create(nil);
      MyFormInterface := MyForm;
    end;
  Result := MyFormInterface;
end;

exports
  GetMyFormInt name CProcName;

где CProcName - некоторая константа.

Автор: Bose 16.12.2008, 18:26
Avers, а интерфейс где-нибудь освобождается?

Автор: Avers 17.12.2008, 12:05
Bose, подумал и об этом. И просто убрал данную переменную, т.е. код стал следующим:
Код

function GetMyFormInt: IMyFormInt;
begin
  if MyForm = nil then
    begin
      MyForm := TMyForm.Create(nil);
    end;
  Result := MyForm;
end;

exports
  GetMyFormInt name CProcName;

В приложении интерфейс просто присваивается nil перед выгрузкой пакета.

Добавлено через 11 секунд
Не помогло :(

Автор: CodeMonkey 17.12.2008, 12:29
Цитата(Avers @  16.12.2008,  17:32 Найти цитируемый пост)
Воспользовался EuricaLog - выяснил, что ошибка из RTL100. В графе "модуль/процедура" написано _IntfClear

Вышу по тексту что там? Кто вызвал _IntfClear?
_IntfClear - это очистка интерфейса.
Т.е. 
Код
var
  i: ISomeInterface;

...

  i := nil; // <- здесь вызывается _IntfClear (если это последняя ссылка)


Иными словами - вы выгрузили пакет, но у вас где-то осталась ссылка на интерфейс из этого пакета. Перед выгрузкой пакета вам нужно явно освободить все переменные. Посмотрите внимательно, не появились ли где у вас временные переменные. Например: http://www.delphikingdom.ru/asp/viewitem.asp?catalogid=1312.

Приведите пример, как вы получаете и используете интерфейсы.

Добавлено через 2 минуты и 51 секунду
Ещё обратите внимание: для TForm у вас есть смешение и ручного управления жизненным циклом (MyForm.Free) и автоматического (function GetMyFormInt: IMyFormInt). Не запутайтесь тут. Как бы не получилось так, что сперва удаляется форма вызовом MyForm.Free, а только потом освобождается её интерфейс.

Автор: MetalFan 17.12.2008, 12:34
Цитата(Avers @  16.12.2008,  17:32 Найти цитируемый пост)
finalization
MyForm.Free;
UnRegisterClass(TMyForm);

а накой убиваешь еще раз форму, если интерфейс ее прибьет, когда его счетчик обнулиться?

Автор: Avers 17.12.2008, 12:40
Цитата(CodeMonkey @  17.12.2008,  12:29 Найти цитируемый пост)
Вышу по тексту что там? Кто вызвал _IntfClear?

Не поверите - ничего. В стеке (EurikaLog'а) запись только о данном модуле.

Добавлено через 3 минуты и 28 секунд
Цитата(MetalFan @  17.12.2008,  12:34 Найти цитируемый пост)
а накой убиваешь еще раз форму, если интерфейс ее прибьет, когда его счетчик обнулиться?

Если данную строчку убрать, то шибка становится еще жестче=))) вылетает с завидной регулярностью (пару сообщений в секунду) до тех пор, пока не будет убит процесс (само приложение).

Добавлено через 5 минут и 39 секунд
Есть одно решение - не выгружать пакет, тем более, что программу могут запустить еще раз, либо другую программу, которая использует этот же пакет.....  Тогда и ошибок никаких не вылетает. Но это не есть выход... :(
Конечно, 700 Кб памяти - в наше время ничто, но все же... не хочется, чтобы пакет висел в памяти до самого завершения работы машины...

Автор: CodeMonkey 17.12.2008, 12:47
Цитата(Avers @  17.12.2008,  12:40 Найти цитируемый пост)
Не поверите - ничего. В стеке (EurikaLog'а) запись только о данном модуле. 

Включите Use Debug DCUs и Stack Frames для пакета и exe и сделайте обоим Build. 

P.S. Кстати, было бы также неплохо выключить run-time пакеты в exe и убрать всё из requires пакета. Плюс поменять LoadPackage/UnloadPackage на LoadLibrary/UnloadLibrary. Но такое кардинальное изменение - потом. Если не получится по-другому.

Вообще, в EL есть некоторые проблемы с этим делом - я уже описывал их в http://forum.vingrad.ru/index.php?showtopic=235599&view=findpost&p=1697491.
Запустите под отладчиком, вызовите исключение, выпадите в отладчик - и посмотрите в окно Call Stack.

Добавлено через 9 минут и 31 секунду
Цитата(CodeMonkey @  17.12.2008,  12:29 Найти цитируемый пост)
Как бы не получилось так, что сперва удаляется форма вызовом MyForm.Free, а только потом освобождается её интерфейс.

В обратную сторону тоже могут быть проблемы (строго говоря, вы код не покази, может у вас такой проблемы и нет, но вы всё равно рассмотрите этот сценарий). 
Освобождается интерфейс -> удаляется форма -> ссылка MyForm теперь указывает на мусор.
Выгружается пакет -> выполняются секции finalization -> вызывается десктруктор MyForm, которая является битой ссылкой.

Результат: очень интересное поведение программы.

Если в деструкторе TMyForm вставить MyForm := nil; то это уберёт ошибку.
Плюс, вы сможете продиагностировать: освободили ли интерфейс, перед выгрузкой пакета:
Код
finalization
  if MyForm <> nil then
    raise EUnableToUnloadPackage.Create('There are some references left: TMyForm.');
  UnRegisterClass(TMyForm);


Аналогично можно сделать и для других интерфейсов.

P.S. Строго говоря, в деструкторе уже стоит такая проверка. Но сообщение там, мягко говоря, маловразумительное.

Автор: CodeMonkey 17.12.2008, 13:02
Цитата(Avers @  17.12.2008,  12:40 Найти цитируемый пост)
Есть одно решение - не выгружать пакет, тем более, что программу могут запустить еще раз, либо другую программу, которая использует этот же пакет.....  Тогда и ошибок никаких не вылетает. Но это не есть выход... :(

Если я ничего не путаю, то COM, например, использует такую технологию: время от времени вызывает функцию, которая говорит, можно ли выгрузить сейчас эту DLL. Т.е. DLL выгружается не сразу, а как только будет освобождён последний её интерфейс.

В принципе, так и надо делать по-правильному.

Но лично мне тоже такой вариант не нравится - ну вот хочется мне сделать явную выгрузку. Но при этом нужно всё аккуратно реализовать. Потому что возможны проблемы как у вас - а именно: смешение ручного и автоматического управления жизненным циклом.
Выгрузка DLL/пакета явно - это есть ручное управление. Использование интерфейсов - автоматическое.
Реализация должна быть очень аккуратной, чтобы не напортачить в этом смешении.

Автор: Avers 17.12.2008, 13:23
Код, касающийся интерфейса и класса (модуль пакета):
Код

...
implementation

{$R *.dfm}

var
  Formn: TForm_Logon;
  LogonInterface: ILogonInt10;

function GetLogonInt: ILogonInt10;
begin
  if LogonInterface = nil then
    begin
      Form_Logon := TForm_Logon.Create(nil);
      LogonInterface := Form_Logon;
    end;
  Result := LogonInterface;
end;

exports
  GetLogonInt name CGetLogonIntName;
...

initialization

RegisterClass(TForm_Logon);

finalization

LogonInterface := nil;
Form_Logon.Free;
UnRegisterClass(TForm_Logon);


Такие имена у интерфеса и формы в реале=))
Так я получаю интерфес в приложении:
Код

var
  Pack: THandle;
  LogonInt: ILogonInt10;
  GetInterfaceFunction: TGetLogonInt;
...
begin
  Application.Initialize;

  CoInitFlags := 0;

  Pack := LoadPackage(ExtractFilePath(Application.ExeName)+'Logon.bpl');
  GetInterfaceFunction :=
    GetProcAddress(Pack,
      CGetLogonIntName);
  LogonInt := GetInterfaceFunction();

...
      LogonInt Ж= nil;
      UnloadPackage(Pack);
...
end.

Еще раз повторюсь - выгрузку пакета пробовал разную, так же много раз правил часть финализации в пакете. 
Имхо, дело в экспортируемой функции.... не нравится она мне.

Автор: CodeMonkey 17.12.2008, 13:35
Что стоит в ... между:

Код
 LogonInt := GetInterfaceFunction();

...
      LogonInt Ж= nil;

???

Если в многоточиях ничего не стоит, то уберите MyForm.free и перебилдите всё. Проверьте.

Цитата(Avers @  17.12.2008,  13:23 Найти цитируемый пост)
Имхо, дело в экспортируемой функции.... не нравится она мне.

Функция здесь ни при чём. У вас где-то имеет место быть неаккуратное обращение с интерфейсом.

Автор: Avers 17.12.2008, 13:49
Цитата(CodeMonkey @  17.12.2008,  13:35 Найти цитируемый пост)
Если в многоточиях ничего не стоит, то уберите MyForm.free и перебилдите всё. Проверьте.

Между есть обращение к функции и свойству  интерфейса.
...
Пробовал.... Ошибки летять одна за одной.... 
Где-то что-то проглядел. Чем больше бьюсь над проблемой, тем больше убеждаюсь, что решение оч. простое.

Автор: CodeMonkey 17.12.2008, 13:56
Цитата(Avers @  17.12.2008,  12:40 Найти цитируемый пост)
Если данную строчку убрать, то шибка становится еще жестче=))) вылетает с завидной регулярностью (пару сообщений в секунду) до тех пор, пока не будет убит процесс (само приложение).

Цитата(Avers @  17.12.2008,  13:49 Найти цитируемый пост)
Пробовал.... Ошибки летять одна за одной.... 

Подробнее опишите. Пока не ясно ничего. Какая ошибка (исключение? Класс?)? В какой момент вылетает (во время работы? после выгрузки? в момент выхода?)? Сообщение? Содержимое Call Stack? Что EurekaLog говорит? Почему мы должны всё это угадывать?

Цитата(Avers @  17.12.2008,  13:49 Найти цитируемый пост)
Между есть обращение к функции и свойству интерфейса.

Может код покажете? 

Давайте вы всё же сделаете:
а). Включите Use Debug DCUs и Stack Frames для пакета и exe и сделайте обоим Build. 
Запустите под отладчиком, вызовите исключение (выйдите из программы), выпадите в отладчик - и посмотрите в окно Call Stack среды IDE (View/Debug windows/Call Stack).
Сообщите сюда его содержание.

б). Убираете MyForm.Free, убираете все лишние действия между GetInterfaceFunction и об-nil-ением интерфейса - чтобы код был в точности такой, какой вы сюда выложили. Всё перебилдите и проверите на работоспособность. 

Автор: Avers 17.12.2008, 14:47
Цитата(Avers @  16.12.2008,  17:32 Найти цитируемый пост)
При выгрузке пакета (вручную из приложения) 
UnloadPackage(MyPackage);
Все нормально. Но при завершении программы вознкает ошибка.... 

Т.е. ошибки возникают при завершении работы приложения. Сама выгрузка пакета происходит вполне корректно.
Ошибка: Access violation at address 2000A264 in module 'rtl100.bpl'. Read of address 05580C9C.
EL Пишет подробнее: 
  2.3 Module Name   : rtl100.bpl - (CodeGear Component Package)
  2.4 Module Version: 11.0.2627.5503
  2.5 Type          : EAccessViolation
  2.6 Message       : Access violation at address 2000A264 in module 'rtl100.bpl'. Read of address 05580C9C.
  2.7 ID            : F599

Use Debug DCUs и Stack Frames - включил. 
Мало чего дало. Пишет следующее:
:2000ф264 TClassHelperBse._Create + $C
:7c816fd7 kernel32.RegisterWaitForInputIdle + 0x49
И все. Причем, в данном случае ошибка вылетела при запуске (!!!). Хотя, раньше вылетала при завершении...... Че-то ваще ни че не понимаю....... 

Дело вот еще в чем. Раньше я получал интерфейс, получая перед этим класс формы, создавая форму в приложении.... Тогда при выгрузке все было нормально. 

Автор: CodeMonkey 17.12.2008, 15:16
Цитата(Avers @  17.12.2008,  14:47 Найти цитируемый пост)
Дело вот еще в чем. Раньше я получал интерфейс, получая перед этим класс формы, создавая форму в приложении.... Тогда при выгрузке все было нормально.

В такой схеме интерфейс не использовался для управления жизнью формы. Тут и нет места, чтобы напутать.

Цитата(Avers @  17.12.2008,  14:47 Найти цитируемый пост)
Т.е. ошибки возникают при завершении работы приложения.

Это вы сейчас какой случай описали? Если это начальная ситуация из самого вопроса:
Цитата(Avers @  16.12.2008,  17:32 Найти цитируемый пост)
Но при завершении программы вознкает ошибка.... 

то почему ошибки, если раньше вы говорили про ошибку?
Если же это ответ на мой вопрос про поведение после убирания MyForm.Free, то где же тут:
Цитата(Avers @  17.12.2008,  12:40 Найти цитируемый пост)
вылетает с завидной регулярностью (пару сообщений в секунду) до тех пор, пока не будет убит процесс (само приложение)
?

Если способы а и б из моего предыдущего поста не помогут, то есть ещё вариант отключить на время тестов пакеты.

Автор: Avers 18.12.2008, 10:56
Цитата(Avers @  17.12.2008,  12:40 Найти цитируемый пост)
Есть одно решение - не выгружать пакет


Цитата(CodeMonkey @  17.12.2008,  13:02 Найти цитируемый пост)
В принципе, так и надо делать по-правильному.Но лично мне тоже такой вариант не нравится - ну вот хочется мне сделать явную выгрузку. Но при этом нужно всё аккуратно реализовать. Потому что возможны проблемы как у вас - а именно: смешение ручного и автоматического управления жизненным циклом.


Окзалось, что - не выгружать пакет - есть единственное решение.
1. Закоментил строки  
Код

      //LogonInt := nil;
      //UnloadPackage(Pack);

2. Проверил, остается ли пакет в памяти, ибо навмеревался попытаться выгрузить его после завершения программы.
Оказалось, что пакета в памяти нет..... почему, не понимаю. Но факт, кто каждый раз при запуске приложения при таком коде:
Код

  Pack := GetModuleHandle(PAnsiChar(ExtractFilePath(Application.ExeName)+'Logon.bpl'));
  if Pack = 0 then // вот это условие выполняется!!! 
    Pack := LoadPackage(ExtractFilePath(Application.ExeName)+'Logon.bpl');

Пакет загружается заново. 
Напрашивается вывод, что при завершении приложения винда пыталась выгрузить пакет (что она в общем-то теперь и делает) и из-за этого ошибка и возникала..... Странно, потому что пакет загружается вручную.

Автор: CodeMonkey 18.12.2008, 11:25
Цитата(Avers @  18.12.2008,  10:56 Найти цитируемый пост)
Проверил, остается ли пакет в памяти, ибо навмеревался попытаться выгрузить его после завершения программы.

Не обижайтесь, но сейчас вы написали полный бред. Вам серьёзно нужно занятся изучением http://wm-help.net/books-online/book/59464/59464-23.html#h1 (весь код, связанный с C/C++ можете игнорировать). В частности: http://wm-help.net/books-online/book/59464/59464-23.html#h1t3p4 или поизучайте описание http://msdn.microsoft.com/en-us/library/ms682658(VS.85).aspx.

Автор: Avers 18.12.2008, 11:50
Благодарю за ссылки (но времени на чтение нет :(( - зачетная неделя, сессия + работа). К критике отношусь вполне адекватно (т.е. обижаться не стану).
Вопрос: можете в двух словах пояснить суть загрузки и выгрузки динамических библиотек (насколько знаю bpl - почти та же dll). Где-то наталкивался на информацию, что dll, вручную загруженную, требуется и выручную выгружать (т.е. сама она ни куда не денется). 
Мне видится логичным, что евсли файл загружен в память, ему присвоен Handle, значит, этот Handle я могу получить и из другого приложения. Зачем же тогда вообще проверка на наличие: загружен ли пакет в память, если у меня было бы только одно приложение, использующее данный пакет. 

Автор: Romikgy 18.12.2008, 12:09
Цитата(Avers @  18.12.2008,  10:50 Найти цитируемый пост)
Где-то наталкивался на информацию, что dll, вручную загруженную, требуется и выручную выгружать (т.е. сама она ни куда не денется). 

при убийстве основного потока.процесса , все что им было загружено все тоже улетает , если не юзают данную библиотеку в другом процессе

Автор: CodeMonkey 18.12.2008, 12:11
Короче, после ваших слов о "пакете, остающемся в памяти после завершения приложения" я понял, что вы, скорее всего, и в пакетах ничего не понимаете. Посему вот: демка в аттаче. Целых три (!) различных варианта реализации пакетов с интерфейсом, формой и ручной выгрузкой. Реализации используют различные схемы управления жизненным циклом - хорошо бы разобраться в этих примерах, прежде чем использовать их.

Примеры набросал на скорую руку - подправьте там где что надо (пути загрузки пакета и т.п.). Приложение, разумеется, нужно компилировать с run-time пакетами.

Автор: CodeMonkey 18.12.2008, 12:17
Очень грубо и кратко: в Win32 каждый процесс не зависим от другого. У каждого процесса есть своё адресное пространство. Поэтому любые адреса, дескрипторы и т.п. имеют смысл только в рамках текущего процесса. При завершении процесса все его ресурсы автоматически освобождаются.

Далее, при загрузке DLL, пакета (это действительно http://gunsmoker.blogspot.com/2008/12/1.html) в адресное пространство пакет загружается в память. При этом загрузка может быть явной (LoadLibrary/LoadPackage) и неявной (procedure ... external 'mydll.bpl'). Вы можете загружать пакет/DLL и несколько раз. В этом случае просто увеличивается счётчик числа загрузок. При выгрузке - он уменьшается. Физически DLL будет выгружаться только когда счётчик равен 0.
Разумеется, поскольку процессы изолированы друг от друга, то в одном процессе библиотека может быть загружена 5 раз, а в другом - и вовсе ни одного раза.

Цитата(CodeMonkey @  18.12.2008,  12:11 Найти цитируемый пост)
Примеры набросал на скорую руку

Поэтому теоретически в них тоже возможны ошибки ;)  Использовать только после проверки. Это просто демонстрация идеи, "proof of concept".

Автор: Avers 18.12.2008, 12:20
Спс))
Вот еще вопрос. 
Приложение запущено, пакет загружен, выведена форма из пакета. Запускаю вторую копию этого же приложения, которая пытается найти уже загруженный пакет с помощью GetModuleHandle, но все равно не находит и грузит его заново..... Вапще не понимаю, почему. Смысл тогда проверять наличи пакета в памяти?..... 

Автор: CodeMonkey 18.12.2008, 12:22
Цитата(Avers @  18.12.2008,  12:20 Найти цитируемый пост)
Смысл тогда проверять наличи пакета в памяти?..... 

А я не знаю, зачем вы это делаете smile Это вообще не ваш сценарий.

Автор: Romikgy 18.12.2008, 12:57
Цитата(Avers @  18.12.2008,  11:20 Найти цитируемый пост)
с помощью GetModuleHandle

вроде он дает ответ , был ли загружен что то в своем же процессе!

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)