Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Автоматизация кодирования импорта функций из DLL

Оформил: DeeCo

Автор: Кочетов Андрей

Круг рассматриваемых вопросов

В этой статье будут рассмотрены методы автоматизации кодирования импорта функций из динамически подключаемых DLL.

Решение этой задачи позволит значительно сократить трудоёмкость (а значит, и время, и количество внесённых ошибок) написания похожего кода в разных проектах и/или для разных DLL.

Материал проиллюстрирован исходными текстами на Borland C++ Builder 6.0; однако все опубликованные идеи справедливы (и применимы с минимальной адаптацией) для любого языка/среды разработки.

Введение и библиография

В журнале "Программист" (№9 за 2002 г.) была опубликована статья Вячеслава Ермолаева "Использование template-классов при импортировании функций из DLL".

Уважаемый автор предлагает для решения этой задачи использовать механизм шаблонов (templates) языка С++. Это огромный шаг вперёд по сравнению с рутинным кодированием, но на мой взгляд, не идеальное решение, которому присущи некоторые недостатки:

  • Всё разнообразие возможных функций ограничивается богатством заранее описан-ных шаблонов; и хотя в статье автор отодвинул планку очень далеко - 13 параметров функций (а много ли Вы припомните функций, не укладывающихся в эти рамки ?), всё же сам факт несвободы как-то стесняет
  • Если Вы всё же где-то откопаете функцию с большим количеством параметров, Вам придётся расширить набор шаблонов, т.е. изменить библиотеку классов, что является крайне нетехнологичным решением - Вы же не изменяете исходники VCL!
  • При кодировании пользовательского приложения приходится использовать до-вольно-таки громоздкое описание импортируемых функций в обёртках template-классов, что не способствует мнемонической лёгкости чтения и понимания текста
  • Механизм шаблонов языка C++ позволяет уменьшить объём исходного, но никак не объектного, полученного после компиляции кода, который раздувается тем сильнее, чем более разнообразны параметры в импортируемых из DLL функциях.
  • Кроме того, в рамках предложенного метода остаётся нерешённой проблема автоматизации довольно трудоёмкой рутинной операции - определения полных идентифика-торов функций в DLL (строковых параметров для функции GetProcAddress):
    • Особых проблем не наблюдается, если все функции в DLL скомпилированы как "extern "C" - в этом случае линкер просто добавляет символ подчёрки-вания перед именем функции
    • Если же DLL собрана с функциями в стиле C++, всё совсем не так одно-значно: идентификаторы могут получиться до полуметра длиной , и выковыривание их из DLL - лишняя ручная работа; можно, конечно, зная прави-ла работы линкера, синтезировать их - но и это явно предмет для автоматизации, к тому же подозреваю, что у разных средств разработки (BCB, Visual C++) раз-ные правила, по которым работает линкер
Решение

Как прекрасна была бы жизнь, если б можно было все насущные нужды разработчика удовлетворить только средствами языка разработки! Увы, нет в мире совершенства, как говорил Лис, и поэтому фирмы-разработчики средств разработки генерируют всё новые, всё более мощные среды разработки (IDE), а также развивают сами языки программирования - взять те же Delphi, BCB, C# : сравните языковые средства с Pascal и C++. Вспомните также, сколько дополнительных (встроенных в IDE и отдельных) инструментальных средств входит в поставку BCB и Delphi.

Borland Software Corporation, отдав дань уважения OWL, задвинула её подальше и стала развивать Delphi и BCB на платформе VCL. Совершенно не вижу, почему бы благородным донам не поступить так же J.

Суть моего решения состоит в интеграции средства языка - компоненты - и дополнительного инструментального средства собственной разработки - DllWizard. Интерфейс-оболочку к DLL обеспечивает компонента TAskDll (исходный код - в архиве AskDll.zip). В её методах инкапсулированы:
  • динамическая загрузка DLL (функция LoadLibrary) в методе LoadDll
  • обработка исключительных ситуаций: при возникновении любых проблем с за-грузкой DLL формируется сообщение на языке локализации Windows и генерируется ис-ключение (Exception) для обработки в вызывающем приложении
  • выгрузка DLL и освобождение памяти (функция FreeLibrary), выполняемые авто-матически при прекращении существования компоненты (например, при закрытии формы, на которой расположена компонента)
Загрузка DLL и инициализация импортируемых функций осуществляется вызовом одного лишь метода компоненты - LoadDll. Параметр метода - указатель на функцию:
bool (*FuncGetProc)(HMODULE PtrDll)
Это должна быть функция вида:
bool Example_Load(HMODULE PtrDll)
{
  if((Func1=(DLL_Func1)GetProcAddress(PtrDll, "@Func1$qiii")) == NULL) return false;
  if((Func2=(DLL_Func2)GetProcAddress(PtrDll, "@Func2$qpct1")) == NULL) return false;

return true;
}

Всё, что нам нужно - это написать подобный код. Именно эта часть работы наиболее тру-доёмка, и когда мы сможем выполнить её легко, быстро и безошибочно, это и будет красивым венцом нашей технологии.

Задача решается с помощью DllWizard в 3 прохода:
  1. Автоматическое формирование описаний функций с их параметрами и возвращае-мыми значениями.
  2. Автоматическое формирование идентификаторов функций (строковых параметров для функции GetProcAddress)
  3. Генерация исходных текстов
Рассмотрим пример работы с Example.DLL, экспортирующей 2 функции:
int __cdecl Func1(int i1, int i2, int i3);
char * __cdecl Func2(char * s1, char * s2

Итак, начинаем:

  1. Запускаем DllWizard и создаём список всех функций, которые мы хотим импорти-ровать из DLL. Если DLL собственной разработки, достаточно просто указать путь к её исходнику и нажать кнопку "Найти": список сформируется автоматически (см.рис. 1)
  2. Указываем путь к DLL и нажимаем кнопку "Найти" (см.рис. 2)
  3. Нажимаем кнопку "Сгенерировать" - в каталогах, указанных на закладке "На-стройки" будут сформированы файлы

рис. 1

рис. 2

Имя DLL-ки является префиксом у всех сгенерированных файлов и у функции в модуле CPP. Исходный текст DLL и тестового приложения находится в архиве DllTest.zip

Подкаталог DLL содержит исходный текст библиотеки: UnitExample.cpp Подкаталог EXE содержит исходный текст тестового приложения: UnitDllTest.cpp и в подкаталоге DllWizard - сгенерированные файлы:
  1. Заголовочные файлы:
    1. Example_Descript.h является служебным и содержит описание функций
    2. Example_Declare.h является служебным и содержит объявления указателей на функции
    3. Example_Extern.h следует включить в тот исходный модуль проекта прило-жения, из которого вызываются функции, импортируемые из DLL.
  2. Example_Load.cpp содержит функцию загрузки Example_Load
Резюме

Подводим итоги. Ниже описан порядок разработки пользовательского приложения, им-портирующего функции из динамически подключаемой библиотеки функций:
  1. С помощью DllWizard генерируем включаемые модули
  2. В проект включаем сгенерированный модуль Example_Load.cpp
  3. "Бросаем" на главную форму компоненту TAskDll и в её свойстве DllName указы-ваем имя DLL-ки (см.рис. 3)

    рис. 3
  4. В модуль главной формы и во все модули, в которых предполагается использовать импортируемые из DLL функции, включаем заголовочный файл Example_Extern.h
  5. Пишем пользовательский код и компилируем проект. Всё! Скриншот работы тес-тового приложения приведён на рис. 4

рис. 4
Преимущества технологии
    1. Разнообразие импортируемых функций не ограничено ничем
    2. Не изменяются коды библиотеки (компоненты)
    3. Не происходит разбухания объектного кода, т.к. не используются шаблоны - все функции конкретно и явно описаны в заголовочных файлах
    4. Полностью автоматизированный процесс генерации кода, включая опреде-ление идентификаторов функций (параметров для GetProcAddress)
    5. Мнемоника кода не ухудшается: имена функций остаются неизменными
    6. Минимальный объём ручного кодирования - всего 2 строки:
      1. Включение заголовочного файла
      2. Вызов метода LoadDll
    1. Технология применима не только для BCB, но и для, например, Visual C++, а также - с небольшой адаптацией - для любого языка/среды разработки; Например, в Visual C++:
    2. сгенерированный код можно использовать без изменений (только за-комментировав включение vcl.h)
    3. вместо компоненты TAskDll следует создать класс.
    4. Многие разработчики делают компоненты-обёртки для функций DLL - их применение намного удобнее. Для этих целей как нельзя лучше подходит данная технология:
      1. Создаётся компонента, производная от TAskDll
      2. Сгенерированный модуль (Example_Load.cpp) включается в проект пакета
      3. В конструкторе компоненты свойству DllName присваивается имя DLL
      4. В методе Loaded компоненты вызывается метод LoadDll. Всё!
Заключение

Я успешно использую эту технологию в своей работе. DllWizard - по сути тривиальная утилита - была написана, отлажена и локализована (английский и русский интерфейсы) за 3 часа. В настоящий момент она не является инструментом общего пользования; ей присущи некоторые ограничения, так как я писал "под себя", а не "для дяди". Тем не менее, мне не жалко поделиться тем, что есть. Хочу ещё раз подчеркнуть, что ограничения свойственны конкретной реализации, а не идее:
  • Генерируется код только для С++
  • Нет полноценного лексического анализа исходного текста DLL: предполагается, что функции описаны в одну строку вида:
  • __declspec(dllexport) char* Func2(char *s1, char *s2) , где
    • __declspec(dllexport) - без пробелов
    • тип возвращаемого значения - без пробелов
    • после имени функции в скобках следует описание параметров
  • Не поддерживается анализ перегруженных функций в одной DLL: например,
    • void Func()
    • long Func(long lp)
    • Тем не менее, трудно отнести это к недостаткам, т.к. это крайне редкая ситуация

P.S. Немножко саморекламы:
компонента TAskDll является составной частью пакета ASK Tools, см. соответствующую ссылку на http://goldenask.narod.ru/

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay