скрыть

скрыть

  Форум  

Delphi FAQ - Часто задаваемые вопросы

| Базы данных | Графика и Игры | Интернет и Сети | Компоненты и Классы | Мультимедиа |
| ОС и Железо | Программа и Интерфейс | Рабочий стол | Синтаксис | Технологии | Файловая система |



Google  
 

Автоматизация кодирования импорта функций из DLL



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

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

В этой статье будут рассмотрены методы автоматизации кодирования импорта функций из динамически подключаемых 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)
    • Тем не менее, трудно отнести это к недостаткам, т.к. это крайне редкая ситуация





Copyright © 2004-2016 "Delphi Sources". Delphi World FAQ




Группа ВКонтакте   Ссылка на Twitter   Группа на Facebook