Квалифицированные специалисты, занимающиеся подготовкой программ для Windows, постепенно отказываются от применения инструментального комплекта SDK Windows и переходят к современным средам разработки для этой ОС, которые обеспечивают более высокую степень абстрагирования от действующих на низком уровне встроенных механизмов этой системы. В результате таких перемен в выигрышной ситуации оказался язык Microsoft Visual C++, в котором основой для объектно-ориентированного представления Windows API служит библиотека базовых классов MFC (Microsoft Foundation Classes).
MFC в руках опытного разработчика — мощное, но далеко не совершенное средство. В действительности едва ли не все знакомые мне программисты, полагающиеся в работе на MFC, вынуждены были хоть раз в жизни разрабатывать собственный набор расширений на базе классов MFC, из которых порождались необходимые производные классы. Но это, в конце концов, относится к возможностям Си++. Если какой-либо класс не годится для ваших нужд (и если, по существу, ему изначально отводилась роль базового класса), вы можете вывести из него производный класс, а затем внести изменения в те его компоненты, которые вас не устраивают.
Создание файла dll с иконками
Предположим, что вы подготовили набор расширений для MFC, которыми, как предполагается, будут пользоваться и другие программисты вашей компании. Возникает вопрос, как скомпоновать эти расширения? Можно было бы распространить файлы с исходными текстами или раздать компоненты, совместимые с галереей Component Gallery Visual C++.
Или, если вы предпочитаете MFC, скомпоновать подготовленные вами расширения в виде DLL-модулей. Из DLL-модулей расширения MFC классы экспортируются таким же образом, как из обычных DLL-модулей функции. Любая программа, динамически связанная с DLL-модулями MFC, может опять же динамически связываться с DLL-модулями расширения MFC. Все, что требуется от разработчика, — включить необходимые заголовочные файлы и библиотеку импорта DLL в список связей этой программы.
Насколько трудно подготовить DLL-модуль расширения MFC? Очень просто, если прибегнуть к средствам Visual C++ и MFC. В этой статье мы покажем, как создать DLL-модуль расширения MFC, который восполнил бы вопиющий недостаток MFC класса CToolTipCtrl. Кроме этого, мы расскажем, как составить программу, работающую с этим модулем. Когда вы поймете, насколько все это просто, у вас, возможно, возникнет желание самостоятельно заняться разработкой MFC-расширений.
Подготовка DLL-модуля расширения MFC
Теоретически вы без каких-либо трудностей назначите подсказки TollTips элементам управления диалогового окна или его произвольно выбранным областям с помощью имеющегося в MFC класса CToolTipCtrl. (Подсказки ToolTips — это миниатюрное окно со справочным текстом, которое всплывает, когда курсор мыши устанавливается на кнопке инструментальной панели или на другом объекте интерфейса.) Но есть одна проблема. Поскольку функция CToolTipCtrl::AddTool не выполняет автоматически деления на подклассы окна, которому назначается подсказка ToolTip, информацию о событиях, связанных с мышью, вам приходится передавать элементу управления ToolTip самостоятельно. Это обычно означает, что задача деления окна на подклассы возлагается на вас.
Создание статической и динамической библиотек Visual Studio C++
Элемент управления ToolTip, который служит основой объекта CToolTipCtrl, будет самостоятельно выполнять деление на подклассы, если вы позаботитесь о передаче ему соответствующих флажков, — функция, которую команда разработчиков MFC обошла своим вниманием. К счастью, исправить эту оплошность совсем нетрудно. Надо просто подготовить класс, производный от класса CToolTipCtrl, и заменить функцию CToolTipCtrl::AddTool аналогичной, содержащей флажок TTF_SUBCLASS в поле uFlags структуры TOOLINFO, используемой в сообщениях TTM_ADDTOOL. А еще лучше заменить ее двумя функциями — для добавления подсказок ToolTips к дочерним окнам и к прямоугольным областям окна. Функция AddTool предусматривает эти возможности, но синтаксически два этих метода очень отличаются друг от друга.
На лист. 1 и 2 приведен исходный текст для производного от CToolTipCtrl класса, носящего название CToolTipCtrlEx. Производному классу помимо унаследованных от CToolTipCtrl:AddWindow принадлежат две функции: для назначения подсказки ToolTip дочернему окну и AddRectangle, которая выполняет то же самое для прямоугольной области окна. Чтобы облегчить задачу назначения подсказки, в обеих функциях используется флажок TTF_SUBCLASS. Обозначим объект CToolTipCtrlEx как m_tooltipCtrl, тогда фрагмент для присвоения подсказки ToolTip кнопочному переключателю с идентификатором IDC_BUTTON выглядит довольно элементарно:
m_tooltipCtrl.AddWindow (GetDlgItem (IDC_BUTTON), «Введите сюда текст подсказки!»);
Лист. 1. Заголовочный файл класса CToolTipCtrlEx.
// ToolTip.h: заголовочный файл // //////////////////////////////////////////////////////////// //Окно CToolTipCtrlEx class AFX_EXT_CLASS CToolTipCtrlEx : public CToolTipCtrl < // Конструктор public: BOOL AddRectangle (CWnd* pWnd, LPCTSTR pszText, LPCRECT pRect, UINT nIDTool); BOOL AddWindow (CWnd* pWnd, LPCTSTR pszText); CToolTipCtrlEx(); //Атрибуты public: //Операции public: //Переопределения // ClassWizard generated virtual function overrides //>AFX_VIRTUAL //Реализация public: virtual ~CToolTipCtrlEx(); // Сгенерированные функции message map protected: //>AFX_MSG DECLARE_MESSAGE_MAP() >;
Лист. 2. cpp-файл для класса CToolTipCtrlEx.
// ToolTip.cpp : файл реализации // #include «stdafx.h» #include «stdafx.h» #include «ToolTip.h» #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif //////////////////////////////////////////////////////////// // CToolTipCtrlEx CToolTipCtrlEx::CToolTipCtrlEx() < >CToolTipCtrlEx::~CToolTipCtrlEx() < >BEGIN_MESSAGE_MAP(CToolTipCtrlEx, CToolTipCtrl) //>AFX_MSG_MAP END_MESSAGE_MAP() /////////////////////////////////////////////////////////// // Обработчики сообщений CToolTipCtrlEx BOOL CToolTipCtrlEx::AddWindow (CWnd* pWnd, LPCTSTR pszText) < TOOLINFO ti; ti, cbSize = sizeof (TOOLINFO); ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; ti.hwnd = pWnd->GetParent ()->GetSafeHwnd (); ti.uId = (UINT) pWnd->GetSafeHwnd (); ti.hinst = AfxGetInstanceHandle (); ti.lpszText = (LPSTR) pszText; return (BOOL) SendMessage (TTM_ADDTOOL, 0, (LPARAM) > BOOL CToolCtrlEx::AddRectangle (CWnd* pWnd, LPCTSTR pszText, LPCRECT pRect, UINT nIDTool) < TOOLINFO ti; ti.cbSize = sizeof (TOOLINFO); ti.uFlags = TTF_SUBCLASS; ti.hwnd = pWnd->GetSafeHwnd (); ti.uId = nIDTool; ti.hinst = AfxGetInstanceHandle (); ti.lpszText = (LPSTR) pszText; ::CopyRect ( return (BOOL) SendMessage (TTM_ADDTOOL, 0, (LPARAM) >
Как оформить класс CToolTipCtrlEx в виде DLL-модуля? Предлагаем пошаговые инструкции для Visual C++ версии 4.x:
- Запуcтите Visual C++, создайте новый проект, выбрав пункт New (новый) в меню File, и щелкните дважды на команде Project Workspace (рабочая область проекта). В поле Name (имя) рабочей области нового проекта New Project Workspace наберите символы «MfcExt» (без кавычек). В окне Type (тип) выберите «мастера» MFC AppWizard(dll), затем щелкните на кнопке Create (создать).
- В окне Step 1 «мастера» AppWizard в ответ на вопрос: «What type of DLL would you like to create?» («Какого типа DLL-модуль вы хотите сформировать?») — выберите «MFC Extension DLL (using shared MFC DLL) [DLL-модуль расширения MFC (с использованием разделяемого модуля DLL MFC).] Щелкните на кнопке Finish (завершить) и на кнопке OK, чтобы сгенерировать исходный текст DLL-модуля. Обратите внимание на заключенную в скобки фразу «using shared MFC DLL», следующую за словами «MFC Extension DLL». Если вы составляете DLL-модуль расширения MFC, нет необходимости реализовывать возможность постоянной привязки к MFC. DLL-модуль должен быть связан с библиотеками MFC динамически.
- Обратитесь к «мастеру» классов ClassWizard и щелкните на кнопке Add Class (добавить класс). Выберите New, чтобы открыть окно Create New Class (создать новый класс). В этом окне выберите CToolTipCtrl в панели «Base class» (базовый класс) и тип «CToolTipCtrlEx» в панели «Name». Щелкните на кнопке Change (изменить), чтобы изменить имена файлов Tooltip.h и Tooltip.cpp. (Особой надобности в подобных изменениях нет, но я все же выполняю этот шаг, чтобы избавиться от длинных имен файлов.) Отмените выбор в окне «Add to Component Gallery» («Добавить к галерее компонентов»), чтобы сэкономить пространство на диске. Щелкните на команде Create, чтобы сообщить «мастеру» классов ClassWizard о необходимости породить новый класс, и на кнопке OK, чтобы завершить работу этого «мастера».
- Добавьте к классу CToolTipCtrlEx принадлежащие функции AddWindow и AddRectangle. Позаботьтесь о том, чтобы обе функции были объявлены как public (общедоступные), поскольку обращения к ним будут происходить за пределами класса CToolTipCtrlEx. В Visual C++ есть простой способ добавления функции, принадлежащей классу, — щелкнуть правой клавишей мыши на имени класса в окне ClassView и выбрать в контекстном меню команду Add Function (добавить функцию).
- В окне ClassView щелкните дважды на CToolTipCtrlEx, чтобы открыть заголовочный файл этого класса. Добавьте AFX_EXT_CLASS к описанию класса справа от ключевого слова class (см. лист. 1).
- Создайте свой проект. В результате вы получите два очень важных файла: собственно DLL-модуль (Mfcext.dll) и библиотеку импорта DLL-модуля (Mfcext.lib). Библиотека импорта содержит в основном список имен экспортируемых функций или, как в нашем случае, перечень экспортируемых классов Cи++. Благодаря связи с библиотекой импорта программа может работать с классами, экспортируемыми из DLL-модулей расширения MFC, так, словно библиотека, содержащая эти классы, связана статически. Кроме этого, связь с библиотекой Mfcext.lib служит для Windows признаком того, что для выполнения конкретной программы необходим файл Mfcext.dll.
Применение DLL-модуля расширения MFC
Подготовка программы, работающей с модулем Mfcext.dll, не составляет особого труда. Просто включите заголовочный файл Tooltip.h в каждый класс, работающий с производным классом CToolTipCtrlEx, и включите файл Mfcext.lib в список связанных с вашим проектом библиотек. После чего следует рассматривать класс CToolTipCtrlEx как обычный класс MFC. Не забудьте, что нужно выбрать «As a shared DLL» («Как разделяемый DLL-модуль»), когда наступит момент отвечать на вопросы «мастера» AppWizard, чтобы ваша программа могла связываться с MFC динамически. Вы можете добавить в список связей вашего проекта файл Mfcext.lib, выберите для этого пункт Settings (параметры) в меню Build языка Visual C++, щелкните на закладке Link (связь) и введите маршрут к файлу Mfcext.lib в окне «Object/library modules» (объектные/библиотечные модули).
Для примера мы приводим программу ToolTest, которая выводит единственную кнопку в диалоговом окне. Она динамически связывается с файлом Mfcext.dll и использует класс CToolTipCtrlEx. Программа работает с диалоговыми окнами, а классу диалогового окна принадлежит переменная CToolTipCtrlEx по имени m_tooltipCtrl. Следующий фрагмент исходного текста функции OnInitDialog диалогового окна создает элемент управления ToolTip и присваивает экранной кнопке этого окна подсказку ToolTip:
m_tooltipCtrl.Create (this); m_tooltipCtrl.AddWindowTool ( GetDlgItem (IDC_EXIT); «Чтобы закрыть, щелкните здесь»);
Когда курсор устанавливается на этой кнопке, в окне ToolTip появляется текст «Чтобы закрыть, щелкните здесь». Щелчок на кнопке — и программа будет закрыта.
Программу ToolTest и файл Mfcext.dll можно загрузить вместе с исходными текстами из службы PC Magazine Online (www.pcmag.com). Выберите пункт Downloads в меню, расположенном с левой стороны базовой страницы, затем пункт PC Tech Archives, а после этого пункт V16n15.zip. Этот файл вы можете найти также в оперативной информационной службе CompuServe в форуме Utilities/Tips (GO ZNT:TIPS). Наши файлы входят в этот архив.
В процессе их разархивирования (Mfcext.zip и Tooltest.zip) установите переключатель в PKUNZIP в положение -d, чтобы развернуть и каталоги, хранящиеся в ZIP-файлах. Прежде чем вы запустите ToolTest на выполнение, скопируйте файл Mfcext.dll в системный каталог Windows или в тот каталог, в котором находится исполнимый модуль Tooltest.exe. В противном случае Windows не сможет найти файл Mfcext.dll, когда начнется выполнение программы ToolTest. Если вы решите заняться подготовкой своей собственной версии модуля Tooltest.exe, внесите изменения в имя маршрута в поле «Object/library modules» диалогового окна Project Settings, чтобы информировать Visual C++ о том, в каком каталоге вашего ПК находится файл Mfcext.lib.
Заключение
Разработка DLL-модулей расширения MFC — задача простая, если, конечно, вы знаете, как это делается. Когда вы делитесь подобными расширениями с другими разработчиками, не забудьте кроме самих DLL-файлов передать им и заголовочные файлы, описывающие классы этих DLL-модулей, и соответствующие LIB-файлы. Тогда у ваших коллег будет все необходимое, чтобы воспользоваться подготовленными вами расширениями.
Джефф Просис — внештатный редактор журнала PC Magazine и автор книги Programming Windows 95 with MFC.
Источник: www.helloworld.ru
Как написать и отладить dll в C++ Builder
Ну, начнём с того, что Dll — это аббревиатура от слов Dynamic Link Library, что переводится как «динамически подключаемая библиотека». По-сути, это модуль, содержащий какой-то полезный код. Чаще всего этот полезный код содержит какие-то программные функции, но может содержать и что-то другое, например, изображения или списки локализации и тому подобное.
Главное отличие модуля Dll от обычной программы заключается в том, что сам по себе такой модуль (и содержащиеся в нём функции) не может быть исполнен как отдельная программа, зато к этому модулю могут подключаться другие программы и исполнять содержащиеся в нём куски кода. Это, например, позволяет не разрабатывать каждый раз заново функции для работы с какими-то железками, базами данных, другими программами, а описать их один раз в отдельном модуле Dll и далее использовать эту Dll во всех программах, в которых они нужны.
Программы могут связываться с модулями Dll двумя способами, называемыми: статическим связыванием и динамическим связыванием.
При статическом связывании Dll-ка будет загружаться в память вместе с запускаемым приложением и приложение без этой Dll-ки работать не будет. Далее мы не будем рассматривать такой тип связывания, поскольку гораздо интереснее связывание динамическое. В этом случае приложение загружает Dll-ку только в том случае, если ему нужно получить доступ к какой-то хранящейся в Dll-ке функции, после чего Dll-ка снова может быть выгружена. Более того, если в процессе работы приложению ни разу не потребовались хранящиеся в Dll-ке функции, то Dll-ка может вообще не загружаться в память.
Как написать Dll?
Для создания Dll в C++ Builder есть специальный Wizard. Найти его можно по следующему пути: File->New->Other… и далее на вкладке New нужно выбрать пункт DLL Wizard.
Далее, в визарде нужно поставить галочки C++, Use VCL (будем писать на C++ и пользоваться компонентами библиотеки VCL) и нажать OK:
В итоге получаем файл cpp с заготовкой кода (пара подключенных библиотек, функция, обозначающая точку входа) и длиннющим комментарием. Комментарий читаем внимательно и стираем нафиг, а проект сохраняем в отдельной папке.
Далее идём по тому же пути File-&gb;New-unit1.h» //————————————————————————— #include #include #pragma hdrstop //————————————————————————— #pragma argsused int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved) { return 1; } //————————————————————————— // Ниже пишем свои функции
Для того, чтобы функцию можно было экспортировать, её описание в h-файле должно быть вот в таком виде:
тип имя типы входных возвращаемой функции переменных переменной в Dll (через запятую) | | | extern «C» OD_Type __declspec(dllexport) DllFunc1 (ID_Types);
Если на выходе функции нужно получить не одну, а несколько переменных, — можно использовать массивы или структуры, только нужно учесть, что вместо AnsiString лучше пользоваться Char-массивами (об этом как раз был комментарий).
Осталось только скомпилировать проект и на выходе мы получим файл с расширением Dll.
Как подключить и использовать Dll в своей программе?
Для того, чтобы использовать функции из Dll в своей программе, — нужно сделать следующее:
Сначала в заголовочном файле программы (*.h) нужно правильно описать типы импортируемых из Dll функций (чтобы описание в программе совпадало с описанием в самой Dll). Проще всего подглядеть эту информацию в самом хидере dll-ки (*.h), хотя конечно, неважно откуда у вас эта информация, главное чтобы она была. Это нужно для того, чтобы программа правильно понимала какие данные (типы, порядок, количество переменных) она будет в Dll передавать и какие принимать. Когда нужная информация у вас есть, описать в программе типы импортируемых функций можно следующим образом:
typedef OD_Type (__import Proto1(ID_Types)); // первый прототип внешней функции Proto1 *Func1; // объявляем импортируемую функцию описанного выше типа typedef OD_Type (__import Proto2(ID_Types)); // второй прототип внешней функции Proto2 *Func2; // а разных функций второго типа будем импортировать Proto2 *Func3; // целых 3 штуки (так тоже можно, если функции имеют Proto2 *Func4; // одинаковый набор типов входных и выходных параметров)
typedef OD_Type (__import Proto1(ID_Types)); // первый прототип внешней функции Proto1 *Func1; // объявляем импортируемую функцию описанного выше типа typedef OD_Type (__import Proto2(ID_Types)); // второй прототип внешней функции Proto2 *Func2; // а разных функций второго типа будем импортировать Proto2 *Func3; // целых 3 штуки (так тоже можно, если функции имеют Proto2 *Func4; // одинаковый набор типов входных и выходных параметров)
Далее, в программе (*.cpp) нужно загрузить Dll из которой мы хотим описанные функции импортировать:
HINSTANCE DllHinst = LoadLibrary(«[путь/]имя_файла.dll»);
HINSTANCE DllHinst = LoadLibrary(«[путь/]имя_файла.dll»);
Если Dll-ка успешно загрузилась, то LoadLibrary вернёт указатель на на неё (идентификатор, по которому к ней можно обращаться), в противном случае функция вернёт NULL.
Теперь, когда у нас есть указатель на загруженную Dll, осталось найти внутри Dll-ки нужную нам функцию. Это можно сделать так:
указатель на имя функции, которую мы ищем загруженную Dll (с подчеркиванием впереди) | | Func1 = (Proto1 *)GetProcAddress(DllHinst,»_DllFunc1″);
Если запрошенная нами функция обнаружится в указанной Dll-ке, то GetProcAddress вернёт нам её адрес, в противном случае будет возвращён NULL.
Вот и всё, теперь мы можем пользоваться функцией из загруженной Dll в своей программе, вызывая её точно также, как буд-то она у нас написана в секции *.cpp.
После того, как мы попользовались функциями из Dll-ки и она нам больше не нужна, можно выгрузить её командой FreeLibrary:
FreeLibrary(DllHinst);
Как отладить Dll?
Последнее, что нам понадобится, — это отладка. Как мы уже говорили, напрямую запустить на исполнение Dll и хранящиеся в ней функции невозможно, её сначала должен кто-то толкнуть. Поэтому для отладки нам понадобится написать какое-то приложение, которое будет использовать отлаживаемый код.
Когда такое приложение есть, — копируем его в папку проекта с Dll-кой, выбираем в меню Run пункт Parameters… , в открывшемся окне щёлкаем на кнопку Browse… , напротив строки Host Application, в открывшемся диалоговом окне выбираем наше приложение (после этого в строке Host Application будет прописан путь к выбранному приложению) и жмём OK.
Всё, теперь можно также как и в обычном приложении расставлять в нашей Dll-ке точки останова и запускать отладку кнопкой F9. При этом сначала загрузится выбранное нами выше приложение, а когда оно дойдёт до использования хранящихся в Dll-ке функций — тут-то и сработают наши точки останова, мы попадём в отладчик и сможем отлаживать написанный в Dll-ке код.
В качестве примера можно посмотреть dll-ку для шлюза, вот из этой статьи (она там с исходниками выложена).
Понравилась статья? Поделись с друзьями!
Источник: radiohlam.ru
DynLib: библиотека для создания и работы с DLL
Библиотека DynLib предоставляет удобные средства для разработчиков, использующих межмодульное взаимодействие (EXEDLL, DLLDLL) в своих проектах, и значительно сокращает время и количество кода.
DynLib стала неотъемлемым инструментом разработки. Под катом делимся результатами.
Недостатки традиционного подхода к реализации DLL
- отсутствие возможности использовать пространства имен
- большое количество служебного кода, необходимого:
- при реализации динамической загрузки библиотек;
- при реализации межмодульного взаимодействия через классы, за счет использования декскрипторов (или иных неявных структур) и классов-оберток;
- при реализации механизмов возвращения ошибки, в случае, когда экспортируемые функции могут генерировать исключения.
Примеры использования DynLib
1. Использование обычной DLL
Задача. Динамически подключить и использовать библиотеку test_lib.dll, реализующую простые математические операции, с интерфейсом, представленным в заголовочном файле:
//========== test_lib.h ========== #pragma once extern «C» __declspec(dllexport) int __stdcall sum(int x, int y); extern «C» __declspec(dllexport) int __stdcall mul(int x, int y); extern «C» __declspec(dllexport) double __stdcall epsilon();
Решение. Необходимо написать следующий заголовочный файл и подключить его к проекту.
//========== test_lib.hpp ========== #pragma once #include DL_NS_BLOCK(( test ) ( DL_C_LIBRARY( lib ) ( ( int, __stdcall, (sum), (int,x)(int,y) ) ( int, __stdcall, (mul), (int,x)(int,y) ) ( double,__stdcall, (epsilon), () ) ) ))
Препроцессор сгенерирует класс test::lib, выполняющий динамическую загрузку DLL и содержащий перечисленные функции sum, mul и epsilon. Для подключения DLL к приложению необходимо включить представленный заголовочный файл test_lib.hpp в исходный код. Далее следует создать объект класса test::lib. Доступ к экспортируемых функциям DLL возможен через ‘.’ или ‘->’.
//========== exe.cpp ========== #include «test_lib.hpp» int main() < test::lib lib( «path_to/test_lib.dll» ); int s = lib->sum( 5, 20 ); int m = lib.mul( 5, 10 ); double eps = lib.epsilon(); return 0; >
2. Создание библиотеки calculator.dll
Задача. Написать библиотеку calculator.dll, которая должна вычислять сумму, произведение двух чисел и значение квадратного корня. Динамически загрузить библиотеку и вызвать каждую функцию.
Решение
//========== calculator.hpp ========== #include DL_NS_BLOCK(( team ) ( DL_LIBRARY( calculator ) ( ( double, sum, (double,x)(double,y) ) ( double, mul, (double,x)(double,y) ) ( double, sqrt, (double,x) ) ) )) //========== calculator_dll.cpp ========== #include «calculator.hpp» struct calculator < static double sum( double x, double y ) < return x + y; >static double mul( double x, double y ) < return x * y; >static double sqrt( double x ) < return std::sqrt(x); >>; DL_EXPORT( team::calculator, calculator )
Использование DLL
//========== application.cpp ========== #include #include «calculator.hpp» int main() < using namespace std; team::calculator calc( «calculator.dll» ); cout //========== calculator.hpp ========== #include DL_NS_BLOCK(( team ) ( DL_LIBRARY( calculator ) ( ( double, sqrt, (double,x) ) ) )) //========== calculator_dll.cpp ========== #include «calculator.hpp» struct calculator < static double sqrt( double x ) < if ( x < 0 ) throw std::invalid_argument( «значение аргумента меньше 0» ); return std::sqrt( x ); >>; DL_EXPORT( team::calculator, calculator )
//========== application.cpp ========== #include #include #include «calculator.hpp» int main() < using namespace std; locale::global( locale(«», locale::ctype) ); try < team::calculator calc( «calculator.dll» ); cout catch (dl::method_error const cerr return 0; > //========== результат выполнения ==========
sqrt1 = 5 what: exception ‘class std::invalid_argument’ in method ‘sqrt’ of class ‘::team::calculator’ with message ‘значение аргумента меньше 0’
4. Реализация библиотеки shapes.dll. Использование интерфейсов.
Задача. Создать библиотеку shapes.dll по работе с геометрическими фигурами (квадрат, прямоугольник, круг). Все фигуры должны поддерживать общий интерфейс, через который можно узнать координаты центра фигуры.
Решение
//========== shapes.hpp ========== #include DL_NS_BLOCK(( shapes ) ( DL_INTERFACE(figure) ( ( char const*, name, ) ( double, center_x, ) ( double, center_y, ) ( void, center_xy, (double,y) ) ) )) DL_NS_BLOCK(( shapes ) ( DL_LIBRARY(lib) ( ( shapes::figure, create_rectangle, (double,left)(double,top)(double,width)(double,height) ) ( shapes::figure, create_square, (double,left)(double,top)(double,size) ) ( shapes::figure, create_circle, (double,center_x)(double,center_y)(double,radius) ) ) )) //========== shapes_lib.cpp ========== #include «shapes.hpp» class rectangle < public: rectangle(double l, double t, double w, double h) : l_(l), t_(t), w_(w), h_(h) < if (w < 0) throw std::invalid_argument( «неверно задана ширина прямоугольника» ); if (h < 0) throw std::invalid_argument( «неверно задана высота прямоугольника» ); >char const* name() < return «rectangle»; >double center_x() < return l_ + w_ / 2.; >double center_y() < return t_ + h_ / 2.; >void center_xy(double y) < x = center_x(); y = center_y(); >private: double l_, t_, w_, h_; >; class square < public: square(double l, double t, double s) : l_(l), t_(t), s_(s) < if (s < 0) throw std::invalid_argument( «неверно задана длина стороны квадрата» ); >char const* name() < return «square»; >double center_x() < return l_ + s_ / 2.; >double center_y() < return t_ + s_ / 2.; >void center_xy(double y) < x = center_x(); y = center_y(); >private: double l_, t_, s_; >; class circle < public: circle(double x, double y, double r) : x_(x), y_(y), r_(r) < if (r < 0) throw std::invalid_argument( «неверно задан радиус круга» ); >char const* name() < return «circle»; >double center_x() < return x_; >double center_y() < return y_; >void center_xy(double y) < x = x_; y = y_; >private: double x_, y_, r_; >; struct shapes_lib < static shapes::figure create_rectangle( double l, double t, double w, double h ) < return dl::shared( l, t, w, h ); > static shapes::figure create_square( double l, double t, double s ) < return dl::shared( l, t, s ); > static shapes::figure create_circle( double x, double y, double r ) < return dl::shared( x, y, r ); > >; DL_EXPORT( shapes::lib, shapes_lib ) //========== application.cpp ========== #include #include «shapes_lib.hpp» void print_center( shapes::figure shape ) < std::cout int main()
Как подключить библиотеку
Библиотека поставляется в виде заголовочных файлов. Никаких .lib и .dll не требуется. Для подключения требуется добавить следующую директиву:
#include
Элементы библиотеки
Многие классы и макросы библиотеки DynLib могут использоваться самостоятельно и отдельно друг от друга.
DL_BLOCK
Служит контейнером для всех остальных макросов.
DL_BLOCK ( // declarations )
DL_NS_BLOCK
Служит контейнером для всех остальных макросов. Создает пространства имен для класса.
DL_NS_BLOCK( (ns0, ns1, ns2 … )/*пространства имен, до 10*/ ( // declarations ))
Макросы, которые описаны ниже кроме DL_EXPORT, должны быть помещены в DL_BLOCK или DL_NS_BLOCK
DL_C_LIBRARY
Назначение макроса — предоставить пользователю готовый класс, реализующий динамическую загрузку DLL и автоматический импорт функций. Макрос представлен как:
DL_C_LIBRARY(lib_class) ( /*functions*/ ( ret_type, call, (name, import_name), arguments ) )
- lib_class — имя класса, реализацию которого генерирует библиотека DynLib;
- functions — перечисление функций, экспортируемых DLL. задается через список следующего формата
(ret_type, call, (name, import_name), arguments)
- ret_type — тип возвращаемого функцией значения;
- call — формат вызова, например: __sdtcall, __cdecl и т.п.;
- name — имя функции (для пользователя);
- import_name — имя функции, заданной в таблице экспорта DLL, включая декорацию (если она есть). Если name и import_name совпадают, то import_name можно не указывать.
- arguments — список (тип аргумента, имя аргумента, = значение по умолчанию), задающий входные аргументы. Имя аргумента и значение по умолчанию можно не указывать.;
DL_RECORD
Макрос DL_RECORD генерирует упакованную структуру данных для использования в межмодульном взаимодействии. Дополнительно создается конструктор со всеми перечисленными в макросе аргументами.
DL_RECORD( record_name ) ( /*fields*/ (type, name, =default_value) )
//========== some_exe.cpp ========== #include DL_BLOCK ( DL_RECORD( data ) ( ( int, x ) ( int, y, = 100 /*значение по умолчанию*/ ) ( int, z, = 200 /*значение по умолчанию*/ ) ) ) int main() < data v( 20 ); //инициализация x = 20, y = 100, z = 200 v.x = 10; v.y = v.x; v.z = 50; v = data( 5, 20, 30 ); data a( 1, 2, 3 ); return 0; >
DL_LIBRARY
- выступает в роли описания (документирования) интерфейса между EXE(DLL) и DLL;
- содержит необходимые структуры для автоматического экспорта функций библиотеки для разработчика;
- реализует класс, обеспечивающий загрузку DLL с заданным интерфейсом и предоставляющий доступ к экспортируемым функциям со стороны пользователя;
- обеспечивает корректное использование C++ исключений:
— автоматический перехват C++ исключений на стороне DLL; — возврат значения через границы DLL, сигнализирующего о наличии исключения; — генерация нового исключения в случае, если на стороне DLL исключение было перехвачено (с восстановлением описания и информации о типе исключения).
DL_LIBRARY( name ) ( /*functions*/ ( ret_type, name, arguments ) )
Классы, генерируемые макросом DL_LIBRARY, нельзя передавать через границы DLL.
Для демонстрации работы макроса представим следующий заголовочный файл:
//========== test1_lib.hpp ========== #pragma once #include DL_NS_BLOCK(( team, test ) ( DL_LIBRARY( lib ) ( ( int, sum, (int,x)(int,y) ) ( void, mul, (int,x)(int,y)(int team::test::lib lib( «test1.dll» ); int s = lib.sum( 5, 10 ); lib.mul( 5, 5, s ); double eps = lib->epsilon(); return 0; >
DL_EXPORT
- lib_class — полное имя класса, описывающего интерфейс взаимодействия (то имя класса, что использовалось в DL_LIBRARY);
- lib_impl_class — полное имя класса класса, РЕАЛИЗУЮЩЕГО функции, указанные в интерфейсе взаимодействия.
- Создать класс (структуру);
- Определить каждую функцию из интерфейса как статическую. Функции должны находиться в области видимости public:;
- Произвести экспорт функций, написав конструкцию DL_EXPORT(lib, impl).
//========== test1_dll.cpp ========== #include «test1_lib.hpp» struct lib_impl < static int sum( int x, int y ) < return x + y; >static void mul( int x, int y, int result = x + y; >static double epsilon() < return 2.0e-8; >>; DL_EXPORT( team::test::lib, lib_impl )
DL_INTERFACE
Макрос позволяет описать интерфейс класса и предоставить пользователю класс-обертку для работы с ним. Реализация класса-обертки обеспечивает корректное использование C++ исключений:
— автоматический перехват C++ исключений на стороне DLL; — возврат значения через границы DLL, сигнализирующего о наличии исключения; — генерация нового исключения в случае, если на стороне DLL исключение было перехвачено (с восстановлением описания и информации о типе исключения).
Класс-обертка, генерируемая данным макросом, имеет разделяемое владение объектом, реализующего данный интерфейс. Разделяемое владение обеспечивается механизмом подсчета ссылок, т.е. когда происходит копирование объектов класса-обертки, вызывается внутренняя функция для увеличения счетчика ссылок, при уничтожении — внутренняя функция по уменьшению счетчика ссылок. При достижении счетчиком значения 0 происходит автоматическое удаление объекта. Доступ к методам интерфейса осуществляется через ‘.’ или ‘->’.
Библиотека DynLib гарантирует безопасное использование классов-интерфейсов на границе EXE(DLL)DLL
DL_INTERFACE( interface_class ) ( /*methods*/ ( ret_type, name, arguments ) )
- interface_class — имя класса, реализацию которого генерирует библиотека DynLib;
- methods — перечисление функций, описывающих интерфейс класса,
DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) ))
Использование:
example::processor p; p =… // см. разделы dl::shared и dl::ref int tcount = p->threads_count(); p.process(some_buf, some_buf_size);
dl::shared
- динамическое создание объекта класса T с аргументами, переданными в конструкторе;
- добавление счетчика ссылок и обеспечение разделяемого владения (подобно boost(std)::shared_ptr);
- неявное приведение к объекту класса, генерируемого макросом DL_INTERFACE.
class my_processor < public: my_processor( char const* name = «default name» ); int threads_count(); void process(char const* buf, std::size_t size); private: // состояние класса >; DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) ))
Примеры использования dl::shared представлены ниже:
dl::shared p1( «some processor name» ); // объект класса my_processor создается динамически dl::shared p2; // объект класса my_processor создается динамически c конструктором по умолчанию dl::shared p3( p1 ); // p3 и p1 ссылаются на один и тот же объект, счетчик ссылок = 2 dl::shared p4( dl::null_ptr ); // p4 ни на что не ссылается p3.swap( p4 ); // p4 ссылается на то же, что и p1, p3 — ни на что не ссылается p4 = dl::null_ptr; // p4 ни на что не ссылается p2 = p1; // p2 ссылается на объект p1 p2 = p1.clone(); // создается копия объекта my_processor // в классе my_processor должен быть доступен конструктор копирования p2->threads_count(); p2->process( /*args*/ ); // использование объекта my_processor example::processor pi = p2; // приведение объекта my_processor к интерфейсу example::processor // pi также хранит ссылку на объект, и изменяет счетчик ссылок при создании, копировании и уничтожении. pi->threads_count(); pi->process(/*args*/); // использование объекта my_processor через интерфейс pi.
dl::ref
Функция библиотеки, позволяющая привести любой объект к объекту класса-интерфейса, объявленному через DL_INTERFACE, с идентичным набором методов. Обычно такое поведение необходимо, когда имеется функция, принимающая в качестве аргумента класс-интерфейс, а ему следует передать объект, размещенный в стеке.
Использовать функцию dl::ref нужно с осторожностью, поскольку объекты классов-интерфейсов, в этом случае не будут владеть переданными объектами, а управление временем жизни объекта и его использованием через классы-интерфейсы ложится на пользователя. Копирование объектов классов-интерфейсов, ссылающих на объекты, переданные через dl::ref, разрешено и вполне корректно (поскольку счетчика ссылок нет, то и изменять нечего — объекты классы-интерфейсов знают как здесь корректно работать).
class my_processor < public: my_processor( char const* name = «default name» ); int threads_count(); void process( char const* buf, std::size_t size ); private: // состояние класса >; DL_NS_BLOCK(( example ) ( DL_INTERFACE( processor ) ( ( int, threads_count, () ) ( void, process, (char const*,buf)(std::size_t,size) ) ) )) void some_dll_func( example::processor p ) < // использование p >int main() < my_processor processor( «abc» ); some_dll_func( dl::ref(processor) ); // В качестве интерфейса выступает обычный объект класса, а не dl::objectreturn 0; >
Поддерживаемые компиляторы
- Microsoft Visual C++ 2008;
- Microsoft Visual C++ 2010;
- MinGW GCC 4.5.0 и выше.
- CodeGear С++ Builder XE (не гарантируется работа при определенных настройках компилятора)
Источник: habr.com