Использование в делфи DLL
И вопросы касательно этой темы
Сначала немного теории о использовании
Но судя по всему проблемы на этом не заканчиваютсяа только начинаются.
Проблема Runtime 216 Access Violation проблемы с выгрузкой из памяти приложения и модулей.
Так вот у меня при использовании компонентов DevExpress в часности тех которые связаны каким-то образом
с uxTheme.dll (TdxBarManager, TdxLayoutControl и т.д.).
Вот так и живем, в инете вобщем-то море таких ошибок проплавает. Уже была така ошибка только она была специфична для компонентов
JEDI прежних версий.
Может у кого есть мысли на этот счет.
А может у кого просто есть хороший совет чтобы поделится по теме.
И вопросы касательно этой темы
Сначала немного теории о использовании
А теперь сам вопрос. Судя по статье если использовать RUNTIME линковку то все будет ОК.Для понимания как механизма возникновения проблем при использовании DLL, так и методики их решения, необходимо разобраться в работе линковщика.
Линковка - процесс, собирающий из разрозненного объектного кода2 итоговый исполняемый файл. Необходимость линковки вызвана желанием строить программу из отдельных фрагментов, откомпилированных в разное время и часто - разными людьми. С точки зрения нашей темы линковка важна прежде всего тем, что именно здесь к программе подключаются стандартные библиотеки Delphi, в частности, библиотека компонент (VCL).
Обычный, действующий по умолчанию механизм иногда называют статической линковкой. Исходные файлы Delphi компилируются в объектный код - файлы с расширением dcu (Delphi Compiled Unit). После этого линковщик собирает из dcu итоговый файл; из каждого объектного файла берется только тот код, те подпрограммы, которые прямо или косвенно используются в программе3.
В первых версиях Delphi стандартная библиотека, равно как и добавленные пользователем компоненты, хранились в одном большом файле с расширением dcl (Delphi Compiled Library). Фактически в этом файле хранились dcu-шки модулей, составляющих библиотеку; они линковались в программу тем же способом. Чуть позже DCL разбили на ряд меньших по размерам пакетов и стали использовать расширение dcp; принципы статической линковки остались теми же самыми.
Особый момент в логике линковщика - обработка DLL, динамически подключаемых библиотек. Если объектный код декларирует использование подпрограммы из DLL, линковщик создает для нее заглушку - псевдоподпрограмму, состоящую по сути из оператора безусловного перехода (jmp). Загрузчик Windows, готовя программу к выполнению, подгружает DLL и инициализирует таблицу переходов реальными адресами подпрограмм; таким образом, код, обращающийся к функции DLL, вызывает как подпрограмму команду jmp и управление передается реальной подпрограмме из DLL4.
Вместе с реализацией пакетов появилась и альтернативная схема линковки библиотек. В результате компиляции пакета, кроме dcp-файла создается также файл с расширением bpl (Borland Package Library). По сути этот файл представляет собой DLL, экспортирующую все интерфейсные функции входящих в нее модулей, а также ряд дополнительных механизмов, добавленных Borland. Эти библиотеки - называемые run-time packages - подключаются примерно так же, как DLL. В результате исполняемый файл получается намного меньшим по размеру, но вместе с ним приходится распространять несколько bpl-файлов. Иногда этот подход называют динамической линковкой. Именно этот вариант сборки - использование run-time packages - позволяет построить корректно работающее приложение.
Для ликвидации терминологической путаницы в этой статье используется следующая терминология:
Статически линкуемым (модулем или пакетом) будем называть объектный код (dcu или dcp), включенный линкером в соответствующий исполняемого файла.
Динамически линкуемым (пакетом, DLL) будем называть объектный код, откомпилированный в виде отдельного исполняемого файла и подключаемый к основному механизмом статической либо динамической загрузки.
Статически загружаемыми будем называть DLL (в том числе и пакеты), автоматически загружаемые при старте приложения описанным выше образом.
Динамически загружаемыми будем называть DLL, явно загружаемые и выгружаемые приложением (например, в варианте LoadLibrary/GetProcAddress).
В результате статической линковки пакетов в каждый выполняемый файл копируются - линкуются - одни и те же библиотеки, в результате чего каждый exe и каждая dll поддерживают свой экземпляр общих, по идее, данных - глобальных переменных, экземпляров синглтонов и так далее. "Общим модулем" же оказываются в том числе ключевые файлы стандартной библиотеки: менеджер памяти, стандартные классы и компоненты, и подобные - не приспособленные для работы в таком режиме. В частности, предыдущий пример показывает неожиданное дублирование информации RTTI - "сердца" всей VCL. Если не предпринять специальных мер, формы, созданные в DLL, не попадут в в список Screen.Forms; компонент TQuery, созданный в DLL, не найдет компонент TDatabase основной программы и так далее. Последствия же этого сказываются в работе других частей стандартного кода - например, форма, созданная в DLL, оказывается в таскбаре, поскольку не находит главного окна приложения. В результате получается огромный запутанный клубок проблем и ошибок.
Также проблемами при статической линковке пакетов обусловлена популярная легенда "в DLL нельзя передавать и из DLL нельзя возвращать объекты, динамические массивы, строки типа AnsiString и так далее". На самом деле - можно и нужно; в DLL можно делать все, что можно делать в обычном exe-файле. Но - при использовании динамической линковки пакетов, при использовании run-time packages.
Для библиотек, собранных с использованием run-time packages, ни одной подобной проблемы не возникает; для них просто нет возможности. Общие модули загружаются в виде bpl - и хотя пакет может использоваться во многих местах, в памяти приложения он присутствует в единственном экземпляре, как код, так и данные. Конфликты и расхождения при этом исключены. Довольно часто программисты пытаются использовать статическую линковку пакетов в DLL чтобы работать "как привычно", "как с единственным exe-шником", "без лишних сложностей, в которых пока не хочется разбираться". На деле же как раз динамическая линковка обеспечивает абсолютно привычную работу без каких-либо особенностей, в то время как для работы приложения со статической линковкой приходится предпринимать множество усилий с заведомо сомнительным результатом.
Собственно, на использовании run-time packages можно было бы закончить статью - это простой, надежный и безошибочный режим. В то же время удивительно много людей предпочитает набивать себе шишки - а потому необходимо упомянуть о возможных путях неправильного решения проблемы, средствах организации работы без run-time packages.
Прежде всего, следует попытаться синхронизировать глобальные переменные и объекты. В частности, переменные Application и Screen в DLL следует инициализировать значениями, переданными из главной программы - это указатели, а следовательно, можно направить несколько указателей на один и тот же физический объект в памяти. В ряде случаев можно опереться на хандлы используемых объектов; в частности, в BDE-приложениях можно использовать в каждой DLL собственный компонент TDatabase, инициализированный одинаковым с другими значением Database.Handle.
Ряд возможностей VCL - например, конструктор CreateParented - предназначен для организации взаимодействия с приложениями, созданными в других средах. С их помощью возможно четко разделить "зоны ответственности" - в частности, избежать ошибок при сочетании на форме объектов, созданных в различных модулях. Некоторые функции VCL - например, FindControl - опираются на возможности Windows и нормально работают при статической линковке пакетов.
Одним из модулей, входящих в стандартную библиотеку, является менеджер динамической памяти. Он используется не только при явных операциях с динамической памятью, но также при создании объектов, при работе со строками и динамическими массивами. При статической линковке пакетов у каждого исполняемого файла оказывается собственный менеджер памяти - и, в первую очередь, собственная копия его внутренних структур. Как результат, блок памяти, выделенный в одном из модулей, не может быть корректно обработан - изменен или освобожден - в другом модуле; менеджер памяти второго модуля просто не найдет в своих данных упоминания об этом блоке памяти и выдаст ошибку "Invalid pointer operation". Именно поэтому передача в/из DLL данных сложных типов - строк, объектов - требует дополнительных усилий.
В составе Delphi поставляется модуль ShareMem, позволяющий решить эту проблему. Для нормальной работы этот модуль должен быть указан первым в списке uses каждого из проектов, составляющих приложение - только так можно гарантировать, что все операции с динамической памятью будут выполнены с использованием правильного менеджера памяти.
Модуль ShareMem достаточно прост. Он использует возможность установки внешнего менеджера памяти, общего для всех DLL, вместо используемого по умолчанию. В нем предлагаются два варианта: могут использоваться либо стандартные функции Windows (GlobalAlloc/GlobalFree), либо специальный менеджер памяти (библиотека borlndmm.dll). Первый вариант плох с точки зрения производительности; второй требует распространения вместе с проектом еще одной, дополнительной dll; тем не менее, оба варианта решают ряд проблем. Для однородного проекта лучшим было бы третье решение, которого, к сожалению, в стандартной поставке нет - взять менеджер памяти exe-шника и использовать его же во всех подгружаемых dll. В любом случае, таким образом устанавливается единый для всех dll менеджер памяти.
Необходимо отметить, что модуль ShareMem решает далеко не все проблемы статической линковки - скажем, приведенный выше пример демонстрирует глюк несмотря на наличие ShareMem в списке uses.
При использовании run-time packages модуль ShareMem не вносит заведомых ошибок, но бессмысленен и вреден - приложение и так использует общий менеджер памяти, любые данные передаются между модулями без каких-либо дополнительных усилий, и нет необходимости подгружать дополнительный и менее эффективный модуль. Также стоит помнить, что в этом случае нельзя быть "немножко беременной" - если модуль ShareMem используется хоть где-нибудь, он должен использоваться везде и быть указан первым во всех списках uses. В противном случае часть переменных окажется обработанной старым менеджером памяти, часть - новым, и это практически наверняка приведет к крайне сложным в отладке ошибкам.
Вывод
Для Delphi существует технология, позволяющая разрабатывать и использовать DLL практически
без каких-либо дополнительных усилий и практически без каких-либо дополнительных проблем.
Существенные факторы, мещающие ее использовать, мне не известны. В то же время отступление
от этой технологии почти гарантирует более или менее серьезные неудобства в случае разработки
приложений, состоящих из нескольких выполняемых файлов (exe и dll). Существующие приложения
могут быть довольно легко приведены к правильной технологии и это практически всегда ликвидирует
возникшие проблемы; однако, конвертация не является полностью прозрачной, тривиальной задачей1.
Единственный вариант, когда использование пакетов заведомо не решит всех проблем - неоднородная
среда разработки, например, разработка на Delphi DLL к недельфовому проекту. Но даже в этом случае
описываемая технология уменьшит количество возникающих проблем.
Но судя по всему проблемы на этом не заканчиваютсяа только начинаются.
Проблема Runtime 216 Access Violation проблемы с выгрузкой из памяти приложения и модулей.
Так вот у меня при использовании компонентов DevExpress в часности тех которые связаны каким-то образом
с uxTheme.dll (TdxBarManager, TdxLayoutControl и т.д.).
Вот так и живем, в инете вобщем-то море таких ошибок проплавает. Уже была така ошибка только она была специфична для компонентов
JEDI прежних версий.
Может у кого есть мысли на этот счет.
А может у кого просто есть хороший совет чтобы поделится по теме.