Библиотеки динамической компоновки (DLL). Создание проекта библиотеки динамической компоновки (DLL)

С самого рождения (или чуть позже) операционная система Windows использовала библиотеки динамической компоновки DLL (Dynamic Link Library), в которых содержались реализации наиболее часто применяемых функций. Наследники Windows - NT и Windows 95, а также OS/2 - тоже зависят от библиотек DLL в плане обеспечения значительной части их функциональных возможностей.

Рассмотрим ряд аспектов создания и использования библиотек DLL:

  • как статически подключать библиотеки DLL;
  • как динамически загружать библиотеки DLL;
  • как создавать библиотеки DLL;
  • как создавать расширения МFC библиотек DLL.

Использование DLL

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

Вообще говоря, DLL - это просто наборы функций, собранные в библиотеки. Однако, в отличие от своих статических родственников (файлов. lib), библиотеки DLL не присоединены непосредственно к выполняемым файлам с помощью редактора связей. В выполняемый файл занесена только информация об их местонахождении. В момент выполнения программы загружается вся библиотека целиком. Благодаря этому разные процессы могут пользоваться совместно одними и теми же библиотеками, находящимися в памяти. Такой подход позволяет сократить объем памяти, необходимый для нескольких приложений, использующих много общих библиотек, а также контролировать размеры ЕХЕ-файлов.

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

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

Библиотеки импортирования

При статическом подключении DLL имя.lib-файла определяется среди прочих параметров редактора связей в командной строке или на вкладке "Link" диалогового окна "Project Settings" среды Developer Studio. Однако.lib-файл, используемый при неявном подключении DLL, - это не обычная статическая библиотека. Такие.lib-файлы называются библиотеками импортирования (import libraries). В них содержится не сам код библиотеки, а только ссылки на все функции, экспортируемые из файла DLL, в котором все и хранится. В результате библиотеки импортирования, как правило, имеют меньший размер, чем DLL-файлы. К способам их создания вернемся позднее. А сейчас рассмотрим другие вопросы, касающиеся неявного подключения динамических библиотек.

Согласование интерфейсов

При использовании собственных библиотек или библиотек независимых разработчиков придется обратить внимание на согласование вызова функции с ее прототипом.

Если бы мир был совершенен, то программистам не пришлось бы беспокоиться о согласовании интерфейсов функций при подключении библиотек - все они были бы одинаковыми. Однако мир далек от совершенства, и многие большие программы написаны с помощью различных библиотек без C++.

По умолчанию в Visual C++ интерфейсы функций согласуются по правилам C++. Это значит, что параметры заносятся в стек справа налево, вызывающая программа отвечает за их удаление из стека при выходе из функции и расширении ее имени. Расширение имен (name mangling) позволяет редактору связей различать перегруженные функции, т.е. функции с одинаковыми именами, но разными списками аргументов. Однако в старой библиотеке С функции с расширенными именами отсутствуют.

Хотя все остальные правила вызова функции в С идентичны правилам вызова функции в C++, в библиотеках С имена функций не расширяются. К ним только добавляется впереди символ подчеркивания (_).

Если необходимо подключить библиотеку на С к приложению на C++, все функции из этой библиотеки придется объявить как внешние в формате С:

Extern "С" int MyOldCFunction(int myParam);

Объявления функций библиотеки обычно помещаются в файле заголовка этой библиотеки, хотя заголовки большинства библиотек С не рассчитаны на применение в проектах на C++. В этом случае необходимо создать копию файла заголовка и включить в нее модификатор extern "C" к объявлению всех используемых функций библиотеки. Модификатор extern "C" можно применить и к целому блоку, к которому с помощью директивы #tinclude подключен файл старого заголовка С. Таким образом, вместо модификации каждой функции в отдельности можно обойтись всего тремя строками:

Extern "С" { #include "MyCLib.h" }

В программах для старых версий Windows использовались также соглашения о вызове функций языка PASCAL для функций Windows API. В новых программах следует использовать модификатор winapi, преобразуемый в _stdcall. Хотя это и не стандартный интерфейс функций С или C++, но именно он используется для обращений к функциям Windows API. Однако обычно все это уже учтено в стандартных заголовках Windows.

Загрузка неявно подключаемой DLL

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

  • Каталог, в котором находится ЕХЕ-файл.
  • Текущий каталог процесса.
  • Системный каталог Windows.

Если библиотека DLL не обнаружена, приложение выводит диалоговое окно с сообщением о ее отсутствии и путях, по которым осуществлялся поиск. Затем процесс отключается.

Если нужная библиотека найдена, она помещается в оперативную память процесса, где и остается до его окончания. Теперь приложение может обращаться к функциям, содержащимся в DLL.

Динамическая загрузка и выгрузка DLL

Вместо того, чтобы Windows выполняла динамическое связывание с DLL при первой загрузке приложения в оперативную память, можно связать программу с модулем библиотеки во время выполнения программы (при таком способе в процессе создания приложения не нужно использовать библиотеку импорта). В частности, можно определить, какая из библиотек DLL доступна пользователю, или разрешить пользователю выбрать, какая из библиотек будет загружаться. Таким образом можно использовать разные DLL, в которых реализованы одни и те же функции, выполняющие различные действия. Например, приложение, предназначенное для независимой передачи данных, сможет в ходе выполнения принять решение, загружать ли DLL для протокола TCP/IP или для другого протокола.

Загрузка обычной DLL

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

HINSTANCE hMyDll; :: if((hMyDll=:: LoadLibrary("MyDLL"))==NULL) { /* не удалось загрузить DLL */ } else { /* приложение имеет право пользоваться функциями DLL через hMyDll */ }

Стандартным расширением файла библиотеки Windows считает.dll, если не указать другое расширение. Если в имени файла указан и путь, то только он будет использоваться для поиска файла. В противном случае Windows будет искать файл по той же схеме, что и в случае неявно подключенных DLL, начиная с каталога, из которого загружается exe-файл, и продолжая в соответствии со значением PATH.

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

Если файл обнаружен и библиотека успешно загрузилась, функция ::LoadLibrary возвращает ее дескриптор, который используется для доступа к функциям библиотеки.

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

// тип PFN_MyFunction будет объявлять указатель на функцию, // принимающую указатель на символьный буфер и выдающую значение типа int typedef int (WINAPI *PFN_MyFunction)(char *); :: PFN_MyFunction pfnMyFunction;

Затем следует получить дескриптор библиотеки, при помощи которого и определить адреса функций, например адрес функции с именем MyFunction:

HMyDll=::LoadLibrary("MyDLL"); pfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll,"MyFunction"); :: int iCode=(*pfnMyFunction)("Hello");

Адрес функции определяется при помощи функции ::GetProcAddress , ей следует передать имя библиотеки и имя функции. Последнее должно передаваться в том виде, в котором экспортируется из DLL.

Можно также сослаться на функцию по порядковому номеру, по которому она экспортируется (при этом для создания библиотеки должен использоваться def-файл, об этом будет рассказано далее):

PfnMyFunction=(PFN_MyFunction)::GetProcAddress(hMyDll, MAKEINTRESOURCE(1));

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

::FreeLibrary(hMyDll);

Загрузка MFC-расширений динамических библиотек

При загрузке MFC-расширений для DLL (подробно о которых рассказывается далее) вместо функций LoadLibrary и FreeLibrary используются функции AfxLoadLibrary и AfxFreeLibrary . Последние почти идентичны функциям Win32 API. Они лишь гарантируют дополнительно, что структуры MFC, инициализированные расширением DLL, не были запорчены другими потоками.

Ресурсы DLL

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

Замечание. С помощью функции LoadLibrary можно также загружать в память исполняемые файлы (не запускать их на выполнение!). Дескриптор выполняемого модуля может затем использоваться при обращении к функциям FindResource и LoadResource для поиска и загрузки ресурсов приложения. Выгружают модули из памяти также при помощи функции FreeLibrary .

Пример обычной DLL и способов загрузки

Приведем исходный код динамически подключаемой библиотеки, которая называется MyDLL и содержит одну функцию MyFunction, которая просто выводит сообщение.

Сначала в заголовочном файле определяется макроконтстанта EXPORT . Использование этого ключевого слова при определении некоторой функции динамически подключаемой библиотеке позволяет сообщить компоновщику, что эта функция доступна для использования другими программами, в результате чего он заносит ее в библиотеку импорта. Кроме этого, такая функция, точно так же, как и оконная процедура, должна определяться с помощью константы CALLBACK :

MyDLL.h #define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str);

Файл библиотеки также несколько отличается от обычных файлов на языке C для Windows. В нем вместо функции WinMain имеется функция DllMain . Эта функция используется для выполнения инициализации, о чем будет рассказано позже. Для того, чтобы библиотека осталась после ее загрузки в памяти, и можно было вызывать ее функции, необходимо, чтобы ее возвращаемым значением было TRUE:

MyDLL.c #include #include "MyDLL.h" int WINAPI DllMain(HINSTANCE hInstance, DWORD fdReason, PVOID pvReserved) { return TRUE; } EXPORT int CALLBACK MyFunction(char *str) { MessageBox(NULL,str,"Function from DLL",MB_OK); return 1; }

После трансляции и компоновки этих файлов появляется два файла - MyDLL.dll (сама динамически подключаемая библиотека) и MyDLL.lib (ее библиотека импорта).

Пример неявного подключения DLL приложением

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

#include #include "MyDLL.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { int iCode=MyFunction("Hello"); return 0; }

Эта программа выглядит как обычная программ для Windows, чем она в сущности и является. Тем не менее, следует обратить внимание, что в исходный ее текст помимо вызова функции MyFunction из DLL-библиотеки включен и заголовочный файл этой библиотеки MyDLL.h. Также необходимо на этапе компоновки приложения подключить к нему библиотеку импорта MyDLL.lib (процесс неявного подключения DLL к исполняемому модулю).

Чрезвычайно важно понимать, что сам код функции MyFunction не включается в файл MyApp.exe. Вместо этого там просто имеется ссылка на файл MyDLL.dll и ссылка на функцию MyFunction, которая находится в этом файле. Файл MyApp.exe требует запуска файла MyDLL.dll.

Заголовочный файл MyDLL.h включен в файл с исходным текстом программы MyApp.c точно так же, как туда включен файл windows.h. Включение библиотеки импорта MyDLL.lib для компоновки аналогично включению туда всех библиотек импорта Windows. Когда программа MyApp.exe работает, она подключается к библиотеке MyDLL.dll точно так же, как ко всем стандартным динамически подключаемым библиотекам Windows.

Пример динамической загрузки DLL приложением

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

#include typedef int (WINAPI *PFN_MyFunction)(char *); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HINSTANCE hMyDll; if((hMyDll=LoadLibrary("MyDLL"))==NULL) return 1; PFN_MyFunction pfnMyFunction; pfnMyFunction=(PFN_MyFunction)GetProcAddress(hMyDll,"MyFunction"); int iCode=(*pfnMyFunction)("Hello"); FreeLibrary(hMyDll); return 0; }

Создание DLL

Теперь, познакомившись с принципами работы библиотек DLL в приложениях, рассмотрим способы их создания. При разработке приложении функции, к которым обращается несколько процессов, желательно размещать в DLL. Это позволяет более рационально использовать память в Windows.

Проще всего создать новый проект DLL с помощью мастера AppWizard, который автоматически выполняет многие операции. Для простых DLL, таких как рассмотренные в этой главе, необходимо выбрать тип проекта Win32 Dynamic-Link Library. Новому проекту будут присвоены все необходимые параметры для создания библиотеки DLL. Файлы исходных текстов придется добавлять к проекту вручную.

Если же планируется в полной мере использовать функциональные возможности MFC, такие как документы и представления, или намерены создать сервер автоматизации OLE, лучше выбрать тип проекта MFC AppWizard (dll). В этом случае, помимо присвоения проекту параметров для подключения динамических библиотек, мастер проделает некоторую дополнительную работу. В проект будут добавлены необходимые ссылки на библиотеки MFC и файлы исходных текстов, содержащие описание и реализацию в библиотеке DLL объекта класса приложения, производного от CWinApp .

Иногда удобно сначала создать проект типа MFC AppWizard (dll) в качестве тестового приложения, а затем - библиотеку DLL в виде его составной части. В результате DLL в случае необходимости будет создаваться автоматически.

Функция DllMain

Большинство библиотек DLL - просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначенных для экспортирования, в каждой библиотеке DLL есть функция DllMain . Эта функция предназначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP , применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMain может выглядеть, например, так:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) { BOOL bAllWentWell=TRUE; switch (dwReason) { case DLL_PROCESS_ATTACH: // Инициализация процесса. break; case DLL_THREAD_ATTACH: // Инициализация потока. break; case DLL_THREAD_DETACH: // Очистка структур потока. break; case DLL_PROCESS_DETACH: // Очистка структур процесса. break; } if(bAllWentWell) return TRUE; else return FALSE; }

Функция DllMain вызывается в нескольких случаях. Причина ее вызова определяется параметром dwReason, который может принимать одно из следующих значений.

При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового потока DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

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

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

В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандартную версию, которая просто возвращает TRUE.

Экспортирование функций из DLL

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

Метод __declspec (dllexport)

Можно экспортировать функцию из DLL, поставив в начале ее описания модификатор __declspec (dllexport) . Кроме того, в состав MFC входит несколько макросов, определяющих __declspec (dllexport), в том числе AFX_CLASS_EXPORT, AFX_DATA_EXPORT и AFX_API_EXPORT.

Метод __declspec применяется не так часто, как второй метод, работающий с файлами определения модуля (.def), и позволяет лучше управлять процессом экспортирования.

Файлы определения модуля

Синтаксис файлов с расширением.def в Visual C++ достаточно прямолинеен, главным образом потому, что сложные параметры, использовавшиеся в ранних версиях Windows, в Win32 более не применяются. Как станет ясно из следующего простого примера, .def-файл содержит имя и описание библиотеки, а также список экспортируемых функций:

MyDLL.def LIBRARY "MyDLL" DESCRIPTION "MyDLL - пример DLL-библиотеки" EXPORTS MyFunction @1

В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использоваться при обращении к GetProcAddress (). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако способ, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно.

В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспортирования DLL:

MyFunction @1 NONAME

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

При использовании вышеприведенного def-файл описания экспортируемых функций DLL-библиотеки может быть,например, не таким:

#define EXPORT extern "C" __declspec (dllexport) EXPORT int CALLBACK MyFunction(char *str); a таким: extern "C" int CALLBACK MyFunction(char *str);

Экспортирование классов

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

Если взглянуть на реализованный в классе файл распределения памяти, в нем можно заметить некоторые весьма необычные функции. Оказывается, здесь есть неявные конструкторы и деструкторы, функции, объявленные в макросах MFC, в частности _DECLARE_MESSAGE_MAP, а также функции, которые написанные программистом.

Хотя можно экспортировать каждую из этих функций в отдельности, есть более простой способ. Если в объявлении класса воспользоваться макромодификатором AFX_CLASS_EXPORT, компилятор сам позаботится об экспортировании необходимых функций, позволяющих приложению использовать класс, содержащийся в DLL.

Память DLL

В отличие от статических библиотек, которые, по существу, становятся частью кода приложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного пространства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало возможность совместного использования их различными задачами.

В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Каждому процессу предоставляется отдельная копия "глобальной" памяти DLL, которая реинициализируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая библиотека не может использоваться совместно, в общей памяти, как это было в Winl6.

И все же, выполнив ряд замысловатых манипуляций над сегментом данных DLL, можно создать общую область памяти для всех процессов, использующих данную библиотеку.

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

#pragma data_seg(".myseg") int sharedlnts ; // другие переменные общего пользования #pragma data_seg() #pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

Все переменные, объявленные между директивами #pragma data_seg(), размещаются в сегменте.myseg. Директива #pragma comment () - не обычный комментарий. Она дает указание библиотеке выполняющей системы С пометить новый раздел как разрешенный для чтения, записи и совместного доступа.

Полная компиляция DLL

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

Если в.def-файле есть строка LIBRART, указывать явно параметр /DLL в командной строке редактора связей не нужно.

Для MFC предусмотрен ряд особых режимов, касающихся использования динамической библиотекой библиотек MFC. Этому вопросу посвящен следующий раздел.

DLL и MFC

Программист не обязан использовать MFC при создании динамических библиотек. Однако использование MFC открывает ряд очень важных возможностей.

Имеется два уровня использования структуры MFC в DLL. Первый из них - это обычная динамическая библиотека на основе MFC, MFC DLL (regular MFC DLL). Она может использовать MFC, но не может передавать указатели на объекты MFC между DLL и приложениями. Второй уровень реализован в динамических расширениях MFC (MFC extensions DLL). Использование этого вида динамических библиотек требует некоторых дополнительных усилий по настройке, но позволяет свободно обмениваться указателями на объекты MFC между DLL и приложением.

Обычные MFC DLL

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

Однако обычные DLL не могут обмениваться с приложениями указателями на классы, производные от MFC.

Если приложению необходимо обмениваться с DLL указателями на объекты классов MFC или их производных, нужно использовать расширение DLL, описанное в следующем разделе.

Архитектура обычных DLL рассчитана на использование другими средами программирования, такими как Visual Basic и PowerBuilder.

При создании обычной библиотеки MFC DLL с помощью AppWizard выбирается новый проект типа MFC AppWizard (dll) . В первом диалоговом окне мастера приложений необходимо выбрать один из режимов для обычных динамических библиотек: "Regular DLL with MFC statistically linked" или "Regular DLL using shared MFC DLL". Первый предусматривает статическое, а второй - динамическое подключение библиотек MFC. Впоследствии режим подключения MFC к DLL можно будет изменить с помощью комбинированного списка на вкладке "General" диалогового окна "Project settings".

Управление информацией о состоянии MFC

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

AFX_MANAGE_STATE(AfxGetStaticModuleState()) ;

Данный оператор определяет использование соответствующей информации о состоянии во время выполнения функции, обратившейся к данной подпрограмме.

Динамические расширения MFC

MFC позволяет создавать такие библиотеки DLL, которые воспринимаются приложениями не как набор отдельных функций, а как расширения MFC. С помощью данного вида DLL можно создавать новые классы, производные от классов MFC, и использовать их в своих приложениях.

Чтобы обеспечить возможность свободного обмена указателями на объекты MFC между приложением и DLL, нужно создать динамическое расширение MFC. DLL этого типа подключаются к динамическим библиотекам MFC так же, как и любые приложения, использующие динамическое расширение MFC.

Чтобы создать новое динамическое расширение MFC, проще всего, воспользовавшись мастером приложении, присвоить проекту тип MFC AppWizard (dll) и на шаге 1 включить режим "MFC Extension DLL". В результате новому проекту будут присвоены все необходимые атрибуты динамического расширения MFC. Кроме того, будет создана функция DllMain для DLL, выполняющая ряд специфических операций по инициализации расширения DLL. Следует обратить внимание, что динамические библиотеки данного типа не содержат и не должны содержать объектов, производных от CWinApp .

Инициализация динамических расширений

Чтобы "вписаться" в структуру MFC, динамические расширения MFC требуют дополнительной начальной настройки. Соответствующие операции выполняются функцией DllMain . Рассмотрим пример этой функции, созданный мастером AppWizard.

Static AFX_EXTENSION_MODULE MyExtDLL = { NULL, NULL } ; extern "C" int APIENTRY DllMain(HINSTANCE hinstance, DWORD dwReason, LPVOID IpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { TRACED("MYEXT.DLL Initializing!\n") ; // Extension DLL one-time initialization AfxInitExtensionModule(MyExtDLL, hinstance) ; // Insert this DLL into the resource chain new CDynLinkLibrary(MyExtDLL); } else if (dwReason == DLL_PROCESS_DETACH) { TRACED("MYEXT.DLL Terminating!\n") ; } return 1; // ok }

Самой важной частью этой функции является вызов AfxInitExtensionModule . Это инициализация динамической библиотеки, позволяющая ей корректно работать в составе структуры MFC. Аргументами данной функции являются передаваемый в DllMain дескриптор библиотеки DLL и структура AFX_EXTENSION_MODULE, содержащая информацию о подключаемой к MFC динамической библиотеке.

Нет необходимости инициализировать структуру AFX_EXTENSION_MODULE явно. Однако объявить ее нужно обязательно. Инициализацией же займется конструктор CDynLinkLibrary . В DLL необходимо создать класс CDynLinkLibrary . Его конструктор не только будет инициализировать структуру AFX_EXTENSION_MODULE, но и добавит новую библиотеку в список DLL, с которыми может работать MFC.

Загрузка динамических расширений MFC

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

If(dwReason == DLL_PROCESS_DETACH) { AfxTermExtensionModule(MyExtDLL); }

Кроме того, следует помнить, что новая библиотека DLL является динамическим расширением и должна загружаться и выгружаться динамически, с помощью функций AfxLoadLibrary и AfxFreeLibrary ,а не LoadLibrary и FreeLibrary .

Экспортирование функций из динамических расширений

Рассмотрим теперь, как осуществляется экспортирование в приложение функций и классов из динамического расширения. Хотя добавить в DEF-файл все расширенные имена можно и вручную, лучше использовать модификаторы для объявлений экспортируемых классов и функций, такие как AFX_EXT_CLASS и AFX_EXT_API,например:

Class AFX_EXT_CLASS CMyClass: public CObject (// Your class declaration } void AFX_EXT_API MyFunc() ;

Библиотекой называется совокупность функций, объединенных в один файл и хранящихся в скомпилированном виде.


Код библиотеки статической компоновки связывается с кодом программы в процессе сборки исполняемого EXE файла. Эту работу выполняет компоновщик, входящий в состав интегрированной среды разработки.


Библиотеки в Windows могут быть статической (.LIB) и динамической (.DLL) компоновки.

Основная особенность библиотек динамической компоновки состоит в том, что она связывается с программой не на этапе сборки, а во время выполнения программы.


В результате получаем преимущества:

  1. Один и тот же код может использоваться несколькими программами (достигается экономия памяти).
  2. Упрощается процесс внесения изменений в программы (достаточно заменить библиотеку).

Недостатки:

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

1.2 Простая dll

Создание dll в Visual C++

Опишем по шагам процесс создания простой динамической библиотеки в Microsoft Visual C++ 2008 Express ed.

Шаг 1 . Создадим новый проект


Шаг 2 . Выберем категорию проекта, название и расположение на диске




Шаг 3 . Выберем настройки проекта




Шаг 4 . Добавим в созданный проект новый.cpp-файл




Шаг 5 . Укажем тип файла: .cpp




Шаг 6 . Добавим текст библиотеки




Библиотека будет состоять всего из 2-х функций:

  1. DllMain - главная функция библиотеки, аналог main в консольных программах.
  2. fnsimple_dll - пробная функция, не принимающая параметров и возвращающая целое число (константу 100).

Шаг 7 . Добавим в библиотеку def -файл для экспортирования имени функции fnsimple_dll




Шаг 8 . Укажем имя: simple_dll.def (имя совпадает с именем библиотеки)




Шаг 9 . В .def -файл поместим несколько строк, содержащих имя динамической библиотеки (раздел LIBRARY ) и имена экспортируемых функций (раздел EXPORTS )




Шаг 10 . Выберем настройки свойств проекта (Properties )


Шаг 11 . Укажем имя только что созданного def-файла в строке с параметром Module Definition File и сохраним настройки




Последний шаг - это построение библиотеки (Build ).


Если на этапе построения возникают ошибки, то нужно внимательно изучить текст cpp -файла и устранить опечатки.


Результатом работы компилятора и компоновщика будет появление файла simple_dll.dll в подкаталоге Debug каталога проекта. Этот файл можно копировать в любое место файловой системы.

2 Работа с dll

2.1 Работа с dll из консольного приложения

Консольное приложение

Необходимо создать новое консольное приложение и назвать его test_dll . В проект добавить cpp -файл и поместить туда следующее содержимое (путь к файлу библиотеки нужно указать свой)




Приведённый текст программы иллюстрирует порядок работы с функциями из библиотеки:


Шаг 1. Подключаем заголовочный файл windows.h и файлы, необходимые для компиляции содержимого библиотеки. В нашем случае - это консольный ввод/вывод.


Шаг 2. Объявляем указатели на функции, содержащиеся в библиотеке. В нашем примере это функция, которая не принимает параметров, но возвращает целое число.

Int (* fnsimple_dll)();

Шаг 3. Объявляем специальную переменную для работы с библиотекой

HMODULE lib;

Шаг 4. Загружаем библиотеку при помощи функции LoadLibraryA с указанием имени файла. Имя файла может быть как относительным, так и абсолютным. После вызова функции необходимо проверить содержимое переменной lib .


Шаг 5. Если загрузка библиотеки прошла успешно, то мы пытаемся связать ранее объявленный указатель на функцию с функцией из библиотеки с помощью GetProcAddress . В качестве параметров указывается lib и название функции. Результат необходимо явно преобразовать к типу указателя.


Шаг 6. Если предыдущий шаг завершился успешно, то можно вызывать функцию из dll. Если нет, то необходимо проверить настройки экспорта в simple_dll и перекомпилировать библиотеку.

Шаг 7. Освободим ресурсы, занимаемые библиотекой, через вызов функции FreeLibrary


После запуска программы можно видеть результат, возвращаемый fnsimple_dll




2.2 Работа с dll из MS Excel

MS Excel

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

  1. Вставить в рабочую книгу Модуль
  2. Поместить в модуль объявление функций из динамической библиотеки

Шаг 1. Запустить MS Excel и вызвать настройки параметров




Шаг 2. Установить значение параметра Показывать вкладку Разработчик на ленте




Шаг 3. Перейти в редактор Visual Basic и вставить в рабочую книгу новый модуль




Шаг 4. Поместить в модуль объявление функции из dll




Шаг 5. Перейти на рабочий лист и вызвать Мастер функций . Выбрать категорию Определённые пользователем




Шаг 6. Поместить вызов функции в ячейку



Вопросы для самоконтроля

  1. Что такое библиотека функций?
  2. В чём особенности библиотек статической компоновки?
  3. Перечислите достоинства dll.
  4. В чём состоят недостатки dll?
  5. Что является результатом построения проекта?

Список литературы

  1. Богатырёв А. Язык С в системе Unix.. 111.
  2. Подбельский В.В., Фомин С.С. Программирование на языке С.. 111.
  3. Голуб А. Правила программирования на С и С++.. 111.
  4. Хэзфилд Р., Кирби Л. Искусство программирования на С.. 111.
  5. Дейтел Х., Дейтел П. Как программировать на С.. 111.
  6. Керниган Б.,Ритчи Д. Язык С.. 111.

Библиотеки динамической компоновки (DLL)

DLL (англ. Dynamic Link Library -- «библиотека динамической компоновки», «динамически подключаемая библиотека») в операционных системах Microsoft Windows и IBM OS/2 -- динамическая библиотека, позволяющая многократное использование различными программными приложениями. K DLL относятся также элементы управления ActiveX и драйверы. В мире UNIX аналогичные функции выполняют так называемые общие объекты.

Формат файлов DLL придерживается тех же соглашений, что и формат исполняемых файлов, сочетая код, таблицы и ресурсы, отличаясь лишь интерпретацией некоторых полей.

Библиотеки динамической компоновки также имеют способность обеспечивать обмен данными между процессами. Когда в рамках DLL объявляется переменная, ее можно сделать разделяемой (shared). Все процессы, обращающиеся к библиотеке, для таких переменных будут использовать одно и то же место в физической памяти. (Здесь также важно не забыть о синхронизации.)

Протокол динамического обмена данными (Dynamic Data Exchange, DDE)

Этот протокол выполняет все основные функции для обмена данными между приложениями. Он очень широко использовался до тех пор, пока для этих целей не стали применять OLE (впоследствии ActiveX). На данный момент DDE используется достаточно редко, в основном для обратной совместимости.

Больше всего этот протокол подходит для задач, не требующих продолжительного взаимодействия с пользователем. Пользователю в некоторых случаях нужно только установить соединение между программами, а обмен данными происходит без его участия. Замечу, что все это в равной степени относится и к технологии OLE/ActiveX.

OLE/ActiveX

OLE (англ. Object Linking and Embedding)-- технология связывания и внедрения объектов в другие документы и объекты, разработанная корпорацией Майкрософт.

В 1996 году Microsoft переименовала технологию в ActiveX.

OLE позволяет передавать часть работы от одной программы редактирования к другой и возвращать результаты назад. Например, установленная на персональном компьютере издательская система может послать некий текст на обработку в текстовый редактор, либо некоторое изображение в редактор изображений с помощью OLE-технологии.

Основное преимущество использования OLE (кроме уменьшения размера файла) в том, что она позволяет создать главный файл, картотеку функций, к которой обращается программа. Этот файл может оперировать данными из исходной программы, которые после обработки возвращаются в исходный документ.

OLE используется при обработке составных документов, может быть использована при передаче данных между различными несвязанными между собой системами посредством интерфейса переноса, а также при выполнении операций с буфером обмена. Идея внедрения широко используется при работе с мультимедийным содержанием на веб-страницах, где используется передача изображения, звука, видео, анимации в страницах HTML либо в других файлах, также использующих текстовую разметку (например, XML и SGML). Однако, технология OLE использует архитектуру «толстого клиента», то есть сетевой ПК с избыточными вычислительными ресурсами. Это означает, что тип файла либо программа, которую пытаются внедрить, должна присутствовать на машине клиента. Например, если OLE оперирует таблицами Microsoft Excel, то программа Excel должна быть инсталлирована на машине пользователя.

В 1996 году Microsoft переименовала технологию OLE 2.0 в ActiveX. Были представлены элементы управления ActiveX, ActiveX документы и технология Active Scripting. Эта версия OLE в основном используется веб-дизайнерами для вставки в страницы мультимедийных данных.

Это действительно универсальная технология, и одно из многих ее применений - межпроцессный обмен данными. Хотя, стоит отметить, что OLE как раз для этой цели и создавалась (на смену DDE), и только потом была расширена. Специально для обмена данными существует интерфейс IDataObject. А для обмена данными по сети используется DCOM, которую под некоторым углом можно рассматривать как объединение ActiveX и RPC.

Каналы (pipes)

В среде операционной системы Microsoft Windows NT вам доступно такое удобное средство передачи данных между параллельно работающими процессами, как каналы типа Pipe. Это средство позволяет организовать передачу данных между локальными процессами, а также между процессами, запущенными на различных рабочих станциях в сети.

Каналы типа Pipe больше всего похожи на файлы, поэтому они достаточно просты в использовании.

Через канал можно передавать данные только между двумя процессами. Один из процессов создает канал, другой открывает его. После этого оба процесса могут передавать данные через канал в одну или обе стороны, используя для этого хорошо знакомые вам функции, предназначенные для работы с файлами, такие как ReadFile и WriteFile. Заметим, что приложения могут выполнять над каналами Pipe синхронные или асинхронные операции, аналогично тому, как это можно делать с файлами. В случае использования асинхронных операций необходимо отдельно побеспокоиться об организации синхронизации.

Каналы - это очень мощная технология обмена данными. Наверное, именно поэтому в полной мере они поддерживаются только в Windows NT/2000. В общем случае канал можно представить в виде трубы, соединяющей два процесса. Что попадает в трубу на одном конце, мгновенно появляется на другом. Чаще всего каналы используются для передачи непрерывного потока данных.

Именованные и анонимные каналы

Существуют две разновидности каналов Pipe - именованные (Named Pipes) и анонимные (Anonymous Pipes).

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

Анонимные каналы обычно используются для организации передачи данных между родительскими и дочерними процессами, запущенными на одной рабочей станции или на “отдельно стоящем” компьютере.

Анонимные каналы используются достаточно редко, они просто передают поток вывода одного процесса на поток ввода другого. Именованные каналы передают произвольные данные и могут работать через сеть. (Именованные каналы поддерживаются только в WinNT/2000.)

Сокеты (sockets)

Сокеты (англ. socket -- разъём) -- название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет -- абстрактный объект, представляющий конечную точку соединения.

Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с оконечными аппаратами телефонной сети, а серверные -- с коммутаторами. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) -- как клиентские, так и серверные сокеты.

Интерфейс сокетов впервые появился в BSD Unix. Программный интерфейс сокетов описан в стандарте POSIX.1 и в той или иной мере поддерживается всеми современными операционными системами.

приложение память межпроцессный сокет

Принципы сокетов

Каждый процесс может создать слушающий сокет (серверный сокет) и привязать его к какому-нибудь порту операционной системы (в UNIX непривилегированные процессы не могут использовать порты меньше 1024). Слушающий процесс обычно находится в цикле ожидания, то есть просыпается при появлении нового соединения. При этом сохраняется возможность проверить наличие соединений на данный момент, установить тайм-аут для операции и т.д.

Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.

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

Это очень важная технология, т.к. именно она отвечает за обмен данными в Интернет. Сокеты также часто используются в крупных ЛВС. Взаимодействие происходит через т.н. разъемы-"сокеты", которые представляют собой абстракцию конечных точек коммуникационной линии, соединяющей два приложения. С этими объектами программа и должна работать, например, ждать соединения, посылать данные и т.д. В Windows входит достаточно мощный API для работы с сокетами.

Почтовые слоты (mailslots)

Почтовые слоты - это механизм однонаправленного IPC. Если приложению известно имя слота, оно может помещать туда сообщения, а приложение-хозяин этого слота (приемник) может их оттуда извлекать и соответствующим образом обрабатывать. Основное преимущество этого способа - возможность передавать сообщения по локальной сети сразу нескольким компьютерам за одну операцию. Для этого приложения-приемники создают почтовые слоты с одним и тем же именем. Когда в дальнейшем какое-либо приложение помещает сообщение в этот слот, приложения-приемники получают его одновременно.

Объекты синхронизации

Как ни странно, объекты синхронизации тоже можно отнести к механизмам IPC. Конечно, объем передаваемых данных в данном случае очень невелик. Но именно эти объекты следует использовать, если одному процессу нужно передать другому что-то вроде "я закончил работу" или "я начинаю работать с общей памятью".

Microsoft Message Queue (MSMQ)

Расшифровывается это как Message Queuing (MSMQ) или Сервер очередей сообщений Microsoft. Очередь сообщений создана для взаимодействия приложений в распределенной среде (на разных компьютерах). Мы уже рассматривали подобные механизмы, например, socket или DCOM. Особенность MSMQ в том, что компьютеры не обязательно должны быть одновременно в сети. То есть можно отправить сообщение, можно получить, а за всем этим следит сервер MSMQ.

Есть несколько деталей, которые отличают очереди сообщений от других механизмов обмена данными в распределенной системе.

Доставка между клиентами одновременно не подключенными

Очередь сообщений поддерживается операционной системой

Очередь сообщений поддерживает транзакции

MSMQ 1.0 используется в Windows NT 4.0, Windows 95, and Windows 98.

MSMQ 2.0 используется в Microsoft® Windows® 2000.

Этот протокол действительно оправдывает свое название - он обеспечивает посылку сообщений между приложениями с помощью очереди сообщений. Основное его отличие от стандартной очереди сообщений Windows в том, что он может работать с удаленными процессами и даже с процессами, которые на данный момент недоступны (например, не запущены). Доставка сообщения по адресу гарантируется. Оно ставится в специальную очередь сообщений и находится там до тех пор, пока не появляется возможность его доставить.

Удаленный вызов процедур (Remote Procedure Call, RPC)

Строго говоря, это не совсем технология IPC, а скорее способ значительно расширить возможности других механизмов IPC. С помощью этой технологии общение через сеть становится совешенно прозрачным как для сервера, так и для клиента. Им обоим начинает казаться, что их "собеседник" расположен локально по отношению к ним.

Удалённый вызов процедур (или Вызов удалённых процедур) (от англ. Remote Procedure Call (RPC)) -- класс технологий, позволяющих компьютерным программам вызывать функции или процедуры в другом адресном пространстве (как правило, на удалённых компьютерах). Обычно, реализация RPC технологии включает в себя два компонента: сетевой протокол для обмена в режиме клиент-сервер и язык сериализации объектов (или структур, для необъектных RPC). Различные реализации RPC имеют очень отличающуюся друг от друга архитектуру и разнятся в своих возможностях: одни реализуют архитектуру SOA, другие CORBA или DCOM. На транспортном уровне RPC используют в основном протоколы TCP и UDP, однако, некоторые построены на основе HTTP (что нарушает архитектуру ISO/OSI, так как HTTP изначально не транспортный протокол).

Принцип

Идея вызова удалённых процедур (Remote Procedure Call -- RPC) состоит в расширении хорошо известного и понятного механизма передачи управления и данных внутри программы, выполняющейся на одной машине, на передачу управления и данных через сеть. Средства удалённого вызова процедур предназначены для облегчения организации распределённых вычислений и создания распределенных клиент-серверных информационных систем. Наибольшая эффективность использования RPC достигается в тех приложениях, в которых существует интерактивная связь между удалёнными компонентами с небольшим временем ответов и относительно малым количеством передаваемых данных. Такие приложения называются RPC-ориентированными.

Характерными чертами вызова удалённых процедур являются:

· Асимметричность, то есть одна из взаимодействующих сторон является инициатором;

· Синхронность, то есть выполнение вызывающей процедуры приостанавливается с момента выдачи запроса и возобновляется только после возврата из вызываемой процедуры.

Реализация удалённых вызовов существенно сложнее реализации вызовов локальных процедур. Можно обозначить следующие проблемы и задачи, которые необходимо решить при реализации RPC:

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

· В отличие от локального вызова удалённый вызов процедур обязательно использует транспортный уровень сетевой архитектуры (например TCP), однако это остается скрытым от разработчика.

· Выполнение вызывающей программы и вызываемой локальной процедуры в одной машине реализуется в рамках единого процесса. Но в реализации RPC участвуют как минимум два процесса -- по одному в каждой машине. В случае, если один из них аварийно завершится, могут возникнуть следующие ситуации: при аварии вызывающей процедуры удалённо вызванные процедуры станут «осиротевшими», а при аварийном завершении удалённых процедур станут «обездоленными родителями» вызывающие процедуры, которые будут безрезультатно ожидать ответа от удалённых процедур.

· Существует ряд проблем, связанных с неоднородностью языков программирования и операционных сред: структуры данных и структуры вызова процедур, поддерживаемые в каком-либо одном языке программирования, не поддерживаются точно так же во всех других языках. Таким образом имеется проблема совместимости, до сих пор не решённая ни с помощью введения одного общепринятого стандарта, ни с помощью реализации нескольких конкурирующих стандартов на всех архитектурах и во всех языках.

2.1.3.Библиотеки динамической компоновки – DLL. COM-модель и COM-объекты.

DLL

Файлы DLL (Dynamic Link Library, библиотека динамической компоновки) являются основой программной архитектуры Windows и отличаются от исполняемых файлов фактически только заголовком. Правда, это не означает, что если переименовать DLL-файл, то он станет исполняе-мым: имеется в виду заголовочная информация файла.

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

Помимо кода, DLL-файлы могут хранить данные и ресурсы. Например, при изменении значка (ярлыка) пользователю предлагается на выбор набор значков из файла SHELL32.DLL.

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

function CreateDC; external gdi32 name "CreateDCA";

Здесь величина gdi32 - константа, описанная в этом же модуле:

Const gdi32 = "gdi32.dll";

Таким образом, функция CreateDC физически размещена в файле gdi32.dll и каждое приложение, использующее функции GDI, обращается к этой библиотеке.

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

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

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

Иногда программисту бывает необходи-мо просмотреть список функций и процедур, размещенных в конкретном файле DLL. Для этого можно воспользоваться утилитой tdump.exe, поставляемой в составе Delphi (в каталоге bin). Для ее использования скопируйте ее и необходимый dll-файл в отдельный каталог и запустите утилиту с параметрами <имя анализируемого файла> и <имя файла-результата>, например:

TDUMP.EXE gdi32.dll gdi.txt

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

Итак, чаще всего DLL представляет собой набор функций и процедур. Как говорится в справке Delphi по DLL, "динамические библиотеки являются идеалом для многоязыковых проектов". Это действительно так: при исполь-зовании DLL совершенно безразлично, в какой среде созданы сама биб-лиотека и вызывающие ее модули.

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

Приведем несложный пример библиотеки:

library MyDLL; //проект библиотеки. Файл должен иметь то же имя.

//описание экспортируемой функции

procedure MySquare (var x: integer); export; stdcall;

exports //список экспортируемых функций

//блок инициализации библиотеки

После компиляции образуется файл MyDLL.DLL. Таким образом, сервер готов. Теперь создадим клиента:

procedure MySquare (var x:integer); stdcall; external "MyDLL.dll";

ShowMessage(IntToStr(x));

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

Динамическое связывание отличается тем, что клиент загружает библиотеку не сразу же после своего размещения в памяти, т.е. запуска, а по мере надобности. Приведем пример динамического связывания:

// процедурный тип подгружаемой функции

type TMySquare = procedure (var x:integer); stdcall;

procedure TForm1.Button1Click (Sender: TObject);

hcDLL: THandle; //указатель на библиотеку

ProcMySquare: TMySquare; //подгружаемая функция

hcDLL:=LoadLibrary("MyDLL.dll"); //Динамически загружаем DLL

if hcDLL<=HINSTANCE_ERROR then //Проверка на наличие библиотеки

ShowMessage("Не найдена библиотека MyDLL.dll.");

//библиотека загружена. Получаем адрес точки входа функции.

ProcMySquare:=GetProcAddress(hcDLL, "MySquare");

if not Assigned(procMySquare) then //Проверка на наличие функции

ShowMessage("Функция не найдена.");

procMySquare(x);

ShowMessage(IntToStr(x));

FreeLibrary(hcDLL); //Выгружаем библиотеку

Схема действий теперь такова: загружаем библиотеку только в тот момент, когда она действительно необходима, получаем адрес требуемой функции, связываем его с соответствующей переменной процедурного типа, и лишь потом обращаемся к ней. Обратите внимание, что успешная загрузка библиотеки не является признаком того, что мы можем успешно использовать нужную нам функцию. Это требуется проверять дополнительно, поскольку в общем случае разные по функциональному составу библиотеки могут иметь одинаковое имя. И, конечно, как это водится у порядочных программистов, после завершения работы с библиотекой сообщаем системе, что мы в библиотеке больше не нуждаемся и ее можно выгрузить, освободив ресурсы. Правда, это не означает, что библиотека на самом деле будет выгружена – система регистрирует все подключения к библиотеке (в том числе и из других программ) и выгрузит ее только тогда, когда все подключения будут освобождены.

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

COM-модель

Технология, основанная на динамических библиотеках, является очень эффективной, поэтому и стала основой программной архитектуры операционной системы. Однако, ей присуще ограничение, не позволяющее использовать парадигму объектно-ориентированного программирования (ООП): библиотеки DLL "в чистом виде" могут содержать код функций и процедур, а также ресурсы, но не способны содержать описание классов. По мере развития программирования как технологии, возникла необходимость поддержки ООП на уровне операционной системы.

Самым ходовым примером такого использования идей ООП на уровне системы являются составные документы. Вставляя в текстовый документ электронную таблицу или записывая в нем математическую формулу с помощью редактора формул, пользователь как раз и встречается со зримым воплощением ООП. Вставленный, внедренный документ является объектом со своими методами и свойствами. Это пример зримого воплощения технологии COM (Component Object Model, модель компонентных объектов). Хотя в примере и приведены только составные документы, COM представляет концепцию взаимодействия программ любых типов: библиотек, приложений, системного программного обеспечения и др.

Первоначально для всей группы технологий, в основе которых лежит COM, корпорацией Microsoft было предложено общее имя -– OLE. Затем по мере развития и дополнения технологии, это название менялось. В настоящий момент укрепилось название ActiveX, но программисты со стажем часто по привычке так и продолжают пользоваться термином OLE для обозначения данной группы технологий.

Важно уяснить, что COM – это не язык, не протокол. Это метод взаимодействия между программами и способ создания программ.

Функции программы, доступные для использования другими приложениями, называются сервисами . COM определяет стандартный механизм, с помощью которого одна часть программного обеспечения предоставляет свои сервисы другой.

Для нас особенно важным является то, что технология COM также является независимой от языка программирования. Физически приложение, предоставляющее сервисы, может быть реализовано в виде обычного выполнимого модуля, либо, чаще всего, в виде библиотеки. Как и в случае обычных библиотек DLL, неважно, в какой программной системе созданы серверы и использующие их клиенты. В случае с обычной DLL-библиотекой клиенту достаточно знать адрес точки входа нужной функции и в определенный момент передать управление по этому адресу. Тот факт, что библиотека должна предоставлять не обычные функции, а методы объектов, внес в эту схему некоторые изменения.

COM-объекты

COM-объекты представляют собой двоичные программные компоненты, подобные невизуальным компонентам Delphi, устанавливаемые на уровне операционной системы и доступные для использования в любой среде программирования. Основное отличие COM-объектов от других состоит в том, что у них нет свойств, а есть только методы. К тому же, еще одно отличие состоит в методах использовании конструкторов и деструкторов.

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

Для удаления COM-объекта вместо метода Free обычно предназначен метод _Release.

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

Сервер поддерживает один или несколько интерфейсов, состоящих из методов. Клиенты могут получать доступ к серверу только через вызовы методов интерфейсов объекта. Иного непосредственного доступа к данным у них нет.

Все COM-интерфейсы унаследованы от интерфейса, называемого IUnknown, обладающего тремя методами: QueryInterface, AddRef и _Release.

Последний в этом списке метод мы уже вскользь обсуждали – удаление объекта.

Предпоследний метод предназначен для подсчета ссылок на интерфейсы. Клиент явно инициирует начало работы экземпляра COM-объекта, а для завершения его работы он вызывает метод _Release. Объект ведет подсчет клиентов, использующих его, и когда количество клиентов становится равным нулю, т.е. когда счетчик ссылок становиться нулевым, объект самоуничтожается. Это сделано для избежания преждевременного уничтожения объекта. Дело в том, что клиент, получив указатель на интерфейсы объекта, способен передать один из них другому клиенту без ведома сервера. В такой ситуации ни один из клиентов не может закончить работу объекта с гарантией того, что делает это не преждевременно. Пара методов AddRef и _Release дает гарантию того, что объект исчезнет только тогда, когда никто его не использует.

Обычно свой первый указатель на интерфейс объекта клиент приобретает при создании главного объекта. Имея первый указатель, клиент получает указатели на другие интерфейсы объекта, методы которых ему необходимо вызвать, запрашивая у объекта эти указатели с помощью метода QueryInterface.

Завершая обзор COM-модели, нужно подчеркнуть, что она является развитием технологии "традиционных" DLL, позволяющей использовать парадигму ООП на уровне операционной системы, функций API.

2.2.Обзор среды программирования Borland Cbuilder

2.2.1.Отличия Borland CBuilder и Borland Delphi. Особенности синтаксиса языка C++ в Borland Cbuilder.

(лекция не читалась)

2.2.2.Особенности разработки приложений в среде CBuilder

(лекция не читалась)

3.Технологии компьютерной графики

3.1.Разработка графических приложений без использования специализированных библиотек

3.1.1.Методы ускорения построения 2D изображений. Динамическая запись в видеопамять устройства.

(лекция не читалась)

3.1.2.Работа с изображениями. Форматы графических файлов (BMP, JPEG, GIF). Чтение и запись графических файлов.

(лекция не читалась)

3.2.Разработка графических приложений с использованием специализированных библиотек

3.2.1.Обзор библиотек OpenGL и DirectX

На протяжение многих лет среди программистов идет спор о преимуществах использования того или иного интерфейса для создания графики в компьютерных играх и других графических приложениях. Главные соперники в этой области - библиотеки OpenGL и DirectX. Индустрия до сих пор так и не сделала однозначный выбор в пользу того или иного API.

Общие сведения о OpenGL

Стандарт OpenGL (Open Graphics Library - открытая гра-фическая библиотека) был со-здан и утвержден в 1992 году ведущими фирмами в области разработки программного обеспечения как эффективный аппаратно-независимый интер-фейс, пригодный для реализации на различных платформах. Основой стандарта стала биб-лиотека IRIS GL, изначально разработанная фирмой Silicon Graphics Inc (SGI).

С начала 90-х годов OpenGL использу-ется в различных областях ин-дустрии и науки. Архитектура библиотеки получилась на-столько удачной, что уже на протяжении более десяти лет она остается стабильной и предсказуемой. OpenGL де-факто является стандартом в области программирования графики. Но в этом скрыт и ее недостаток. Комитет по пересмотру архитектуры (ARB) работает довольно медленно – любое изменение стандарта требует множества согласований, оформления документации и т.д. Как следствие, OpenGL развивает-ся очень медленно. Правда, до недавнего времени с этим не было особых проблем, поскольку изна-чально библиотека предназна-чалась для быстрых рабочих станций профессионального уровня, которые обновляют не так уж часто. Однако сейчас даже дешевые видеокарты за $100 превзошли уровень профессиональных монстров пяти-летней давности стоимостью в тысячи долларов. При этом об-новление их возможностей происходит в среднем раз в год. Фактически OpenGL не по-спевает за индустрией, и, что-бы получить доступ к новей-шим функциям видеокарт, иг-ровые разработчики вынужде-ны использовать так называе-мый механизм расширений (extensions).

На данный момент OpenGL прошла путь от версии 1.0 всего до версии 1.4. "Революционная" версия 2.0 находится в процессе стан-дартизации.

Общие сведения о DirectX

К моменту выхода Windows 95 большинство игр по-прежнему делалось под MS-DOS. Windows в те времена не пред-оставляла никаких возможностей для программирования игр. Много-численные уровни абстракции (введенные в целях совмести-мости и универсальности) де-лали доступ к звуковому и ви-деооборудованию весьма мед-ленным и неприменимым для игровых приложений. Поэтому было решено разработать биб-лиотеку, предоставляющую возможность прямого доступа к аппаратуре. Это позволило бы играм работать на приемлемой скорости и, как следствие, увеличило бы про-дажи Windows 95.

Вместо создания собствен-ного API Microsoft использовала разработку небольшой компа-нии RenderMorphic. По неофициальной версии, изначально API был выполнен авторами в рамках студенческо-го задания и в конечном итоге провалился на экзамене. Тем не менее, Microsoft интегрировала эту библиотеку в свой Game SDK (игровой комплект разработки). Корпорация подавала это как идеальное решение для программирования игр.

Однако то, что позже стало называться DirectX 1.0, не при-обрело широкой популярности. Библиотека оказалась медлен-ной, с большим количеством ошибок, с неудобной архитекту-рой и, кроме того, чрезмерно сложной.

Разумеется, Microsoft не со-биралась сдаваться и продол-жила развитие библиотеки с учетом пожеланий разработчи-ков игр. Первой более или ме-нее жизнеспособной версией была DirectX 3.0. Позже после-довали версии 5, 6 и 7 (четвер-той не было), Седьмая версия была воспринята разработчика-ми с интересом: она хорошо работала, ее интерфейсы были достаточно удобны в использовании. Восьмая версия при-несла интересные нововведения – вершинные и пиксельные шейдеры (специальные, обычно короткие программы, предназначенные для выполнения на графическом процессоре; ис-пользуются для расчета осве-щения, создания тех или иных спецэффектов и т.п.). Не-давно вышедший DirectX 9 явился развитием этого перспективного направления.

Длительное время DirectX рассматривалась как неудачная альтернатива OpenGL. Однако последние улучшения в API сделали эту библиотеку весьма мощной и стабильной. По-скольку она разрабатывается авторами ОС, можно утверждать, что скорость ее рабо-ты с графикой оптимальна. Многие считают, что именно DirectX, а не OpenGL, становит-ся стандартом для программи-рования графики. Microsoft по-стоянно работает в тесном кон-такте с разработчиками аппаратуры, обеспечивая поддержку новых возможностей. Более того, DirectX иногда предлагает различные возмож-ности раньше, чем на рынке появляются видеокарты с их аппаратной реализацией.

Кроме того, DirectX, помимо собственно графики, предлагает интерфейсы для работы со звуком, источникам ввода, мультимедиа и т.д.. У OpenGL таких функций нет - это чисто графическая библиотека.

Архитектура

Ключевая особенность OpenGL – простота. Ядро OpenGL конт-ролирует процесс обработки примитивов (т.е. треуголь-ников). Для передачи данных используется процедурная мо-дель, фактически – вызовы функций. В каждый момент времени состояние OpenGL оп-ределяется через набор пере-менных, задающих параметры обработки (например, наклады-вать текстуру или не накладывать, использовать источник освещения или нет и т.п.). Каждый новый передан-ный треугольник проходит об-работку в соответствии с теку-щим состоянием. Такой меха-низм весьма эффективен, а код обычно короток и прост. Хотя ядро OpenGL процедурное, в использовании OpenGL совме-стно с объектно-ориентиро-ванными технологиями слож-ностей обычно не возникает: все зависит от выбора про-граммиста.

Структура DirectX очень сильно отличается от OpenGL. DirectX основан на модели COM (Component Object Model). Как следствие, в отличие от простого вызова функций эта модель предполагает выполнение дополнительных действий, связанных с компонентной архитектурой DirectX. Такая архитектура имеет как достоинства, так и недостатки. В частности, код, в котором используются вызовы DirectX, обычно трудно назвать легко читаемым и понимаемым. Даже рисование простого треугольника требует огромного объема кода. Раз-работчики Microsoft, конечно, понимают это, поэтому для уп-рощения программирования ими создана отдельная биб-лиотека DirectX Common Files, которая скрывает часто ис-пользуемый код. Однако, даже она не спасает ситуацию.

Хотя архитектура DirectX сильно отличается от OpenGL, в их развитии все более за-метны тенденции к сближе-нию. Такая ситуация возникает прежде всего потому, что обе библиотеки предназначены для эффективной работы с ап-паратурой, и чем ближе их структура будет к аппаратной реализации, тем меньше времени будет уходить на преобразование ко-манд пользователя в команды аппаратуры.

Производительность

Вопрос производительности настолько же важен, насколько запутан и неясен. Дебаты на тему "Что быстрее - OpenGL или DirectX?" не утихают. При этом, как ни парадоксально, скорость обоих библиотек оди-накова.

Иначе и быть не может, по-тому что сейчас большинство функций реализованы напря-мую через аппаратные ускори-тели. Естественно, производи-тельность может различаться в зависимости от степени опти-мизации программного кода и используемой для тестирова-ния аппаратной платформы. Оптимизация аппаратных драй-веров тоже может внести свой вклад в преимущество той или иной библиотеки. Такие вещи достаточно тяжело предвидеть, поэтому хорошие игровые "движки" часто имеют две вер-сии: под OpenGL и под DirectX. Это приводит к тому, что срок разработки увеличивается, стоимость ее возрастает и, естественно, появля-ются ошибки. Однако, таковы реа-лии современного рынка.

Сравнение

В чем же, основное различие между биб-лиотеками? Прежде всего – в удобстве интерфейса, функциях, гибкости, перспективах разви-тия и области применения.

Начнем с функций. В последнее время все чаще появляются заявления вроде "DirectX 9 под-держивает пиксельные шейдеры, a OpenGL не поддерживает, поэтому все игры должны быть написаны под DirectX!" Такое мнение верно только отчасти. Действительно, если посмот-реть на стандарт OpenGL по-следней версии (1.4), в нем ни слова о шейдерах.

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

Этот процесс выглядит сле-дующим образом: как только производитель выпускает ви-деокарту с поддержкой опреде-ленной полезной функции, он включает ее в свою реализа-цию OpenGL (которая обычно входит в поставку драйвера). Для программиста эта возмож-ность становится доступной, если он специальным образом запросит данное расширение. Конечно, такой путь не универсален: на другой видеокарте сделать это наверняка не получится из-за различий в интерфейсе. Поэтому существуют расшире-ния, одобренные ARB, – при их использовании можно надеять-ся, что они будут работать на видеокартах различных произ-водителей. Такие расширения являются кандидатами на вклю-чение в последующие версии OpenGL.

Данный механизм является не-удобным. В DirectX все проще: функциональность, либо под-держивается, либо не поддер-живается данной версией биб-лиотеки. Если нет – придется надеяться и ждать следующей версии. Однако случаи, когда аппаратные функции не ис-пользуются из-за того, что не были включены в версию DirectX, довольно редки – как уже говорилось, Microsoft тесно работает с производителями аппаратуры. С другой стороны, ждать версий DirectX приходится око-ло года, а в это время новые функции уже доступны через расширения OpenGL.

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

DirectX очень удобен для любителей объектно-ори-ентированного программирования и СОМ. СОМ в DirectX используется для внесения изменений в библиотеку (в новых версиях) без изменения существующего кода. В OpenGL такого нет, но это вряд ли можно назвать серьезным не-достатком. И вот почему.

Объем кода, необходимого для написания простой про-граммы на DirectX, весьма ве-лик (варьируется от 200 до 800 строк). Microsoft активно пыта-ется уменьшить этот показа-тель, но пока ее усилия особого успеха не приносят. В OpenGL все существенно проще - для решения такой же задачи необходимо менее 50 строк кода.

Серьезным достоинством OpenGL является, прежде всего, то, что это "открытый стан-дарт". Любая компания, имею-щая аппаратную платформу, может купить лицензию у SGI и затем сделать собственную реализацию OpenGL. Измене-ния в OpenGL предлагаются, обсуждаются и утверждаются представителями различных компаний. Что касается DirectX, то здесь ситуация пря-мо противоположная. Только Microsoft может вносить какие-либо изменения в библиотеку. Иначе говоря, именно Microsoft в конечном итоге оп-ределяет все пути развития библиотеки, и если путь был выбран неверно, это может быть исправлено только в но-вой версии.

Итак, достоинства библиотек становятся наиболее очевидны при их использовании в разных (но в то же время пересекаю-щихся) прикладных областях. DirectX идеален для профессио-нальной разработки игр и муль-тимедийных приложений на платформе Windows. OpenGL ис-пользуется на высокопроизводи-тельных рабочих станциях, в на-учной сфере, в образовании, а также в любых проектах, где требуется переносимость приложений на различные программные или аппаратные платформы. Кроме того, OpenGL применяется и для написания игровых "движков", правда, в последнее время на этом по-прище его теснит DirectX.

Перспективы развития

Появление графических про-цессоров (GPU - Graphics Processing Unit) нового поколения, позволяющих создавать доселе немыслимые спецэффекты в реальном времени, всколыхнуло индустрию: всем стало ясно, что за GPU будущее. Очевидно, что графические библиотеки должны соответствовать этой тенденции.

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

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

В то же время близится к завершению разработка нового стандарта библиотеки – OpenGL 2.0. Развитие графической аппаратуры вышло за пределы исходной спецификации. Вторая версия OpenGL призвана поднять планку и вновь создать стандарт для компьютерной графики на десятилетия. Помимо прочего OpenGL 2.0 включает в себя возможность программирования всего графического конвейера на языке высокого уровня (подобный язык уже появился в девятой версии DirectX и называется HLSL – High Level Shading Language).

Создание проекта библиотеки динамической компоновки (DLL)

1. В меню Файл выберите Создать, Проект.

2. В левой области диалогового окна Новый проект разверните Установленные, Шаблоны, Visual C++ и затем выберите Win32.

3. В центральной области выберите Консольное приложение Win32.

4. Укажите имя для проекта - например, MathFuncsDll - в поле Имя. Укажите имя для решения - например, DynamicLibrary - в поле Имя решения. Нажмите кнопку ОК.

5. На странице Обзор диалогового окна Мастер приложений Win32 нажмите кнопку Далее.

6. На странице Параметры приложения в разделе Тип приложения выберите DLL.

7. Нажмите кнопку Готово, чтобы создать проект.

Добавление класса в библиотеку динамической компоновки

Чтобы создать файл заголовка для нового класса, в меню Проект выберите Добавить новый элемент. В диалоговом окне Добавить новый элемент в левой области в разделе Visual C++ выберите Код. В центральной области выберите Файл заголовка (.h). Укажите имя для файла заголовка, например MathFuncsDll.h, а затем нажмите кнопку Добавить. Показан пустой заголовочный файл.

Добавьте следующий код в начало файла заголовка:

// MathFuncsDll.h

#ifdef MATHFUNCSDLL_EXPORTS

#define MATHFUNCSDLL_API __declspec(dllexport)

#define MATHFUNCSDLL_API __declspec(dllimport)

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

namespace MathFuncs

// This class is exported from the MathFuncsDll.dll

class MyMathFuncs

// Returns a + b

static MATHFUNCSDLL_API double Add(double a, double b);

// Returns a - b

static MATHFUNCSDLL_API double Subtract(double a, double b);

// Returns a * b

static MATHFUNCSDLL_API double Multiply(double a, double b);

// Returns a / b

// Throws const std::invalid_argument& if b is 0

static MATHFUNCSDLL_API double Divide(double a, double b);

Когда символ MATHFUNCSDLL_EXPORTS определен, символ MATHFUNCSDLL_API установит модификатор __declspec(dllexport) в объявлениях функции-члена в этом коде. Этот модификатор разрешает экспорт функции библиотекой DLL для использования ее другими приложениями. При неопределенном MATHFUNCSDLL_EXPORTS MATHFUNCSDLL_API определяет модификатор __declspec(dllimport) в объявлениях функции-члена. Этот модификатор позволяет компилятору оптимизировать импорта функции из библиотеки DLL для использования в других приложениях. По умолчанию MATHFUNCSDLL_EXPORTS определяется при сборке проекта MathFuncsDll. Для получения дополнительной информации см. dllexport, dllimport. Примечание

Если проект DLL собирается в командной строке, воспользуйтесь параметром компилятора /D, чтобы определить символ MATHFUNCSDLL_EXPORTS.

В проекте MathFuncsDll в Обозревателе решений в папке Исходные файлы откройте файл MathFuncsDll.cpp.

Реализуйте функциональность класса MyMathFuncs в исходном файле. Код должен выглядеть примерно следующим образом:

// MathFuncsDll.cpp: Defines the exported functions for the DLL application.

#include "stdafx.h"

#include "MathFuncsDll.h"

#include

using namespace std;

namespace MathFuncs

double MyMathFuncs::Add(double a, double b)

double MyMathFuncs::Subtract(double a, double b)

double MyMathFuncs::Multiply(double a, double b)

double MyMathFuncs::Divide(double a, double b)

throw invalid_argument("b cannot be zero!");

Скомпилируйте библиотеку динамической компоновки, выбрав Собрать решение в меню Сборка. Примечание

При использовании выпуска Express, где не отображается меню Сборка в строке меню выберите Сервис, Параметры, Экспертные параметры, чтобы включить его, а затем выберите Сборка,