Главная » C++, C++ Builder » Создание формы для динамической загрузки DLL в CBuilder

0

Для того чтобы использовать нашу DLL в проекте CBuilder, нам нужна форма,  с  помощью которой пользователь вызывал бы эти функции, а также для просмотра результатов. На рис. 10.4 показана форма, которую мы будем использовать в нашем проекте динамической DLL.  Как видите,  мы  построили  эту  форму,  использовав  три  кнопки,  метку  и  поле  для  ввода  данных,

посылаемых в функции в DLL.

Первый шаг при загрузке информации из DLL в точности такой же, как и при получении ресурсов из файла, то есть нам нужно получить ссылку на библиотеку с помощью  функции API LoadLibrary. Для этого добавьте следующий код в конструктор формы:

__fastcall TForm1::TForm1(TComponent* Owner)

{

hLibHandle = LoadLibrary("vcdll.dll");

}

Аналогично, нам нужно убедиться, что ссылка на библиотеку освобождена, когда мы закончили с ней работать.  Это делается в обработчике события  OnClose формы,  FormClose. Создайте обработчик и добавьте в него следующий код:

void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)

{

if ( hLibHandle ) FreeLibrary( hLibHandle );

}

Рис. 10.4. Форма для динамической загрузки DLL

По крайней мере, до этой точки процесс, которому мы следовали, полностью совпадает с тем, что мы делали при загрузке строк из DLL. Это место в процессе, где два примера расходятся. Давайте посмотрим на это,  добавив обработчик для нажатия  на первую кнопку (Вызвать функцию 1)  в форму. Вот код, который нужно добавить в обработчик Button1Click:

void __fastcall TForm1::Button1Click(TObject *Sender)

{

if ( hLibHandle )

{

// Пытаемся загрузить функцию из библиотеки pfivFunc pFunc = (pfivFunc)GetProcAddress( hLibHandle,

"CallFunction1"); if ( pFunc ) (*pFunc)();

}

}

Вам нужно добавить еще одну строку в файл, чтобы он скомпилировался. Добавьте следующую строку прямо над функцией, а потом мы поговорим о том, что же происходит в этом фрагменте кода:

typedef int (*pfivFunc)(void);

Пойдем по порядку. Эта строка (typedef) — способ описания нового типа в C или C++. Хотя она и выглядит совершенно по-другому, нежели обычные описания типов, которые вы когда-либо видели в своем коде, это на самом деле просто отдельный специальный случай оператора typedef.

Основная форма оператора typedef, которая просто описывает новый тип  как  более  простую строку для компилятора, выглядит так: typedef описание-старого-типа описание-нового-типа. Вот простой пример, чтобы вы поняли, как это работает. Представьте, что вы хотите описать новый тип, IntBool, для представления логического (булевского) значения в виде целого. Этот тип не отличается от целого, но мы хотим, чтобы программист, читающий код, понимал, что данная переменная должна хранить только булевские значения (true, истина, или false, ложь) в целом представлении. Тогда мы напишем:

typedef int IntBool;

Теперь далее в коде мы можем использовать новый тип для описания переменных: IntBool bMyInteger;

Для компилятора нет разницы в использовании типов int и IntBool. Он просто сопоставляет один другому. В нашем типе pfivFunc (см. выше) мы на самом деле написали, что тип pfivFunc представляет функцию, у которой нет параметров (ключевое слово void в скобках) и которая возвращает целое значение. Общая форма оператора typedef по отношению к функциям такова:

return-type (*function-name)(parameter types)

где return-type — это тип возврата функции (int, void, bool и т. д.), а function-name— имя определения typedef. Это имя, которое мы будем использовать как новый тип, точно так же, как мы использовали раньше IntBool. Parameter types — список корректных типов C++ (включая новые типы). Если у вашей функции три параметра, скажем целое, вещественное и ссылка (reference) на объект класса Foo, то это будет выглядеть так: (int, float, Foo&).

Теперь, когда эта странность с оператором typedef в коде позади, весь оставшийся код ясен как божий день. Мы используем функцию API GetProcAddress для получения указателя на функцию, находящуюся в библиотек е DLL, на которую у нас есть ссылка. Этот указатель будет типа FARPROC, что есть просто указатель на функцию. Нам же нужно вызывать эту функцию с параметрами и проверять возвращаемое значение, так что нужно объяснить компилятору, что у функции есть такие параметры. Так как у нас есть замечательный оператор typedef, описывающий в точности прототип нашей функции, то мы преобразу ем возвращенный  указатель  типа FARPROC в один из этих новых типов указателей на функции и затем косвенно вызываем нашу функцию, применяя оператор разрешения указателя (звездочку) и передавая ей аргументы (если они есть, конечно).

ЗАМЕЧАНИЕ

Очень важно, чтобы типы полностью соответствовали количеству и типу параметров, которые вы передаете таким образом в функцию. Ошибка в данном месте может привести к ужасным последствиям, включая фатальный сбой операционной системы Windows.

Как вы помните, первая функция (CallFunction1) в библиотеке  просто  отображала  окно сообщения. Функция GetProcAddress извлекает адрес функции CallFunction1 из DLL, находя внешнее (экспортированное) имя в DLL, совпадающее с именем, которое мы передаем.  Вот почему так важно использовать модификатор extern "C" в определении DLL. Иначе нам бы пришлось вызывать извращенное (mangled) (иногда его называют  мягче,  декорированное, decorated) имя в DLL, и в результате мы вызывали бы что-нибудь вида «CallFunction1@iv1». А в нашем случае мы обращаемся к функции просто по имени.

ЗАМЕЧАНИЕ

Вам, наверное, интересно, можете ли вы использовать функцию GetProcAddress для доступа к функциям в вашей программе и использования их, например, для реализации языка сценариев в вашей программе. Да, вы можете так делать. Если функции доступны по имени и вы знаете это имя, можете вызывать их из любой программы, на которую у вас есть ссылка. Теоретически, если вам удалось добыть ссылку на экземпляр запущенной программы, вы можете  также запускать функции, содержащиеся в ней.

Для второй кнопки нам нужна функция, имеющая один аргумент — строку (const char *) и возвращающая целое значение. Для определения типа, который представляет такие функции, нам нужно добавить следующую строку в исходный файл:

typedef int (*pficsFunc)(const char *);

Соответственно, мы теперь можем добавить обработчик для нажатия на вторую кнопку на форме, вызывающий вторую функцию в DLL (CallFunction2). Создайте обработчик для второй кнопки и поместите в него следующий код:

void __fastcall TForm1::Button2Click(TObject *Sender)

{

if ( hLibHandle )

{

// Пытаемся загрузить функцию из библиотеки pficsFunc pFunc = (pficsFunc)GetProcAddress( hLibHandle,

"CallFunction2");

if ( pFunc )

(*pFunc)(Edit1->Text.c_str());

}

}

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

Стоит заметить, что данный код хорошо защищен. Есть несколько проблем, которые могут возникнуть в этом коде, так что он делает все, чтобы предотвратить (в случае каких-либо ошибок) появление катастрофических нарушений в системе. Во-первых, мы проверяем ссылку на DLL, не равна ли она NULL. Это может привести к серьезным  проблемам у функции  GetProcAddress. Когда мы проверили, что ссылка корректна (в силу нашего понимания), следующим мы проверяем

указатель, возвращенный функцией API GetProcAddress. Если функция не может найти запрошен

ную функцию в DLL или не может выполнить запрос по какой-то другой причине, то возврат от функции API будет равен NULL (большинство функций API, которые возвращают ссылки или указатели, вернут вам NULL в случае какой-либо ошибки). В таком случае нам не хотелось бы вызывать косвенно функцию, так как это приведет к нарушению защиты Windows или даже хуже.

Третья (последняя) кнопка вызывает функцию, которая имеет один параметр— целое и ничего не возвращает (void). Прототип функции в операторе typedef C++ выглядит так:

typedef void (*pfviFunc)(int);

После чего мы можем использовать этот тип для передачи данных в функцию, написав код для обработчика нажатия на третью кнопку. Добавьте этот обработчик и внесите в него такой код:

void __fastcall TForm1::Button3Click(TObject *Sender)

{

if ( hLibHandle )

{

// Пытаемся загрузить функцию из библиотеки pfviFunc pFunc = (pfviFunc)GetProcAddress( hLibHandle,

"CallFunction3"); if ( pFunc )

(*pFunc)(atoi(Edit1->Text.c_str()));

}

}

Опять же, мы извлекаем функцию из DLL, передавая ей на этот раз целый аргумент. Хотя я и предупреждал, что не надо передавать свойство Text в функцию, в данном  случае  это  делать можно. Плохой идеей является  передача свойства Text в виде  строки функции, которая может изменить строку. В этом случае может получиться непредсказуемый результат. В случае же функции atoi, которая обещает не изменять переданный аргумент, это является относительно безопасным маневром.

Теперь, когда вы скомпилируете и соберете приложение, сможете увидеть результаты вашего упорного труда. Во-первых, убедитесь, что файл VCDLL.DLL находится в том же каталоге, что и программа (project1.exe). Тогда можно быть уверенным, что Windows найдет этот файл и загрузит его. Если вы не положите файл в тот же каталог, что и исполняемый файл, то Windows применит простой процесс для поиска этого файла. В первую очередь система будет искать этот файл в каталогах Windows и Windows\System (для Windows NT это,  соответственно,  WinNT, WinNT\System и WinNT\System32). Если файла там нет, то Windows будет искать в каждом каталоге, заданном в переменной окружения path (у Windows NT есть специальная переменная, которая задает путь для поиска  DLL). И наконец,  если файл не  будет найден,  функция LoadLibrary вернет NULL, что означает, что DLL не обнаружена.

Если вы на самом деле положили DLL в такое место, где Windows сможет его найти, форма загрузится и отобразит желанные кнопки. Введите какой-нибудь  текст  в  поле  ввода  в  нижней части формы и нажмите на вторую кнопку (Вызвать функцию2). Вы увидите окно сообщения с текстом, который вы ввели, показанное на рис. 10.5. Это означает, что функция была найдена и что текст был корректно передан функции в DLL.

Рис. 10.5. Форма приложения «динамическая загрузка DLL» во время выполнения

На этом мы завершаем дискуссию о работе с динамическими библиотеками. Эта очень мощная технология позволяет вам вынести часть вашего кода, содержащего общие функции, во внешнюю разделяемую библиотеку, которую можно подгрузить при желании во время выполнения. У такого подхода есть два очевидных преимущества. Во-первых, код будет лежать в одном и том же месте, даже если его используют разные приложения. Это экономит дисковое пространство и память. Во- вторых, использование DLL для хранения общих функций программы позволит вам менять эти функции без необходимости изменять само приложение. Если в какой-то функции обнаружена ошибка, то вы просто распространяете среди пользователей DLL, содержащую эту  функцию, вместо того чтобы рассылать всем все приложение целиком.

Источник: Теллес М. – Borland C++ Builder. Библиотека программиста – 1998

По теме:

  • Комментарии