Студопедия

КАТЕГОРИИ:

АвтоАвтоматизацияАрхитектураАстрономияАудитБиологияБухгалтерияВоенное делоГенетикаГеографияГеологияГосударствоДомЖурналистика и СМИИзобретательствоИностранные языкиИнформатикаИскусствоИсторияКомпьютерыКулинарияКультураЛексикологияЛитератураЛогикаМаркетингМатематикаМашиностроениеМедицинаМенеджментМеталлы и СваркаМеханикаМузыкаНаселениеОбразованиеОхрана безопасности жизниОхрана ТрудаПедагогикаПолитикаПравоПриборостроениеПрограммированиеПроизводствоПромышленностьПсихологияРадиоРегилияСвязьСоциологияСпортСтандартизацияСтроительствоТехнологииТорговляТуризмФизикаФизиологияФилософияФинансыХимияХозяйствоЦеннообразованиеЧерчениеЭкологияЭконометрикаЭкономикаЭлектроникаЮриспунденкция

Обработка сообщений в рамках системы




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

Никаких примеров пока нет – у Вас есть возможность проверить ее работу самостоятельно.

 



Разработка и использование собственных DLL

В данном разделе широко использованы материалы из главы 22 работы [1].

Если Вы хотите писать по-настоящему модульное ПО, то Вас заинтересуют динамически подключаемые библиотеки (Dynamic-Link Library – DLL). Может показаться, что программы и так модульные, раз модульны классы С++. Но разница заключается в том, что классы обеспечивают модульность при разработке программы, a DLL — в период ее выполнения. Вместо гигантских ехе-файлов, которые пришлось бы перестраивать и тестировать при любом, даже самом незначительном, изменении кода, гораздо эффективнее создавать компактные DLL-модули и тестировать их по отдельности. Если, например, выделить в DLL какой-нибудь класс С++, то после компиляции и компоновки объем модуля вряд ли превысит 12 Кб. В период выполнения клиентские программы смогут очень быстро загружать и подключать ваши DLL. В частности, в самой Windows поддержка основных функций строится на применении именно DLL.

Сегодня писать DLL стало гораздо легче. В Win32 модель программирования резко упростилась, да и со стороны AppWizard и MFC поддержка DLL существенно расширена и улучшена. В этой главе Вы научитесь писать на C++ DLL-модули, а также клиентские программы, способные их использовать. Вы увидите, как Win32 проецирует DLL на адресные пространства процессов, и узнаете о различиях между обычными DLL MFC-библиотеки (regular DLLs) и DLL-расширениями (extension DLLs).

Основы DLL

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

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

 

Примечание. DLL-модули в Win32 позволяют экспортировать и глобальные переменные.

В Win32 каждый процесс получает свою копию глобальных переменных DLL для чтения и записи. Если Вы хотите, чтобы несколько процессов совместно использовали какой-то участок памяти, надо либо прибегнуть к файлу, проецируемому в память (file-mapping), либо объявить общий раздел данных (shared data section), как описано в книге «Джеффри Рихтер. Windows для профессионалов – M.: Русская Редакция, 1997). Всякий раз, когда DLL запрашивает память из кучи, эта память выделяется из кучи, принадлежащей клиентскому процессу.

 

Согласование импортируемых элементов с экспортируемыми

 

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

 

В коде DLL экспортируемые функции надо объявлять явным образом, например:

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

__declspec(dllexport) int MyFunction (int n);

 

(Альтернатива — перечислить экспортируемые функции в файле определения модуля (DEF) — гораздо хлопотнее). В клиентской программе необходимо указывать, что данная функция импортируется:

__declspec(dllimport) int MyFunction (int n);

 

Однако компилятор C++ сгенерирует для MyFunction() так называемое расширенное имя (decorated name), которое нельзя использовать в других языках. Это длинное имя, составляемое компилятором из имен класса и функции, а также типов параметров. Чтобы у функции было простое имя, например MyFunction, объявления функций следует писать так:

extern "С" __declspec(dllexport) int MyFunction (int n);

extern "С" __declspec(dllimport) int MyFunction (int n);

 

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

 

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

Явное и неявное связывание

В предыдущем разделе в основном описывалось неявное связывание (implicit linking), которое Вы, как программист на C++, скорее всего, будете применять для своих DLL. Формируя DLL, компоновщик создает дополнительный LIB-файл, содержащий символьные имена всех элементов, экспортируемых из DLL и (необязательно) их порядковые номера, но никакого кода в нем нет. LIB-файл — это суррогат DLL, добавляемый к проекту клиентской программы. Когда собирается клиентская программа, импортируемые символьные имена согласуются с экспортируемыми из LIB, и эти имена (или порядковые номера) сохраняются в БХЕ-файле. LIB-файл содержит, и имя DLL-файла (без указания пути), которое тоже хранится в ЕХЕ-файле. При загрузке клиентского приложения Windows находит и загружает нужные DLL-модули, а затем динамически связывает их по символьным именам или порядковым номерам.

Явное связывание (explicit linking) больше подходит для интерпретирующих языков вроде Visual Basic, но при необходимости его можно использовать и в C++. При явном связывании файл импорта не используется — вместо этого Вы вызываете Win32-функцию LoadLibrary(), указывая полное имя DLL как параметр. LoadLibrary() возвращает параметр типа HINSTANCE, который можно использовать в вызове GetProcAddress(), преобразующей символьное имя (или порядковый номер) в адрес. Допустим, что DLL экспортирует функцию так:

extern "С" __declspec(dllexport) double SquareRoot(double d);

 

Вот пример явного связывания клиента с экспортируемой функцией:

typedef double (SQRTPROC)(double);

HINSTANCE hInstance;

SQRTPROC* pFunction;

VERIFY(hInstance = ::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));

VERIFY(pFunction = (SQRTPROC*)::GetProcAddress(hInstance,

"SquareRoot"));

double d = (*pFunction)(81.0); // вызов функции из DLL

 

Если при явном связывании можно определить, когда загружать или выгружать DLL, то при неявном связывании все DLL загружаются в момент загрузки клиента. Явное связывание позволяет указывать и то, какие DLL следует загрузить. Например, пусть у Вас есть одна DLL со строковыми ресурсами на английском языке, а другая — со строковыми ресурсами на русском. Когда пользователь выберет язык для работы, Ваше приложение сможет загрузить соответствующую DLL.










Последнее изменение этой страницы: 2018-04-12; просмотров: 343.

stydopedya.ru не претендует на авторское право материалов, которые вылажены, но предоставляет бесплатный доступ к ним. В случае нарушения авторского права или персональных данных напишите сюда...