Delphi - сбориник статей

         

Немного теории


Передать, точнее, экспортировать несколько функций из DLL – не проблема. Приводим типы, соглашения о вызовах, заполняем список экспортируемых функций – и всё (в основном). Об этом написано немало, например, в [], в параграфе «Использование DLL, разработанных в С++».

Экспортировать класс несколько сложнее. Даже если и DLL, и основная программа написаны на Delphi, возникают проблемы с распределением памяти, которые решаются использованием модуля ShаreMem, первым в списке uses как проекта, так и DLL [, ]. Причем, этот модуль можно, в принципе, заменить самодельным менеджером памяти []. Но как использовать ShаreMem, если DLL написана на другом языке, или написать собственный менеджер для двух языков? Наверное, можно и так, но, напоминаю, срок сдачи проекта – вчера. Если есть и другие возражения, часто время – определяющий фактор.

Можно создавать экземпляр класса при загрузке DLL, ликвидировать при выгрузке (используя события DLL_PROCESS_ATTACH/DETACH), а для методов класса (функций-членов, раз уж класс на С++) написать однострочные функции-обертки, не являющиеся членами, а просто вызывающие соответствующие функции-члены. Некрасиво, и много лишней работы. Попробуем все же экспортировать класс.

В [], сказано: «Библиотеки DLL не могут экспортировать классы и объекты, если только вы не используете специализированную технологию Microsoft под названием СОМ или какую-либо другую усовершенствованную технологию». Впрочем, там же есть замечание: «На самом деле объекты могут быть переданы из DLL в вызывающую программу в случае, если эти объекты спроектированы для использования в качестве интерфейсов или чистых абстрактных классов». Кроме этого замечания, в [] об экспорте объектов почти всё, но уже хорошо, что есть шанс «сделать это по-быстрому».

И, наконец, в [] находим параграф «Экспорт объектов из DLL». Там сказано: «К объекту и его методам можно получить доступ, даже если этот объект содержится внутри DLL. Но к определению такого объекта внутри DLL и его использованию предъявляются определенные требования. Иллюстрируемый здесь подход применяется в весьма специфических ситуациях, и, как правило, такого же эффекта можно достичь путем применения пакетов или интерфейсов». Наша ситуация вполне специфическая; пакеты здесь неприменимы, так как они все же для использования с Delphi, про использование интерфейсов и СОМ уже сказано, а использовать интерфейсы без СОМ вне Delphi, судя по [], нельзя.

И, пожалуй, самое важное из []:
«На экспорт объектов из DLL накладываются следующие ограничения:
  1. Вызывающее приложение может использовать лишь те методы объекта, которые были объявлены как виртуальные.
  2. Экземпляры объектов должны создаваться внутри DLL.
  3. Экспортируемый объект должен быть определен как в DLL, так и в вызывающем приложении с помощью методов, определенных в том же порядке.


  4. Из объекта, содержащегося вутри DLL, нельзя создать объект-потомок.
Здесь перечислены лишь основные ограничения, но возможны и некоторые другие.»

Далее там рассказывается о работе с DLL, написанной в Delphi, но полученной информации достаточно для работы с DLL, создаваемой в MS VC++.

Мастер MS VC++ позволяет создать обычную (regular) DLL и DLL-расширение (extension). Обычная DLL может экспортировать только С-функции и не способна экспортировать С++-классы, функции-члены или переопределенные функции []. Стало быть, надо использовать DLL-расширение. Мастер создаст заготовку, затем в каталог проекта надо будет скопировать два файла – заголовочный и файл кода (*.h и *.cpp), содержащие класс, с экземпляром которого предстоит поработать. Затем подключить их к проекту DLL и немного доработать в соответствии с указанными ограничениями.

Во-первых, все экспортируемые открытые методы (ну, или функции-члены, как хотите) необходимо объявить с директивой __stdcall, по понятным причинам. Во-вторых, их также необходимо объявить виртуальными. Это делается для того, чтобы точки входа оказались записанными в таблицу виртуальных функций (VMT), через которую и будет осуществлятся экспорт-импорт. В-третьих, класс требуется объявить с макроопределенной директивой AFX_EXT_CLASS или AFX_CLASS_EXPORT, это синонимы. Сделанные изменения не влияют на работоспособность класса в ехе-проекте, даже директива экспортируемого класса.

Далее в файл .срр проекта DLL нужно добавить функции создания и ликвидации объекта. Пример в [] обходится без функции ликвидации, видимо, потому, что в приведенном там примере и DLL и импортирующее приложение написаны на Delphi, так что можно освободить память методом Free, который есть у всех наследников TObject и отсутствует у объектов С++, не имеющих общего класса-предка. Функция создания объекта должна просто вызывать конструктор, передать ему полученные от приложения параметры и вернуть указатель на созданный объект. Функция ликвидации принимает указатель на объект и вызывает деструктор. И обязятельно вписать эти функции в список экспортируемых.

И всё! Пятнадцать минут работы, при минимальном знании С++. Остальное в Delphi.

В импортирующей программе необходимо объявить класс, содержащий виртуальные открытые функции в том же порядке. Также необходимо объявить сложные структуры данных, используемые в DLL и передаваемые через ее границу в любом направлении. Имеются в виду структуры С++, они же записи Паскаля. И, конечно же, нужно объявить импортируемые функции создания и уничтожения класса. Теперь для создания экземпляра класса вызывается соответствующая функция DLL, когда объект перестает быть нужным – снова вызывается соответствующая функция DLL, а методы вызываются традиционно – «Объект.Метод(Параметры)». При этом обзывать методы в Delphi можно как угодно, важен лишь порядок их следования и списки параметров.

Если в С++ функция-член возвращает значение, в Delphi соответствующий метод должен быть тоже функцией. Если же функция-член возвращает void, в Delphi соответствующий метод – процедура.

Если в С++ параметр передается по значению, то и в Delphi тоже, если же по ссылке (то есть как указатель), то в Delphi такой параметр должен быть объявлен с ключевым словом var.

Чуть подробнее о параметрах и их типах. Практически везде, где говорится о DLL, упоминается, что, если хотите обеспечить совместимость DLL с программами на других языках, необходимо обеспечить совместимость типов. То есть, стремиться использовать стандартные типы ОС Windоws. Такие типы, как string или file вообще не совместимы с С++, с TDateTime можно поэкспериментировать, вообще-то, он соответствует стандарту, принятому в OLE-автоматизации ([]). Опять же, [] заявляет о соответствии типов single и double Delphi с float и double в С++ соответственно. Хотя в [] есть такой совет со ссылкой на News Group: «Если вы создаете DLL не с помощью Delphi, то избегайте чисел с плавающей точкой в возвращаемом значении. Вместо этого используйте var-параметр (указатель или ссылочный параметр в С++) Причина кроется в том, что Borland и Microsoft применяют различные способы возврата чисел с плавающей точкой. Borland С++ и Delphi могут использовать один и тот же метод».

Тип С++БайтовТип Delphi
int?(4)integer
unsigned int?(4)cardinal
char, __int8 1 shortint
short, __int162smallint
long, __int32 (int)4longint (integer)
__int648int64
unsigned char1byte
unsigned short2word
unsigned long4longword
float4single
double8double
char *PChar

Таблица соответствия типов Delphi и С++



Содержание раздела