Delphi programming blog
Источник: http://teran.karelia.pro/articles/item_4452.html
 

TMS Components в DLL

Опубликовано 12.01.2011 г. 16:22

В конце прошлого года столкнулся с проблемой использования компонентов TMS Software в динамических библиотеках. На самом деле эта проблема почти взорвала мой мозг, о чем я писал в одной из предыдущих статей.

Если кратко, предыстория такова: Создаем группу проектов: ехе и dll. В библиотеку добавляем форму, использующую компоненты TMS. никакого кода библиотека при этом не содержит, никакие функции не экспортирует. Вызывающий ехе содержит следующий код при создании главной формы:
    libFileName := ExtractFilePath(application.ExeName) + 'kernel.dll';
    kernelLibHandle := LoadLibrary(PChar(libFileName));
    if kernelLibHandle > 0 then
        FreeLibrary(kernelLibHandle)
Т.е просто загружает и выгружает библиотеку. В качестве результата получается AV. Весьма интересно программа ведет себя в win7x64. Если в x32 мы сразу получаем AV, то x64 может вполне нормально отработать и закрыться, однако если мы откроем например окно свойств системы, или окно проводника, то опять же получаем AV. Была создана тема на форуме поддержки, и написано письмо в суппорт. И перед новым годом я таки получил ответ на него: как ни странно у товарищей из TMS все прекрасно работало, и они прислали мне мой откомпилированный тестовый проект. И у меня он собственно тоже выполнялся правильно. Но опять же перекомпилировав его я получал ошибки. В ответе суппорта был весьма резонный вопрос: использую ли я последнюю версию компонентов. На что я мог ответить разве что то, что наша лицензия не позволяет обновить компоненты до последней версии. Последняя версия Advanced String Grid на текущий момент является v.5.6.0.1 (согласно скачанной триал версии с сайта). Кстати с установленной триал версией этот код и правда нормально работает, без ошибок. У меня же использовалась версия 5.5.2.0 после чего она была обновлена до 5.6.0.0. Ошибка таки не исчезла. Новое письмо в суппорт: в 5.6.0.0 ошибка есть, в триал 5.6.0.1 ошибки нет. Ответ был весьма простой: Please use the latest version of the component. (с) Отлично подумал я. Если запускать приложение вне среды, то Windows строит отчет об ошибке, из которого мы можем узнать, что имя модуля, вызвавшего ошибку - gdiplus.dll Ну раз так, и при условии что никакого кода в библиотеки моего нет, а сами компоненты, в частности этот грид весьма насыщены графикой, то надо предположить, что какая то часть работы с GDI проходит в секции инициализации/финализации модулей, вызывая ошибку. О чем я, впрочем, и написал в суппорт. Чем мне нравится windows 7 дак это удобным поиском. *.pas файлы очень удобно добавляются в список индексирования для полнотекстового поиска, можно также добавить их для предварительного просмотра как текста в проводнике. Поисковый запрос вида "GDI *.pas initialization" выдал мне около 30 файлов. и здесь все наше внимание приковывается к файлу advgdip.pas приводя выдержки исходного кода из initialization/finalization частей модуля можно увидеть следующее:
initialization
begin
  GdiplusStartup(gdiplusToken, @StartupInput, @StartupOutput);
end;

finalization
begin
  if not IsLibrary then  begin
    GdiplusShutdown(gdiplusToken);
  end;
end;
А это значит следующее: загрузка GDI+ с помощью GDIPlusStartup() происходит всегда, независимо от типа исполняемого модуля, а вот его его выгрузка с использованием GDIPlusShutdown() происходит только в том случае, если наш исполняемый модуль не является библиотекой. Внимание вопрос: что же происходит в случае, если мы поместим компонент в библиотеку? Ответ кажется достаточно прост: получим AV при выгрузке. Весьма интересно узнать когда же появилась эта версия компонента 5.6.0.1. Не в начале ли января этого года. Как вообще мог набор компонентов дожить до такой версии (сам Component Pack имеет версию 5.8.3) имея такую ошибку. Документация весьма четко говорит
The GdiplusShutdown function cleans up resources used by Microsoft Windows GDI+. Each call to GdiplusStartup should be paired with a call to GdiplusShutdown.
С таким же успехом в примере TMS по использованию компонентов в DLL присутствует 2 вызова LoadLibrary() с одним вызовом FreeLibrary(), хотя они также должны быть парными. Еще есть такой момент, я вот не знаток к сожалению разработки DLL, и нахожусь только вначале изучения. Я так понимаю, что секции Initialization/finalization вызываются из DLLMain. Однако документация гласит:
Do not call GdiplusStartup or GdiplusShutdown in DllMain or in any function that is called by DllMain
Это я к тому, что правильно ли проводить инициализацию GDI+ в секции Initialize.
Метки:  error  |  dll  |  TMS 

Комментарии

GunSmoker
12.01.2011 в 18:32
Очередная проблема DllMain www.transl-gunsmoker.ru/2009/01/dllmain.html

Сама Delphi имеет аналогичные проблемы:
qc.embarcadero.com/wc/qcmain.aspx?d=36652
qc.embarcadero.com/wc/qcmain.aspx?d=57442

Themes.pas:

finalization
if not IsLibrary then
InternalServices.Free;
end.

Появилось после:
qc.embarcadero.com/wc/qcmain.aspx?d=4167
ter
12.01.2011 в 22:52
ага. статью читал седня.
Владимир
13.01.2011 в 11:07
Я на билдере 6м нарвался на AV начиная с версии TMS 5.6.0.0: а именно, достаточно стартовать стройку и при выходе из неё получаю ошибку типа деления на 0 (216).
в предыдущих этого нет, и судя по всему тоже связано с gdiplus. Суппорт меня послал, сказав что у них всё в порядке и найти баг слишком сложно. Все последние версии дают эту проблему, приходится мириться.
Denis
18.03.2011 в 19:01
Похожая проблема!
Разрабатываю COM Add-in`s для офиса (.dll), в часности для Word-а.
В этой COM интеграции я использую TMS taskdialog (на котором есть AdvGlowButtons для отрисовки которых используется GDI+ в AdvGDIP.pas).
Так вот даже если мой код не исполняется (dll просто подгружается в Word), Word 2003 при закрытии крэшается!!!
После долгих вычислений нашёл причину - инициализационная секция модуля AdvGDIP.pas.

Имею TMS Component Pack v5.7.2.0.
initialization уже немного модифицирован по сравнению с примером вверху.

initialization
begin
// Initialize StartupInput structure
StartupInput.DebugEventCallback := nil;
StartupInput.SuppressBackgroundThread := True;
StartupInput.SuppressExternalCodecs := False;
StartupInput.GdiplusVersion := 1;

StartupOutput.NotificationHook := nil;
StartupOutput.NotificationUnhook := nil;

// Initialize GDI+
GdiplusStartup(gdiplusToken, @StartupInput, @StartupOutput);

StartupOutput.NotificationHook(gdiplusToken);
end;

finalization
begin
// Close GDI +
if not IsLibrary then
begin
StartupOutput.NotificationUnhook(gdiplusToken);
GdiplusShutdown(gdiplusToken);
end;
end;


Так вот если не использовать SuppressBackgroundThread и NotificationHook - всё работает отлично.

Но опять же вопрос:
Как можно вызывать инициализацию (переинициализацию) GDI и потом её не финализировать в DLL-ке? Если руководствоваться MSDN-ом, то тут полный абсурд.
К примеру приложение грузит мою dll-ку и на момент загрузки application message loop уже работает и GdiplusStartup, NotificationHook уже нельзя вызывать.
Конечно можно сделать условие don`t SuppressBackgroundThread if IsLibrary =true, но как это отразится на работе TMS компонентов не знаю.

P.S. наверное стоит отказаться от использования этих компонентов.
P.S.S. Кстати баг получается очень редко и не на всех Word-ах (скорее всего зависит от установленных в Word других эдинов или компонентов использующих GDI).
ter
21.03.2011 в 13:40
ну там в примере вверху приводилось только необходимые строки,где стартап и завершение GDI
я в суппорт об этом им писал, сказали юзать последние версии компонентов. мб данный вопрос там решен уже. я самую последнюю не ставил. счас там вроде 6я версия компонент пака вышла.

зы: а я из компонентов их юзаю только грид. Он в действительно весьма функционален и полезен.правда багов имеет не мало тоже, и из за своих размеров немного медлителен.
ak545
24.04.2011 в 02:48
initialization
begin
// Initialize StartupInput structure

StartupInput.DebugEventCallback := nil;
StartupInput.SuppressBackgroundThread := True;
StartupInput.SuppressExternalCodecs := False;
StartupInput.GdiplusVersion := 1;

StartupOutput.NotificationHook := nil;
StartupOutput.NotificationUnhook := nil;

// Initialize GDI+
GdiplusStartup(gdiplusToken, @StartupInput, @StartupOutput);
if not IsWinVista then
StartupOutput.NotificationHook(gdiplusToken);
end;

finalization
begin
// Close GDI +
if not IsLibrary then
begin
if not IsWinVista then
StartupOutput.NotificationUnhook(gdiplusToken);
GdiplusShutdown(gdiplusToken);
end;
end;
ter
25.04.2011 в 10:31
вас не смущает что gdiStartup проводится всегда а shutdown только если не библиотека?
зачем условие на isWinVista? в чем суть его?
Дмитрий Уколов
26.04.2011 в 10:24
У меня в подобной ситуации зависала регистрация Dll (COM).
Исправилось после указания
  SuppressBackgroundThread := True;


для GDI+
Sweet blog! I found it while browsing on Yahoo News.

Do you have any suggestions on how to get listed in Yahoo News?

I've been trying for a while but I never seem to get there!
Thanks
- Имя
- e-mail*
- Сайт
вы можете использовать теги [i],[b],[code],[quote]
Дополнительно