Использование CoFreeUnusedLibraries приводит к Access Violat

Модераторы: Duncon, Naeel Maqsudov, Игорь Акопян, Хыиуду

Ответить
Пабло
Сообщения: 33
Зарегистрирован: 15 авг 2006, 15:22

Добрый день!
Столкнулся с непонятной проблемой, бьюсь уже бог знает сколько времени, уже незнаю даже где копать... может кто чего подскажет?
итак проблема:
Если создать такое тестовое приложение

MainFrom->ChildForm->ActiveXForm1->ActiveXForm2->ActiveXForm3
___________________ChildAX1.ocx__ChildAX2.ocx__ChildAX3.ocx

где каждая ActiveX форма находиться в своем *.ocx, а на событие Application.OnIdle вызывать coFreeUnusedLibrary, то после 10 раз выполнения следующей последовательности: создать ChildForm, отобразить, закрыть ChildForm, освободить *.ocx с помощью coFreeUnusedLibrary, то на 11 раз происходит Access Violation в модуле ChildsAX2.ocx. Если же убрать вызов coFreeUnusedLibrary, то все будет работать нормально, но только *.ocx никогда не выгрузяться, даже если все созданные из них ActiveX формы давно убиты... Все ActiveX формы дефаултовые, никакого кода нет, кроме как создания в ActiveXFrom1 ActiveXForm2, а в ActiveXForm2 ActiveXForm3.
Создание ActiveX формы идет следующим образом:
FActiveXForm:=TActiveXForm1.Crate(Self);
FActiveXForm.Parent:=Self;
FActiveXForm.Align:=alClient;
тестовое приложение пробовал компилить под Delphi6,2006 абсолютно все идентично...

Как добится нормальной работы данного прилдожения?
Очень надеюсь на вашу помощь...
Eugie
Сообщения: 708
Зарегистрирован: 17 фев 2004, 23:59
Откуда: SPb

Возможно, ваш случай: http://support.microsoft.com/default.as ... US;q248400
Пабло
Сообщения: 33
Зарегистрирован: 15 авг 2006, 15:22

Насколько я понял, там говориться о том, что не происходит вызова UnRegisterClass для классов созданных внутри ActiveX контрола. Я попробовал после освобождения в ActiveXForm2 ActiveXForm3 сделать UnRegisterClass для ActiveXForm3... тоже самое и для ActiveXForm1...
к сожалению непомогло :cry:

С помощью отладчика удалось определить что при создании TActiveXForm2 в TActiveForm1 происходит вызов функции CreateWindowEx, в которой и происходит ошибка из-за того что функция GetClassInfo вдруг возвращает класс с запрашиваемым именем 'DAXParkingWindow'. Этот класс создаеться для того чтобы прицепить к нему окно TActiveXForm2 насколько я понял. Причем в структуре TWndClass которую возвращает GetClassInfo совершенно левый hInstance, из-за чего я так подозреваю и происходит ошибка.
Eugie
Сообщения: 708
Зарегистрирован: 17 фев 2004, 23:59
Откуда: SPb

Так практически ваш сценарий ошибки описан в приведенной статье, цитата:
When you create a window by using ATL, ATL firsts attempts to find whether a window class was registered for the given window by using the ::GetClassInfoEx function. If the class is not registered, then ATL will register the class and create the window. In the scenario in the "Steps to Reproduce the Problem" section, DLL A and DLL B both register the Button class with different WndProc addresses. Assuming DLL B loaded first the second time around, this time at the base address of DLL A, the ::GetClassInfoEx function returns the WndProc address that was registered by DLL A for the Button class. The address is invalid, and the first call to it causes an access violation.
Почему UnRegisterClass() не помог: возможно, вы его не вовремя вызываете. Поместите его вызов после разрушения ActiveX (отдельно для каждого типа), но до вызова coFreeUnusedLibrary().
Пабло
Сообщения: 33
Зарегистрирован: 15 авг 2006, 15:22

ПОЛУЧИЛОСЬ!!! :D Тестовый пример заработал!!!
Я прописал в TActiveXForm.Destroy следующий код.

destructor TActiveXForm.Destroy;
var
WinClassName:string;
DAXParkingWindowHandle:HWND;
begin
inherited;
//делаем UnregisterClass для данной ActiveX формы
WinClassName:=ClassName;
if not Windows.UnregisterClass(PChar(WinClassName),HInstance) then
ShowMessage(ClassName+' - '+SysErrorMessage(GetLastError));
//делаем UnregisterClass для DAXParkingWindow - это скорее всего
//оберточное окно для отображаемого внутри ActiveX.
WinClassName:='DAXParkingWindow';
DAXParkingWindowHandle:=FindWindow(PChar(WinClassName),nil);
while DAXParkingWindowHandle<>0 do
begin
DestroyWindow(FindWindow(PChar(WinClassName),''));
DAXParkingWindowHandle:=FindWindow(PChar(WinClassName),nil);
end;
if not Windows.UnregisterClass(PChar(WinClassName),0) then
ShowMessage(WinClassName+' - '+SysErrorMessage(GetLastError));
end;

вообщем теперь осталось решить две проблемы:
1) разобраться почему в момент выгрузки OCX есть еще окна класса 'DAXParkingWindow'. Я пробовал делать UnregisterClass для 'DAXParkingWindow' в DLLCanUnloadNow однако функция не отробатывала и возвращала ошибку что еще есть окна класса 'DAXParkingWindow'
2) Где правильней и удобней выполнять вышеприведенные действия.

Отдельное и большущее спасибо Eugie за неоценимую помощь! Наверно если посчитать время потраченное мной на эту проблему, то наверно выйдет почти неделя рабочего времени :)
Eugie
Сообщения: 708
Зарегистрирован: 17 фев 2004, 23:59
Откуда: SPb

Народ уже на эти грабли наступал, и вот результат :) :
http://qc.borland.com/wc/qcmain.aspx?d=8311
http://qc.borland.com/wc/qcmain.aspx?d=3315
http://cc.borland.com/Item.aspx?id=18656

IMHO, лучшее решение - комбинация двух вариантов: обеспечить уникальность имени класса 'DAXParkingWindow' (как именно, это уже дело техники - например, добавляя суффиксом адрес оконной процедуры, как предложено в 3-й ссылке) + в секции финализации модуля AxCtrls вызывать UnregisterClass(). По любому, придется править AxCtrls.pas и перекомпилировать VCL.
Пабло
Сообщения: 33
Зарегистрирован: 15 авг 2006, 15:22

О!, это же мои грабли :D

По третей ссылке дано хорошее решение. Изменил модуль AxCtrls.pas и добавил в секцию finalization UnregisterClass как было рекомендовано многоуважаемым Eugie. Модуль перекомпилил и положил в Delphi\Libs
Все работает как часы, швейцарские прошу заметить :) .
Пабло
Сообщения: 33
Зарегистрирован: 15 авг 2006, 15:22

Добавляю информацию еще по одним граблям.
Грабли описаны здесь: http://qc.borland.com/wc/qcmain.aspx?d=3272 как исправить описано там же. Хотя ошибка связана с таймером на Hint(ы), падало у меня при первом вызове контекстного меню, после выгрузки библиотеки ocx где был запущен таймер для hint(а).
Так что симптом не всегда говорит о болезни :)
Ответить