DLL по протоптанной дорожке - страница 3

 
Debugger >>:


В Паскале несколько иная структура. Там есть искуственный аналог. Его я использую. Весь прикол в том что это проихсодить только под MT4.

Ладно. Этим надо заниматься на свежую голову.

Если речь о примерно таком коде,


procedure DLLEntryPoint(Reason: DWORD);
begin
case Reason of
DLL_PROCESS_DETACH: ;
DLL_PROCESS_ATTACH: ;
DLL_THREAD_ATTACH: ;
DLL_THREAD_DETACH: ;
end;
end;

begin
if not Assigned(DllProc) then begin
DllProc:= @DLLEntryPoint;
DllEntryPoint(DLL_PROCESS_ATTACH);
end;
end.


то это не искусственная структура, а вполне таки нормальная точка входа.


Делаете ли вы инициализацию потоков или динамическое распределение памяти в пределах DLLEntryPoint?

 
HideYourRichess >>:

Если речь о примерно таком коде,


procedure DLLEntryPoint(Reason: DWORD);
begin
case Reason of
DLL_PROCESS_DETACH: ;
DLL_PROCESS_ATTACH: ;
DLL_THREAD_ATTACH: ;
DLL_THREAD_DETACH: ;
end;
end;

begin
if not Assigned(DllProc) then begin
DllProc:= @DLLEntryPoint;
DllEntryPoint(DLL_PROCESS_ATTACH);
end;
end.


то это не искусственная структура, а вполне таки нормальная точка входа.


Делаете ли вы инициализацию потоков или динамическое распределение памяти в пределах DLLEntryPoint?


Да, это все есть и менно так. Далее создается объекты и выделяется помять под динамические массивы.
 
HideYourRichess >>:

Если речь о примерно таком коде,


procedure DLLEntryPoint(Reason: DWORD);
begin
case Reason of
DLL_PROCESS_DETACH: ;
DLL_PROCESS_ATTACH: ;
DLL_THREAD_ATTACH: ;
DLL_THREAD_DETACH: ;
end;
end;

begin
if not Assigned(DllProc) then begin
DllProc:= @DLLEntryPoint;
DllEntryPoint(DLL_PROCESS_ATTACH);
end;
end.


то это не искусственная структура, а вполне таки нормальная точка входа.


Делаете ли вы инициализацию потоков или динамическое распределение памяти в пределах DLLEntryPoint?


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

Попробую поместить сюда несколько отрывков из неоконченной статьи про длл для МТ4 на Дельфи. Пока писал, пришло сообщение о скором выходе мт5, и я это дело забросил. Обратите внимание на DllMain и что там нельзя делать. Очень вероятно, что ваши проблемы лежат именно там.


beginend

При создании проекта Delphi, предназначенного для компиляции DLL, в файле проекта .DPR появляется секция begin...end. Эта секция выполняется всегда при первой проекции DLL в адресное пространство процесса. Другими словами, можно считать, что это своеобразная секция initialization, которая есть у всех unit. В этом месте можно провести какие то действия, которые нужно выполнить в самом начале и только один раз, для текущего процесса. При загрузке DLL в адресное пространство другого процесса эта секция будет выполнена там повторно. Но так как адресные пространства процессов разделены между собой, то начальная инициализация в одном процессе ни как не скажется на другой процесс.

У этой секции есть некоторые ограничения о которых нужно знать и учитывать. Ограничения эти связаны с тонкостями функционирования механизма загрузки DLL Windows. Более подробно о них поговорим позднее.


initialization/finalization

У каждого unit Delphi есть особые секции, так называемые секции инициализации и завершения. Как только любой unit подключается к проекту эти секции подключаются к специальному механизму загрузки и выгрузки главного модуля. И эти секции выполняются до того как главная секция beginend начнет свою работу, и после того как работа будет завершена. Это бывает очень удобно, так как избавляет от необходимости прописывать инициализацию и финализацию в самой программе. При этом, подключение и отключение проводится автоматически, стоит только подключить или отключить unit к проекту. И это происхоит не только в традиционных EXE файлах, но и в DLL так же. Очередность инициализации DLL, при «загрузке» её в память следующая, сначала выполняются все секции инициализации unit, в том порядке как они обозначены в uses проекта, затем выполняется секция begin...end. Финализация происходит в обратном порядке, за исключением того, что в файле проекта DLL нет специально предназначенной функции завершения. Это, в общем то, ещё одна причина, почему проекты DLL рекомендуется разделять на файл проекта и используемые unit.

 

DllMain

Это так называемая точка входа DLL. Дело в том, что у Windows переодически возникает необходимость сообщить о каком либо событии, происходящем в пределах процесса, самой DLL. Для того что бы это сделать и существует точка входа. То есть специально предопределенная функция, которая есть у каждой DLL и которая может обрабатывать сообщения. И хотя мы до сих пор не видили этой функции в DLL написанной на Delphi, тем не менее такая точка есть и у неё. Просто механизм её функционирования завуалирован, но до него всегда можно добраться. Ответ на вопрос, - а нужно ли это вообще? – не так очевиден как кажется.

Сначала попытаемся разобраться, что же такое стремиться Windows сообщать DLL. Всего существует 4 сообщения, с которыми операционная система приходит к DLL. Первое, уведомление DLL_PROCESS_ATTACH – присылается всякий раз, когда система присоединяет DLL к адресному пространству вызывающего процесса. В случае MQL4 это неявная загрузка. При этом, не важно что данная DLL уже была загружена в адресное пространство другого процесса, сообщение всё равно прийдет. И неважно, то что на самом деле фактически Windows грузит конкретную DLL в память всего один лишь раз, все процессы желающие загрузить эту DLL к себе в адресное пространство получают всего лишь отражение этой DLL. Это один и тот же код, но данные которые могут быть у DLL уникальны для каждого процесса (хотя возможно и существование общих данных). Второе, уведомление DLL_PROCESS_DETACH - сообщает DLL, что необходимо произвести отсоединение от адресного пространства вызывающего процесса. Фактически, это сообщение поступает перед тем, как Windows начнет выгружать DLL. На самом деле, если DLL используется другими процессами, то ни какой выгрузки не происходит, просто Windows «забывает» что некая DLL существовала в адресном пространстве процесса. Ещё два уведомления, DLL_THREAD_ATTACH и DLL_THREAD_DETACH поступают в тот момент когда у процесса, который загрузил DLL, возникают или уничтожаются потоки в пределах процесса. Есть несколько тонких моментов, связанных с очередностью поступления уведомлений о потоках, но мы их рассматривать не будем.

Теперь о том, как устроены DLL написанные на Delphi и то что обычно скрыто от программистов. После того, как Windows произвел «проецирование DLL на адресное пространство вызывающего процесса», а по простому говоря загрузил DLL в память, в этот момент происходит вызов функции расположенной в точке входа и передача уведомления DLL_PROCESS_ATTACH этой функции. В DLL написанном на Delphi в этой точке входа расположен специальный код, который делает много разных вещей, включая и запуск инициализации units. Запоминает что инициализация и первый запуск DLL были произведены, выполняет секции инициализации units, и выполняет begin...end главного файла проекта. Таким образом, этот код первоначальной загрузки выполняется только один раз, все остальные обращения Windows в точку входа происходят уже с учетом этого, обрабатывает последующие уведомления, - фактически игнорирует их, кроме сообщения DLL_PROCESS_DETACH, по которому производит финализацию unit. Так, в общих чертах, выглядит механизм загрузки DLL написанной на Delphi. В большенстве случаев этого достаточно для написания и использования DLL в MQL4.

 

Если всё же нужен DllMain такой же (с некоторыми тонкими отличиями) как и в C, то его несложно организовать. Делается это следующим образом. При первой загрузке DLL, в числе прочего, модуль System (он всегда присутствует в программе или DLL) автоматически создает глобальную процедурную переменную DllProc, которая инициализируется nil. Это означает что никакой дополнительной обработки уведомлений DllMain, кроме той которая существует, не требуется. Как только этой переменной будет присвоен адрес функции, то все уведомления для DLL от Windows станут поступать к этой функции. Что и требуется от точки входа. При этом, уведомление DLL_PROCESS_DETACH всё равно будет отслеживаться функцией завершения DLL, как и раньше, для того что бы была возможность провести финализацию.

procedure DllEntryPoint(Reason: DWORD);

begin

case Reason of

DLL_PROCESS_ATTACH: ; //'Подключение процесса'

DLL_THREAD_ATTACH: ; //'Подключение потока'

DLL_THREAD_DETACH: ; //'Отключение потока'

DLL_PROCESS_DETACH: ; //'Отключение процесса'

end;

end;

begin

if not Assigned(DllProc) then begin

DllProc:= @DllEntryPoint;

DllEntryPoint(DLL_PROCESS_ATTACH);

end;

end.

В том случае, если нас не интересуют уведомления о потоках, всё это излишне. Стоит только в unit организовать секции initialization/finalization как события подключения и отключения процесса будут отслеживаться автоматически.
 

Коварство и вероломство DllMain

Теперь, пожалуй, пришло время коснуться вопроса который удивительно мало освещен в литературе по программированию. Эта тема касается не только Delphi или С, но вообще любого языка программирования, способного создавать DLL. Это свойство загрузчика DLL Windows. Из переведенной серьёзной и широкораспостраненной литературе по программирования в среде Windows, только у одного автора удалось найти упоминание об этом и то, в самых туманных выражениях. Этот автор Дж. Рихтер, и ему простительно, так как его замечательная книга вышла в 2001 году, когда в общем то и 32 разрядный Windows не был так распостранен.

Интересно, что сам MS ни когда не скрывал существование проблемы с DllMain и даже разместил у себя специальный документ, что то вроде – «Наилучший способ использования DllMain». В котором объяснил, что можно делать в DllMain, а что нерекомендуется. Причем, было указано, что нерекомендованные вещи ведут к трудноуловимым и непостоянным ошибкам. Желающие ознакомиться с этим документом могут заглянуть на их сайт. Более популярное изложение нескольких переводов алармистких сообщений по этому поводу изложено в ссылках ниже.

Суть проблемы очень проста. Дело в том, что DllMain, особенно при загрузке DLL, это особое место. Место где нельзя делать ничего сложного и выдающегося. Например, не рекомендуется CreateProcess, или LoadLibrary другие DLL. Так же не рекомендуется CreateThread или проводить CoInitialize COM. И т.д. Динамически распределять память то же не рекомендуется.

Можно делать самые простые вещи. Иначе ничего не гарантируется. Поэтому, не помещайте ничего лишнего в DllMain, иначе будете с удивлением наблюдать крах приложений использующих вашу DLL. Лучше перестраховаться и создать специальные экспортированные функции инициализации и финализации, которые в нужные моменты будет вызывать главное приложение. Это, по крайней мере, поможет избежать проблем с DllMain.


 
На самом деле не всё так страшно, как указано в алармистких ссылках, но проблема существует, и о ней нужно знать.
 

HideYourRichess писал(а) >>

Попробую поместить сюда несколько отрывков из неоконченной статьи про длл для МТ4 на Дельфи. Пока писал, пришло сообщение о скором выходе мт5, и я это дело забросил.

Может, все-таки забросите MT5, и завершите статью? ))) Шучу, конечно. - ничего забрасывать не надо, но статью и вправду хочется увидеть. Спасибо, что привели отрывок. С DllMain действительно ситуация, требущая повышенного к ней внимания.

 

Для МТ4 - смысла нет дописывать, для МТ5 - пока бета - то же. А время идет, на грабли DllMain постоянно наступают (потому что не читают что рекомендует MS) - по этому решил поместить отрывок, раз уж такой удобный случай выдался.


Ведь хорошая была задумка, повторить Sample на С но в Дельфи. И ведь почти удалась. :)