Большой архив статей, книг, документации по программированию, вебдизайну, компьютерной графике, сетям, операционным системам и многому другому
 
<Добавить в Избранное>    <Сделать стартовой>    <Реклама на сайте>    <Контакты>
  Главная Документация Программы Обои   Экспорт RSS E-Books
 
 

   Программирование -> C++ Builder -> Работа с DLL в C++ Builder

   DLL - одна из самых полезных и мощных возможностей, когда-либо добавленных в операционную систему Windows. Используя DLL, вы решаете две главные задачи в разработке приложений: ограничения по памяти и проблемы с версиями.
   Ограничения по памяти в многозадачных операционных системах возникают из-за того, что одновременно загружено в память много программ. Многие из этих программ выполняют одни и те же задачи, однако каждая содержит свой собственный код для выполнения этой работы. Если бы весь этот код был извлечен из приложения и помещен в отдельный модуль, совместно используемый всеми запущенными программами, то было бы гораздо больше свободной памяти для программ. Это основная концепция DLL. Лучшим примером DLL может служить сама операционная система. Все компоненты Windows, которые вы вызываете из вашего приложения (код прорисовки, код файловой системы и т. д.), живут в наборе системных DLL, которые используются совместно всеми программами, работающими в Windows. Возможность совместно использовать этот код означает, что вашей программе не нужно держать этот код в себе.
   Когда ваша программа загружает DLL, динамически или статически, происходит поиск в памяти на предмет того, загружена ли уже эта DLL. Если DLL найдена, то вместо того, чтобы загрузить вторую копию, операционная система возвратит вам указатель (называемый handle) на уже загруженную DLL и увеличит счетчик использования DLL.
   Важно, что вы можете не только загружать информацию, такую как ресурсы, из DLL, но также и использовать функции, которые там хранятся. В данном примере мы создадим библиотеку (DLL), а затем используем другую программу для загрузки функции из этой DLL в наше приложение. Мы можем использовать функции в DLL для отображения сообщения, подсчета результатов и любых других вещей, которые можно делать в обычном приложении. У DLL есть некоторые ограничения, в основном они относятся к управлению памятью, но к нашему примеру они отношения не имеют.

Создание DLL в CBuilder

Для того чтобы использовать код в DLL, нам сначала нужно создать DLL, содержащую код. Далее мы исследуем использование DLL, созданной в CBuilder, в другой системе, однако сейчас мы собираемся загружать эту DLL в CBuilder.
   Одно небольшое замечание. На самом деле есть два разных способа использовать функции в DLL, созданной в CBuilder. Первый - это создание библиотеки импорта утилитой implib, которая поставляется вместе с CBuilder. Затем библиотека импорта может быть напрямую встроена в приложение, и Windows автоматически загрузит вашу DLL во время выполнения. Запомните, что вам нужно использовать программу implib, поставляемую с CBuilder, а не более древнюю версию из другой системы, так как библиотеки CBuilder имеют другой формат, нежели старые библиотеки, и поэтому нужны свежие программы для генерации этих библиотек из DLL. Если у вас есть библиотека импорта для вашей DLL, вы можете просто описать прототипы функций, которые вам нужны, и использовать их напрямую, как будто они были описаны в вашем приложении. В нашем примере мы более заинтересованы в механизме динамической загрузки, так что мы все сделаем сами.
В этом примере мы создадим DLL в другой среде разработки, в данном случае - в Visual C++. Эта среда выбрана потому, что Visual C++ является довольно популярным языком программирования, а также из-за того, что вы скорее будете использовать DLL из других источников (так называемые third-party), чем созданные в CBuilder. В любом случае нам нужно делать одно и то же, так почему бы не показать вам еще кое-что?
В отличие от CBuilder среда Visual C++ (VC) не хочет сама создавать для вас DLL. Вместо этого она создаст файл сборки (makefile) и оставит его пустым, без исходных файлов.В любом случае создайте в Visual C++ (или в обычном текстовом редакторе) новый файл и добавьте в него следующий код:

#include
#include
#include
#include
BOOL WINAPI DllEntryPoint (HINSTANCE hinstDLL, DWORD fdwReason. LPVOID IpReserved)
{
   BOOL fSuccess = TRUE;
   switch (fdwReason) {
      case DLL_PROCESS_ATTACH:
      case DLL_THREAD_ATTACH:
                                               break;
      case DLL_PROCESS_DETACH:
      case DLL_THREAD_DETACH:
                                               break;
   }
   return (fSuccess);
}

extern "C"
{
__declspec( dllexport ) int CalFunction1( void )
{
   MessageBox(NULL,"You called function 1", "Info", MB_OK);
   return 0;
}
__declspec( dllexport ) int CallFunction2( const char *str )
{
   MessageBox(NULL, "You called function 2", str, MB_OK);
   return 0;
}
__declspec( dllexport ) void CallFunction3( int nValue )
{
   char szBuffer[20];

   sprintf(szBuffer, "%d". nValue);
   MessageBox(NULL, szBuffer, "Value", MB_OK);
}

   Важным в этом коде являются три функции. Пока что вы можете игнорировать смешно выглядящие модификаторы declspec (dll export). Это просто способ объяснить VC, что эти функции должны быть экспортируемыми в DLL, чтобы другие программы могли их использовать. Может быть, вы думали, что функции экспортируются по умолчанию; в таком случае многое в мире программирования под Windows является не таким, каким оно вам казалось. В DLL определены три функции: CallFunctlon1, CallFunction2 и CallFunction3. Они отличаются типом возвращаемого значения, а также количеством и типами аргументов. Мы будем использовать все три функции в нашем примере.

Замечание Как вы заметили, функции объявлены с модификатором extern "С" в предыдущем листинге. Visual C++ и CBuilder используют разные схемы изменения имени (mangling) при генерации имен для функций. Чтобы не смотреть в листинг экспорта и запоминать полученные имена функций в переделанном виде, мы разрешаем компилятору использовать соглашение о вызовах, а также схему внешних имен, принятые в С. Это означает, что в результате имена функций в экспортном виде будут просто CallFunction1 и т. д.

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

Создание формы для динамической загрузки DLL


   Для того чтобы использовать нашу 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 );
}

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

void __fastcall TForm1::Button1Click (TObject *Sender)
{
   if ( hLibHandle )
   {
      // Пытаемся загрузить функцию из библиотеки
       pfivFunc pFunc = (pfivFunc)GetProcAddress(hLibHandle,"Call Function!");
       if ( pFunc ) (*pFunc)();
   }
}

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

typedef int (*pfivFunc)(void);

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

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

   Как вы помните, первая функция (CallFunction1) в библиотеке просто отображала окно сообщения. Функция GetProcAddress извлекает адрес функции CallFunction1 из DLL, находя внешнее (экспортированное) имя в DLL, совпадающее с именем, которое мы передаем. Вот почему так важно использовать модификатор extern "С" в определении 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());
   }
}

   Процесс загрузки отличается немногим. Мы просто извлекаем функцию CallFunction2 из 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 и WindowsSystem (для Windows NT это, соответственно, WinNT, WinNTSystem и WinNTSystem32). Если файла там нет, то Windows будет искать в каждом каталоге, заданном в переменной окружения path (у Windows NT есть специальная переменная, которая задает путь для поиска DLL). И наконец, если файл не будет найден, функция LoadLibrary вернет NULL, что означает, что DLL не обнаружена.
   Если вы на самом деле положили DLL в такое место, где Windows сможет его найти, форма загрузится и отобразит кнопки. Введите какой-нибудь текст в поле ввода в нижней части формы и нажмите на вторую кнопку (Вызвать функцию 2). Вы увидите окно сообщения с текстом, который вы ввели. Это означает, что функция была найдена и что текст был корректно передан функции в DLL.
   На этом мы завершаем дискуссию о работе с динамическими библиотеками. Эта очень мощная технология позволяет вам вынести часть вашего кода, содержащего общие функции, во внешнюю разделяемую библиотеку, которую можно подгрузить при желании во время выполнения. У такого подхода есть два очевидных преимущества. Во-первых, код будет лежать в одном и том же месте, даже если его используют разные приложения. Это экономит дисковое пространство и память. Во-вторых, использование DLL для хранения общих функций программы позволит вам менять эти функции без необходимости изменять само приложение. Если в какой-то функции обнаружена ошибка, то вы просто распространяете среди пользователей DLL, содержащую эту функцию, вместо того чтобы рассылать всем все приложение целиком.

 

 
Интересное в сети
 
10 новых программ
CodeLobster PHP Edition 3.7.2
WinToFlash 0.7.0008
Free Video to Flash Converter 4.7.24
Total Commander v7.55
aTunes 2.0.1
Process Explorer v12.04
Backup42 v3.0
Predator 2.0.1
FastStone Image Viewer 4.1
Process Lasso 3.70.4
FastStone Image Viewer 4.0
Xion Audio Player 1.0.125
Notepad GNU v.2.2.8.7.7
K-Lite Codec Pack 5.3.0 Full


Наши сервисы
Рассылка новостей. Подпишитесь на рассылку сейчас и вы всегда будете в курсе последних событий в мире информационных технологий.
Новостные информеры. Поставьте наши информеры к себе и у вас на сайте появится дополнительный постоянно обновляемый раздел.
Добавление статей. Если вы являетесь автором статьи или обзора на тему ИТ присылайте материал нам, мы с удовольствием опубликуем его у себя на сайте.
Реклама на сайте. Размещая рекламу у нас, вы получите новых посетителей, которые могут стать вашими клиентами.
 
Это интересно
 

Copyright © CompDoc.Ru
При цитировании и перепечатке ссылка на www.compdoc.ru обязательна. Карта сайта.
 
Rambler's Top100