Пример явного подключения и использования DLL



Ниже приведен исходный код приложения MyLibTest2.exe, которое явно загружает библиотеку MyLib.dll, описанную в примере п. 6.1, получает адрес и вызывает из нее функцию MyLibFunc(), создает динамический объект (*pDynObj) класса MyLibClass, вызывает для него методы этого класса:

// MyLibTest2.cpp : Исходный код приложения, которое явно загружает MyLib.dll

#include "stdafx.h"

#include <stdio.h>

#include "MyLib.h" // Заголовочный файл DLL

 

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

                              LPSTR lpCmdLine, int nCmdShow)

{

char cBuffer[256]; // Буфер для хранения выводимой строки

int iSqrVar; // Переменная для хранения квадрата переменной x

 

// Загрузка библиотеки MyLib.dll и получение её описателя

HINSTANCE hMyLib = LoadLibrary("MyLib.dll");

if (hMyLib == NULL)

{

MessageBox(NULL,"DLL не загружена!\nhMyLib==NULL", "Error", MB_OK);

return 1;

}

 

// Объявление указателя на функцию MyLibFunc() и получение её адреса

void (*pMyLibFunc)(PSTR);

(FARPROC &) pMyLibFunc = GetProcAddress(hMyLib, "MyLibFunc");

if (pMyLibFunc == NULL)

{

MessageBox(NULL, "Адрес функции из DLL не получен!", "Error", MB_OK);

return 1;

}

// Вызов функции MyLibFunc() из DLL через указатель

pMyLibFunc("Явная загрузка библиотеки MyLib.dll");

 

// Объявление указателя на функцию CreateDynObj() и получение её адреса

MyLibClass* (*pCreateDynObj)();

(FARPROC &) pCreateDynObj = GetProcAddress(hMyLib, "CreateDynObj");

// Создание динамичесого объекта класса MyLibClass из DLL вызовом

// функции CreateDynObj() из DLL через её указатель

MyLibClass *pDynObj = pCreateDynObj();

 

// Вызов виртуальных функций класса MyLibClass из DLL производится

// напрямую – по имени, через таблицу адресов виртуальных функций vtable

pDynObj->SetX(234);

// Объявление указателя на переменную iMyLibVar и получение её адреса

int *piMyLibVar;

(FARPROC &) piMyLibVar = GetProcAddress(hMyLib, "iMyLibVar");

// Переменной iMyLibVar из DLL через указатель присваивается

// значение свойства х объекта *pDynObj

*piMyLibVar = pDynObj->GetX();

iSqrVar = pDynObj->SqrX();

// Формирование строки для вывода в диалоговом окне

sprintf(cBuffer, "iMyLibVar = %d\n iSqrVar = %d", *piMyLibVar, iSqrVar);

// Вызов функции MyLibFunc() из DLL через указатель

// для вывода строки с результатами

pMyLibFunc(cBuffer);

 

// Объявление указателя на функцию DeleteDynObj() и получение её адреса

void (*pDeleteDynObj)(MyLibClass *);

(FARPROC &) pDeleteDynObj = GetProcAddress(hMyLib, "DeleteDynObj");

// Уничтожение динамического объекта класса MyLibClass из DLL вызовом

// функции DeleteDynObj() из DLL через её указатель

pDeleteDynObj(pDynObj);

 

FreeLibrary(hMyLib); // Выгрузка библиотеки MyLib.dll

 

return 0;

}

В файле с исходным кодом приложения MyLibTest2.cpp вначале производится включение в него стандартных заголовочных файлов stdafx.h и stdio.h, а также – заголовочного файла DLL MyLib.h, содержащего необходимое описание класса MyLibClass из этой DLL (в случае, когда приложение не работает с указанным классом, файл MyLib.h можно не включать). Использовать здесь модификатор extern " C " для объявления импортируемых из DLL идентификаторов внешними в формате C нет необходимости, так как DLL в приложении будет загружена явно, требуемые приложению функции и переменные объявлены в DLL с помощью этого модификатора.

В главной функции приложения WinMain () выполняются следующие действия:

- объявляются символьный массив cBuffer для хранения выводимой строки с результатами и целая переменная iSqrVar для хранения квадрата переменной x – свойства класса MyLibClass из DLL;

- с помощью функции LoadLibrary() производится явная загрузка библиотеки MyLib.dll и получение её описателя hMyLib. Если функции не удастся загрузить указанную DLL, то описатель hMyLib получит значение NULL, после его проверки будет выведено соответствующее сообщение и приложение завершит свою работу;

- производится объявление указателя pMyLibFunc на функцию MyLibFunc() и получение её адреса с помощью функции GetProcAddress(). Если ей не удастся найти в DLL функцию с указанным именем, то указатель pMyLibFunc получит значение NULL, после его проверки будет выведено соответствующее сообщение и приложение завершит свою работу;

- через указатель pMyLibFunc вызывается из DLL функция MyLibFunc(), которая выводит в диалоговом окне сообщение "Явная загрузка библиотеки MyLib.dll";

- объявляется указатель pCreateDynObj на функцию CreateDynObj() из DLL и производится получение её адреса с помощью функции GetProcAddress(). Затем создается динамический объект *pDynObj класса MyLibClass вызовом функции CreateDynObj() из DLL через её указатель pCreateDynObj;

- с использованием указателя pDynObj на созданный динамический объект производится вызов виртуальных методов SetX(), GetX() и SqrX() класса MyLibClass из DLL напрямую – по имени, через таблицу адресов виртуальных функций vtable;

- для работы с глобальной переменной iMyLibVar из DLL вначале объявляется указатель piMyLibVar, а затем производится получение её адреса с помощью функции GetProcAddress(). После этого переменной iMyLibVar из DLL через указатель piMyLibVar присваивается значение свойства х объекта *pDynObj класса MyLibClass путем вызова виртуального метода GetX(). В результате вызова виртуального метода SqrX() этого класса локальной переменной iSqrVar присваивается значение квадрата свойства x;

- с помощью функции sprintf() в массиве cBuffer производится формирование строки с полученными результатами, после чего функция MyLibFunc() через указатель pMyLibFunc выводит эту строку в диалоговом окне;

- объявляется указатель pDeleteDynObj на функцию DeleteDynObj() из DLL и производится получение её адреса с помощью функции GetProcAddress(). Затем уничтожается динамический объект *pDynObj класса MyLibClass вызовом функции DeleteDynObj() из DLL через её указатель pDeleteDynObj с передачей ей указателя на этот объект pDynObj;

- производится явная выгрузка библиотеки MyLib.dll из адресного пространства процесса с помощью функции FreeLibrary().

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

а) из каталога проекта библиотеки …\MyLib скопировать в каталог проекта приложения …\MyLibTest2 заголовочный файл этой DLL – MyLib.h;

б) из подкаталога проекта библиотеки MyLib.dll …\MyLib\Debug скопировать в подкаталог проекта приложения …\MyLibTest2\Debug файл динамически подключаемой библиотеки MyLib.dll.

В результате компиляции и компоновки будет сформирован файл приложения MyLibTest2.exe, которое в процессе своего выполнения явно подключит библиотеку MyLib.dll и вызовет требуемые функции из этой DLL.

 

 

Тема: МЕЖПРОЦЕССНОЕ ВЗАИМОДЕЙСТВИЕ ( IPC )

 

1. Общие сведения о межпроцессном взаимодействии

ОС Windows содержит средства для обеспечения взаимодействия и разделения данных между приложениями. Действия, реализуемые этими средствами, называются межпроцессным взаимодействием (IPC, InterProcess Communication).

Приложения могут использовать IPC в качестве клиентов или серверов. Клиент - это приложение, которое запрашивает сервис у другого приложения. Сервер - это приложение, которое отвечает на клиентский запрос. Некоторые приложения могут действовать, как клиент, так и сервер, в зависимости от ситуации.

Windows поддерживает следующие средства межпроцессного взаимодействия (IPC):

- передача данных с использованием сообщений;

- общесистемный буфер обмена ( Clipboard );

- динамический обмен данными (DDE);

- проекции файлов в память ( File mapping );

- каналы ( Pipes );

- почтовые ячейки ( Mailslots );

- удаленный вызов функций (RPC);

- сокеты Windows (Windows Sockets);

- сервисы объектов COM.

Рассмотрим некоторые из этих средств IPC.

2. Передача данных с помощью сообщений

Это средство IPC предоставляет возможность одному приложению передавать данные другому приложению, используя сообщения WM_SETTEXT и WM_COPYDATA.

2.1. Сообщение WM_SETTEXT

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

- wParam не используется;

- lParam является указателем строки, содержащей текст, передаваемый другому окну.

В процессе-отправителе для передачи строки окну другого процесса с использованием сообщения WM_SETTEXT необходимо выполнить следующее:

- с помощью функции Fi nd Window () получить дескриптор окна процесса-получателя;

- с помощью функции SendMessage() отправить окну процесса-получателя сообщение WM_SETTEXT, задав в нем в wParam значение 0, в lParam – указатель на строку, содержащую передаваемый текст.

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

- в обычных окнах (overlapped, pop-up, child) помещает полученный текст в заголовок окна;

- в окнах типа EditBox строка отображается в поле для редактирования;

- в окнах типа ListBox и ComboBox строка помещается в список этого окна, становится доступной для редактирования и выбора;

- в окнах типа Button строка отображается в виде надписи на кнопке.

При посылке сообщения WM_SETTEXT функцией SendMessage() производится копирование строки в проекцию страничного файла. Параметру l Param сообщения присваивается адрес копии строки в проекции файла. В оконной процедуре окна-приемника с помощью функции DefWindowProc() производится передача полученного текста окну, в зависимости от его типа, как отмечалось выше.

Пример 1 изменения заголовка главного окна приложения AppReceiver.exe с именем "WndRcv" путем посылки сообщения WM_SETTEXT с новым заголовком из другого приложения AppSender.exe.

Файл AppSender . cpp

// Получение дескриптора окна-приемника

HWND hWndRcv = FindWindow("WndRcv", NULL);

if(!hWndRcv)

{

MessageBox(hWnd, "Окно WndRcv не найдено", "Info", MB_OK); return 0;

}

// Изменение заголовка окна процесса-получателя

SendMessage(hWndRcv, WM_SETTEXT, (WPARAM) 0, (LPARAM) "New title of WndRcv");

2.2. Сообщение WM _ COPYDATA

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

- wParam представляет собой описатель окна, посылающего данное сообщение;

- lParam является указателем на структуру типа COPYDATASTRUCT, которая содержит данные, передаваемые другому процессу.

Структура COPYDATASTRUCT определена в заголовочном файле winuser.h и имеет следующий вид:

typedef struct tagCOPYDATASTRUCT {

ULONG_PTR dwData;

DWORD cbData;

PVOID lpData;

} COPYDATASTRUCT, *PCOPYDATASTRUCT;

Назначение полей структуры:

- dwData может содержать дополнительную информацию, например тип передаваемых данных;

- cbData задает размер блока передаваемых данных в байтах;

- lpData является указателем на блок передаваемых данных.

В процессе-отправителе для передачи данных другому процессу c использованием сообщения WM_COPYDATA необходимо выполнить следующее:

- с помощью функции Fi nd Window () получить дескриптор окна процесса-получателя;

- описать структуру типа COPYDATASTRUCT, заполнить её поля, указав размеры и адрес передаваемого блока данных;

- с помощью функции SendMessage() отправить окну процесса-получателя сообщение WM_COPYDATA, задав в wParam дескриптор окна–отправителя, в lParam - указатель на структуру типа COPYDATASTRUCT.

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

Посылая сообщение WM_COPYDATA, функция SendMessage() создает проекцию страничного файла размером cbData байтов, копирует в неё данные и переходит в режим ожидания. После того, как окно-приемник получит данные через структуру типа COPYDATASTRUCT и возвратит значение TRUE, SendMessage() выходит из режима ожидания и процесс-отправитель продолжает свою работу.

Пример 2 передачи с использованием сообщения WM_COPYDATA массива из 10000 случайных целых чисел между приложением-отправителем AppSender.exe и приложением-получателем AppReceiver.exe с главным окном "WndRcv".

а) фрагмент исходного кода приложения-отправителя AppSender.exe:

Файл AppSender . cpp

int iSendArray[10000]; // Пересылаемый массив

// Получение дескриптора окна-приемника

HWND hWndRcv = FindWindow("WndRcv", NULL);

if(!hWndRcv)

{

MessageBox(hWnd, "Окно WndRcv не найдено", "Info", MB_OK); return 0;

}

// Заполнение массива случайными целыми числами

for(int i=0; i<l0000; i++)   

iSendArray[i] = rand();

// Передача данных в другой процесс

COPYDATASTRUCT CDS;

CDS.dwData = 0;

CDS.cbData = sizeof(iSendArray);

CDS.lpData = (LPVOID) iSendArray;

SendMessage(hWndRcv, WM_COPYDATA, (WPARAM) hWnd, (LPARAM) &CDS);

б) фрагмент исходного кода приложения-получателя AppReceiver.exe:

Файл AppReceiver . cpp

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,

 WPARAM wParam, LPARAM lParam)

{

COPYDATASTRUCT *pCDS;

DWORD dwCount;

HANDLE hFile;

// Обработка сообщения для окна

switch (uMsg)

{

   // Обработка сообщения WM_COPYDATA

   case WM_COPYDATA:

// Получение адреса стр-ры COPYDATASTRUCT

pCDS = (COPYDATASTRUCT *) lParam;

// Создание файла

hFile = CreateFile("iArray.dat", GENERIC_

WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

// Запись данных в файл

WriteFile(hFile, pCDS->lpData, pCDS->cbData,

         &dwCount, NULL);

CloseHandle(hFile); // Закрытие файла

// Возврат TRUE для функции SendMessage()             

return TRUE;

   … // Обработка других сообщений

   // Уничтожение окна

   case WM_DESTROY:

          PostQuitMessage (0);

          return 0;

}

return DefWindowProc (hWnd, uMsg, wParam, lParam);

}

3. Буфер обмена ( Clipboard )

Буфер обмена - это набор функций и сообщений Windows, который позволяет сохранять данные в памяти и передавать их между окнами одного или разных приложений. Операции с буфером обмена могут выполняться программно или по инициативе пользователя при выборе им из меню "Edit" пунктов "Cut", "Copy" или "Paste".

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

3.1. Форматы данных для буфера обмена

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

Имеется несколько видов форматов данных буфера обмена: а) стандартные форматы; б) регистрируемые форматы; в) частные форматы.

Стандартные форматы данных буфера обмена

Они являются зарегистрированными в Windows форматами буфера обмена и обозначаются в виде символьных констант, определённых в заголовочном файле Winuser.h. Наиболее часто используемыми из них являются:

- CF_TEXT – группа символов из кодовой таблицы ASCII заканчивающаяся нуль-символом, в конце каждой строки которой имеются символы возврата каретки и перевода строки;

- CF_BITMAP – зависящий от устройства битовый образ, который передается в буфер обмена с помощью описателя битового образа HBITMAP;

- CF_ENHMETAFILE – формат для расширенного метафайла, содержащего несколько новых функций, структур данных и новое расширение файла - EMF по сравнению со стандартным WMF-метафайлом.

Регистрируемые форматы буфера обмена

Они используются для передачи через буфер обмена различных объектов данных, с форматами, отличными от стандартных. Регистрируемым форматам присваиваются идентификаторы в виде целых значений в диапазоне от 0xC000 до 0xFFFF.

Для регистрации нового формата буфера обмена используется функция RegisterClipboardFormat().

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

Частные форматы данных буфера обмена

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

- от CF_PRIVATEFIRST (0x0200) до CF_PRIVATELAST (0x02FF).

- от CF_GDIOBJFIRST (0x0300) до CF_GDIOBJLAST (0x03FF) для объектов типа GDI (Graphics Device Interface).

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

3.2. Открытие, очистка и закрытие буфера обмена

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

Открытие буфера обмена выполняет следующая функция:

BOOL OpenClipboard(HWND hWndNewOwner);

Параметр hWndNewOwner является описателем окна, которое станет новым владельцем буфера обмена. Если этот параметр равен NULL, то владельцем буфера обмена будет текущий процесс, а не одно из его окон.

Окно с описателем hWndNewOwner становится владельцем буфера обмена после того, как вызовет функцию EmptyClipboard (), которая производит его очистку.

Функция возвращает значение: TRUE, если буфер обмена успешно открыт, или FALSE, когда буфером обмена владеет другое окно и держит его открытым.

Очистка содержимого буфера обмена выполняется функцией:

BOOL EmptyClipboard(VOID);

Она не имеет параметров и возвращает значение TRUE, если буфер обмена успешно очищен, или FALSE, в противном случае.

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

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

BOOL CloseClipboard(VOID);

Функция не имеет параметров и возвращает значение TRUE, если буфер обмена успешно закрыт, или FALSE, в противном случае. После закрытия буфера обмена окно уже не может помещать в него данные.

Пример открытия, очистки и закрытия буфера обмена окном с описателем hWnd:

if (!OpenClipboard(hWnd))

   return FALSE;

EmptyClipboard();

… // Передача данных в буфер обмена

CloseClipboard();

3.3. Передача и получение данных через буфер обмена

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

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

HANDLE SetClipboardData(UINT uFormat, HANDLE hMem);

Параметры:

- uFormat задает формат данных, передаваемых в буфер обмена.

- hMem является описателем блока памяти, где размещены передаваемые данные в указанном формате. Если он равен NULL, то данные будут переданы в буфер обмена позже, по запросу окна-получателя (отложенная передача данных буферу обмена).

Блок памяти для данных должен быть выделен в куче функцией GlobalAlloc () c флагом GHND, комбинирующим флаги GMEM_MOVEABLE и GMEM_ZEROINIT.

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

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

HANDLE GetClipboardData(UINT uFormat);

Параметр uFormat задает требуемый формат данных в буфере обмена.

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

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

3.4. Отложенная передача данных буферу обмена

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

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

При этом ОС посылает окну-владельцу буфера обмена 2 сообщения:

- WM_RENDERFORMAT, поле wParam указывает формат данных, которые должны быть переданы;

- WM_RENDERALLFORMATS перед закрытием окна-владельца буфера обмена, чтобы оно передало в буфер обмена все отложенные данные.

Для реализации отложенной передачи данных буферу обмена в приложении-отправителе необходимо:

1) при вызовах функции SetClipboardData() для первого параметра uFormat указывать конкретный формат передаваемых данных, а для второго параметра hMem задавать значение NULL.

Пример отложенной передачи буферу обмена текстовых данных и битового образа:

OpenClipboard(hWnd);

EmptyClipboard();

SetClipboardData(CF_TEXT, NULL);

SetClipboardData(CF_BITMAP, NULL);

CloseClipboard();

2) при обработке сообщения WM_RENDERFORMAT получить описатель области памяти с передаваемыми данными в формате типа wParam и, не открывая буфер обмена, вызвать функцию SetClipboardData(), задав ей эти формат и описатель области памяти.

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

Для уменьшения объема программного кода и устранения дублирования его частей можно совместить обработку сообщений WM_RENDERALLFORMATS и WM_RENDERFORMAT:

case WM_RENDERALLFORMATS:

OpenClipboard(hWnd);

EmptyClipboard();

case WM_RENDERFORMAT:

if((wParam == CF_TEXT) || (uMsg == WM_RENDERALLFORMATS))

{

… // Копирование текста в область памяти

// с описателем hGMemText

SetClipboardData (CF_TEXT, hGMemText);

}

if((wParam == CF_BITMAP) || (uMsg == WM_RENDERALLFORMATS))

{

… // Загрузка в память битового образа и

// получение его описателя hBitmap

SetClipboardData (CF_BITMAP, hBitmap);

}

if(uMsg == WM_RENDERALLFORMATS)  

CloseClipboard();

return 0;

3.5. Уведомление об изменениях буфера обмена

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

Одновременно может быть несколько окон-наблюдателей за буфером обмена, из них только одно окно, которое зарегистрировалось последним, называется текущим наблюдателем за буфером обмена (Current Clipboard Viewer). Этому окну ОС посылает сообщение при изменении содержимого буфера обмена.

Несколько окон, которым необходимо получать уведомления от буфера обмена, образуют цепочку окон-наблюдателей за буфером обмена (Clipboard Viewer Chain). При этом все окна цепочки по очереди получают сообщения, которые Windows отправляет текущему окну-наблюдателю.

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

Для организации наблюдения за буфером обмена в приложении необходимо:

- добавить своё окно в цепочку наблюдателей за буфером обмена;

- обрабатывать сообщение WM_DRAWCLIPBOARD при изменении содержимого буфера обмена;

- удалить окно из цепочки наблюдателей за буфером обмена до уничтожения этого окна;

- обрабатывать сообщение WM_CHANGECBCHAIN при удалении другого окна из цепочки наблюдателей за буфером обмена.

1.1.2 3.5.1.Добавление окна в цепочку наблюдателей за буфером обмена

1.1.3 Окно добавляется в цепочку наблюдателей за буфером обмена и становится текущим наблюдателем с помощью функции:

HWND SetClipboardViewer(HWND hWndNewViewer);

1.1.4 Параметр hWndNewViewer является описателем окна, которое добавляется в цепочку наблюдателей за буфером обмена. Возвращаемым значением функции является описатель окна предыдущего текущего наблюдателя за буфером обмена. В программе необходимо сохранить этот описатель в статической переменной, например, с именем h W ndNextViewer .

3.5.2. Обработка сообщения WM_DRAWCLIPBOARD

При обработке этого сообщения в оконной функции необходимо:

- с помощью функции SendMessage() передать это сообщение следующему окну в цепочке наблюдателей за буфером обмена. Если это последнее окно цепочки, для которого значение h W ndNextViewer будет равно NULL, то пересылка сообщения не производится;

- определить наличие в буфере обмена данных в требуемом формате, извлечь их и далее либо отобразить в окне, либо обработать.

3.5.3. Удаление окна из цепочки наблюдателей за буфером обмена

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

BOOL ChangeClipboardChain(HWND hWndRemove, HWND hWndNewNext);

Параметрами функции являются описатель окна, удаляемого из цепочки hWndRemove, и описатель следующего окна просмотра буфера обмена hWndNewNext. Функция возвращает значение FALSE, когда окно успешно удалено из цепочки наблюдателей за буфером обмена, и значение TRUE, если в цепочке присутствует только одно окно и оно удалено.

Когда программа вызывает функцию ChangeClipboardChain(), Windows посылает сообщение WM_CHANGECBCHAIN текущему окну просмотра буфера обмена. Параметром wParam этого сообщения является описатель окна, удаляемого из цепочки, а параметром lParam – описатель следующего в цепочке окна просмотра буфера обмена.

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

3.5.4. Обработка сообщения WM _ CHANGECBCHAIN

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

 

4. Обмен данными между процессами через проекцию страничного файла

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

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

1. С помощью функции CreateFileMapping() создать в памяти именованный объект "проекция файла". При этом параметры функции должны получить следующие значения:

- в первом параметре hFile указывается значение INVALID_HANDLE_VALUE (равное -1). Это является признаком того, что будет создана проекция страничного файла, а не открытого файла на диске;

- в третьем параметре flProtect указывается необходимый атрибут защиты страниц физической памяти: PAGE_READONLY, PAGE_READWRITE или PAGE_WRITECOPY;

- в параметрах dwMaximumSizeHigh и dwMaximumSizeLow указывается объем выделяемой для проекции памяти, обязательно больший нуля;

- в последнем параметре lpName задаётся имя проекции в виде строки. Это имя будет использоваться другими процессами для открытия данной проекции.

Функция возвращает дескриптор объекта "проекция файла" hMapPage File, который исполь­зуется в том же процессе для взаимодействия с объектом.

2. Отобразить проекцию файла на своё адресное пространство с помощью функции MapViewOfFile(), которая возвращает указатель lpMapData на созданный регион памяти с отображе­нием проекции файла, и явно преобразовывать его в требуемый тип.

3. Работать с общими данными, находящимися в проекции страничного файла через разыменование указателя *lpMapData.

4. По окончании работы с этими данными процесс должен, используя функцию UnmapViewOfFile(), сделать недействительным указатель lpMapData и освободить соответствующий регион памяти в своем адресном пространстве. Затем с помощью функции CloseHandle() освободить дескриптор проекции hMapPage File.

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

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

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

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

ВНИМАНИЕ!

5. Использование каналов для передачи данных между процессами

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

Имеется два вида каналов (труб) для межпроцессной связи: анонимные каналы и именованные каналы.

5.1. Анонимные каналы ( A nonymous pipe s )

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

5.1.1. Функции для работы с анонимными каналами

Создает анонимный канал родительский процесс с помощью функции:

BOOL CreatePipe(PHANDLE phReadPipe, PHANDLE phWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize);

1.1.4.1 Параметры функции:

- phReadPipe , phWritePipe указывают на переменные, которые получают значения дескрипторов чтения и записи канала;

- lpPipeAttributes указывает на структуру типа SECURITY_ATTRIBUTES, которая определяет права доступа к каналу и наследуемость его дескрипторов. Член bInheritHandle этой структуры должен быть равным TRUE, тогда дескрипторы будут наследуемы;

- nSize указывает размер буфера для канала, в байтах. Если он равен 0, то используется размер буфера по умолчанию.

Возвращаемое значение будет равно TRUE, если функция выполнена успешно и FALSE – в противном случае.

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

HANDLE GetStdHandle(DWORD nStdHandle);

Параметр nStdHandle задает вид стандартного устройства для получения дескриптора и может принимать следующие значения:

- STD_INPUT_HANDLE для получения дескриптора устройства стандартного ввода;

- STD_OUTPUT_HANDLE для получения дескриптора устройства стандартного вывода;

- STD_ERROR_HANDLE для получения дескриптора стандартного устройства для вывода сообщений об ошибках.

При успешном завершении функция GetStdHandle() возвращает дескриптор указанного устройства, иначе возвращается значение INVALID_HANDLE_VALUE.

Выполнение операций чтения и записи данных с каналом. Для получения данных из канала используется дескриптор чтения канала при вызове функции ReadFile(). Вызов ReadFile() завершается успешно, когда другой процесс записал информацию в канал. Если дескриптор записи с другого конца канала закрыт, то функция ReadFile() возвращает значение FALSE.

Для передачи данных в канал используется дескриптор записи канала при вызове функции WriteFile(). Вызов WriteFile() не завершается, пока не будет записано указанное число байтов в канал. Функция WriteFile() возвращает значение FALSE, когда производится попытка записи в канал при закрытом дескрипторе чтения из канала с другой его стороны.

Закрытие дескриптор а канала сервером или клиентом производится с помощью функции CloseHandle().

5.1.2. Однонаправленная передача данных между процессами с использованием анонимного канала

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

Родительский процесс должен выполнить следующие действия:

- создать с помощью функции CreatePipe() анонимный канал, получив при этом наследуемые дескрипторы для чтения и записи данных с разных концов канала;

- перенаправить стандартный ввод или вывод дочернего процесса на канал, для чего необходимо очистить структуру STARTUPINFO и заполнить четыре её члена: hStdInput, hStdOutput , hStdError и dwFlags. Одному из членов этой структуры hStdInput или hStdOutput присваивается дескриптор канала для чтения hReadPipe или для записи hWritePipe . Остальные члены структуры STARTUPINFO получают стандартные дескрипторы с помощью функции GetStdHandle(), а dwFlags – значение STARTF_USESTDHANDLES;

- создать дочерний процесс с помощью функции CreateProcess(), передав ей адрес структуры STARTUPINFO и задав её параметру bInheritHandles значение TRUE;

- закрыть с помощью функции CloseHandle() дескриптор канала, переданный дочернему процессу;

- выполнять операции записи или чтения данных с каналом вызывая функции WriteFile() или ReadFile() с использованием действующего дескриптора канала;

- по окончании работы с каналом закрыть его дескриптор с помощью функции CloseHandle().

Дочерний процесс должен выполнить следующие действия:

- с помощью функции GetStdHandle() получить дескриптор чтения или записи канала, переданный родительским процессом в виде дескриптора устройства стандартного ввода или вывода;

- выполнять операции чтения или записи данных с каналом вызывая функции ReadFile() или WriteFile() с использованием полученного ранее дескриптора канала;

- по окончании работы с каналом закрыть его дескриптор с помощью функции CloseHandle().

ВНИМАНИЕ!

5.2. Именованные каналы ( Named pipes )

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

Имена таких каналов назначают по схеме: \\имя_машины\ріре\имя_канала. Для доступа к каналу на локальном компьютере вместо имени машины используется символ '.': \\.\ріре\имя_канала. Такие имена называют UNC-именами (Universal Naming Convention - универсальное соглашение для задания имен).

5.2.1. Функции для работы с именованными каналами

Создание именованного канала производится на локальном компьютере с помощью функции:

HANDLE CreateNamedPipe(LPCTSTR lpName,

DWORD dwOpenMode, DWORD dwPipeMode,

DWORD nMaxInstances, DWORD nOutBufferSize,

DWORD nInBufferSize, DWORD nDefaultTimeOut,

LPSECURITY_ATTRIBUTES lpSecurityAttributes);

Параметры функции:

- lpName обозначает имя канала в форме: \\.\ріре\имя_канала. Создавать именованные каналы на удаленных машинах нельзя;

- dwOpenMode задает режим открытия канала: PIPE_ACCESS_DUPLEX – двухсторонняя (дуплексная) передача данных, PIPE_ACCESS_INBOUND – передача от клиента к серверу, PIPE_ACCESS_OUTBOUND – передача от сервера к клиенту и др.;

- dwPipeMode задает режим обмена данными с каналом: PIPE_TYPE_BYTE, PIPE_READMODE_BYTE – побайтная запись и чтение данных, PIPE_WAIT – ожидание освобождения канала или подключения клиента и др. Если он равен 0, то эти режимы будут по умолчанию;

- nMaxInstances задает максимальное количество экземпляров канала (клиентских соединений). Неограниченное количество задают как PIPE_UNLIMITED_INSTANCES;

- nOutBufferSize, nInBufferSize задают размеры в байтах буферов ввода и вывода для канала. Если указать нули, будут использоваться значения по умолчанию

- nDefaultTimeOut определяет время ожидания клиентом доступа к каналу: по умолчанию –NMPWAIT_USE_DEFAULT_WAIT, без ограничений – NMPWAIT_WAIT_FOREVER;

- lpSecurityAttributes указывает на структуру типа SECURITY_ATTRIBUTES, которая определяет права доступа к каналу и наследуемость его дескрипторов.

Функция CreateNamedPipe () при успешном выполнении возвращает дескриптор для сервера канала, иначе – значение INVALID_HANDLE_VALUE.

Для ожидания сервером подключения клиента к именованному каналу используется функция:

BOOL ConnectNamedPipe(HANDLE hNamedPipe,

                        LPOVERLAPPED lpOverlapped);

Параметр hNamedPipe является дескриптором сервера канала, полученный при вызове функции CreateNamedPipe ().

Параметр lpOverlapped указывает на структуру типа OVERLAPPED, которая содержит информацию для асинхронного ввода-вывода. Этот параметр используется, если при создании канала был разрешен режим асинхронного ввода-вывода, иначе он получает значение NULL.

После подключения клиента к каналу функция завершает ожидание и возвращает TRUE. Если клиент подключается до вызова функции, то она возвращает FALSE.

Отключение сервером экземпляра канала от клиента производится с помощью функции:

BOOL DisconnectNamedPipe(HANDLE hNamedPipe);

Сервер канала вызывает эту функцию, чтобы освободить дескриптор (экземпляр канала) hNamedPipe для соединения с другим клиентом.

При успешном отключении функция возвращает TRUE, иначе - FALSE.

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

BOOL WaitNamedPipe(LPCTSTR lpNamedPipeName,

                                DWORD nTimeOut);

Параметр lpNamedPipeName указывает на строку, содержащую имя канала в виде: \\имя_сервера\ріре\имя_канала.

Параметр nTimeOut задает время ожидания в мсек. Неограниченное время ожидания задается значением NMPWAIT_WAIT_FOREVER.

Если канал стал доступен клиенту до истечения времени ожидания, функция возвращает TRUE, иначе - FALSE.

Установление соединения клиента с каналом производится с помощью функции:

HANDLE CreateFile(LPCTSTR lpNamedPipeName, …);

В первом параметре указывается имя канала в виде: \\имя_сервера\ріре\имя_канала. Остальные параметры задаются, как и для дисковых файлов.

Функция возвращает дескриптор канала для клиента. В случае неудачной попытки соединения возвращается значение INVALID_HANDLE_VALUE.

Операции чтения и записи данных с каналом выполняются сервером и клиентом с помощью своих дескрипторов канала путем вызова функций ReadFile() и WriteFile().

Закрытие дескрипторов канала на стороне сервера и клиента производится с помощью функции CloseHandle().

 

6. Использование почтов ых ячеек для передачи данных между процессами

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

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

Сообщения (данные), передаваемые на все почтовые ячейки в сети, могут быть не больше, чем 400 байтов, тогда как сообщения, отправляемые отдельной почтовой ячейке, ограничены только максимальным размером сообщения, указанным mailslot-сервером при создании этой почтовой ячейки.

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

6.1. Функции для работы с почтовыми ячейками

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

HANDLE CreateMailslot(LPCTSTR lpName,

DWORD nMaxMessageSize, DWORD lReadTimeout,

LPSECURITY_ATTRIBUTES lpSecurityAttributes);

Параметры функции:

- lpName указывает на строку, содержащую имя почтовой ячейки, создаваемой на локальном компьютере в виде: \\.\mailslot\имя_ячейки;

- nMaxMessageSize задает максимальный размер (в байтах) сообщений, которые может отправ­лять клиент. При значении 0 ограничения на размер сообщений нет;

- lReadTimeout определяет длительность (в миллисекундах) ожидания операции чтения. При значении 0 произойдет немедленный возврат, a при MAILSLOT_WAIT_FOREVER — бесконеч­ное ожидание;

- lpSecurityAttributes указывает на структуру типа SECURITY_ATTRIBUTES. Обычно этому параметру задают значение NULL.

Функция CreateMailslot () при успешном выполнении возвращает дескриптор для сервера почтовой ячейки, иначе – значение INVALID_HANDLE_VALUE.

Чтение сервером сообщения из почтовой ячейки производится с помощью функции:

BOOL ReadFile(HANDLE hMailslot, …);

Параметр hMailslot указывает дескриптор почтовой ячейки, полученный функцией CreateMailslot (). Остальные параметры задаются, как и при чтении из дисковых файлов.

Функция ReadFile () при успешном чтении сообщения возвращает TRUE и FALSE – когда выполнена попытка чтения из почтовой ячейки, буфер которой недостаточен для размещения сообщения.

Открытие клиентом почтовой ячейки производится с помощью функции:

HANDLE CreateFile(LPCTSTR lpMailslotName, …);

В первом параметре можно использовать следующие формы задания имени почтовой ячейки:

а) \\.\mailslot\имя_ячейки, когда сервер и клиент расположены на одном компьютере;

б) \\имя_сервера\mailslot\имя_ячейки, когда сервер и клиент расположены на разных компьютерах в сети;

в) \\*\mailslot\имя_ячейки, когда необходимо посылать по сети сообщения всем серверам почтовых ячеек, имеющих данное имя. В этом случае максимальная длина сообщения составляет 400 байт.

Остальные параметры функции CreateFile () задаются, как и для дисковых файлов.

Функция возвращает дескриптор почтовой ячейки для клиента. В случае неудачной попытки открытия возвращается значение INVALID_HANDLE_VALUE.

Запись клиентом сообщения в почтовую ячейку производится с помощью функции:

BOOL WriteFile(HANDLE hMailslot, …);

Параметр hMailslot указывает дескриптор почтовой ячейки, полученный функцией CreateFile (). Остальные параметры задаются, как и при записи на дисковые файлы.

Функция WriteFile() при успешной записи сообщения возвращает TRUE и FALSE – в противном случае.

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

 

3.3.1. Особенности передачи и получения из буфера обмена данных стандартных форматов (текст, битовый образ, расширенный метафайл)

 

3.3.1.1. Передача и получение текстовых данных

Для передачи текстовых данных в буфер обмена необходимо:

- используя функцию GlobalAlloc() с флагом GHND, выделить перемещаемый блок памяти требуемого размера с учетом нуль-символа;

- с помощью функции GlobalLock() зафиксировать выделенный блок и получить его указатель;

- скопировать исходный текст в зафиксированную область памяти с использованием функции CopyMemory() или другим способом;

- с помощью функции GlobalUn l ock() снять фиксацию с этой области памяти;

- открыть и очистить буфер обмена с помощью функций OpenClipboard() и EmptyClipboard ();

- передать буферу обмена описатель блока памяти с текстом с помощью функции SetClipboardData () в формате CF_TEXT и закрыть буфер обмена.

Пример 4 передачи в буфер обмена символьной строки с указателем pString и длиной iLength:

char *pString = "Символьная строка …";

int iLength = strlen(pString);

// Выделение памяти и копирование в неё строки

HGLOBAL hGlobalMemory = GlobalAlloc(GHND, iLength + 1);

char *pGlobMem=(char *) GlobalLock(hGlobalMemory);

CopyMemory(pGlobMem, pString, iLength+1);

GlobalUnlock(hGlobMem);

// Помещение строки в буфер обмена

OpenClipboard(hWnd);

EmptyClipboard();

SetClipboardData(CF_TEXT, hGlobMem);

CloseClipboard();

Для получения текстовых данных из буфера обмена необходимо:

- проверить с помощью функции IsClipboardFormatAvailable(), содержатся ли в буфере обмена данные в формате CF_TEXT;

- если да, то открыть его и с помощью функции GetClipboardData() получить описатель области памяти буфера обмена, содержащей текст;

- выделить в куче блок памяти требуемого размера для размещения текста. Это можно сделать с помощью оператора new или функцией HeapAlloc ();

- с помощью функции GlobalLock() зафиксировать область памяти буфера обмена и получить её указатель;

- скопировать текст из буфера обмена в выделенный блок памяти с использованием функции CopyMemory() или другим способом;

- с помощью функции GlobalUn l ock() снять фиксацию с области памяти буфера обмена и закрыть его, вызвав функцию CloseClipboard().

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

Пример 5 получения текстовых данных из буфера обмена:

if(IsClipboardFormatAvailable (CF_TEXT))

{

OpenClipboard(hWnd);

HGLOBAL hClipMemory=GetClipboardData(CF_TEXT);

UINT uTextLength = GlobalSize(hClipMemory);

char *pMyCopy = new char [uTextLength];

pClipMemory = GlobalLock(hClipMemory);

CopyMemory(pMyCopy, pClipMemory, uTextLength);

GlobalUnlock(hClipMemory);

CloseClipboard();

}

3.3.1.2. Передача через буфер обмена битовых образов

Для передачи битового образа в буфер обмена необходимо:

- получить описатель зависящего от устройства битового образа с помощью функции LoadBitmap ();

- открыть и очистить буфер обмена с помощью функций OpenClipboard() и EmptyClipboard ();

- передать буферу обмена описатель битового образа функцией SetClipboardData () в формате CF_BITMAP и закрыть буфер обмена.

Пример 6 передачи в буфер обмена битового образа, загруженного из ресурсов приложения:

HBITMAP hBitmap = LoadBitmap(hInst, "Bitmap");

OpenClipboard(hWnd);

EmptyClipboard();

SetClipboardData(CF_BITMAP, hBitmap);

CloseClipboard();

Для получения битового образа из буфера обмена необходимо:

- открыть буфер обмена и с помощью функции GetClipboardData() получить описатель битового образа в буфере обмена и проверить его;

- если он равен NULL, то в буфере обмена отсутствуют данные в формате CF_BITMAP и его следует закрыть, ничего не делая;

- определить характеристики битового образа в буфере обмена с помощью функции GetObject() и выведести в окно с помощью функции BitBlt();

- закрыть буфер обмена.

Можно также создать копию этого битового образа с помощью функции CreateBitmapIndirect() и затем использовать в приложении.

Пример 7 получения из буфера обмена битового образа и вывода его в окно:

OpenClipboard (hWnd);

HBITMAP hGBitmap = GetClipboardData (CF_BITMAP);

if(hGBitmap == NULL)

{

CloseClipboard();

return FALSE;

}

// Вывод битового образа в окно

BITMAP bm;

GetObject(hGBitmap, sizeof(BITMAP), (PBITMAP) &bm);

HDC hDC = GetDC(hWnd);

HDC hdcMyBitmap = CreateCompatibleDC(hDC);

SelectObject(hdcMyBitmap, hGBitmap);

BitBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hdcMyBitmap, 0, 0, SRCCOPY);

DeleteDC(hdcMyBitmap);

ReleaseDC(hWnd, hDC);

CloseClipboard();

 

3.3.1.3. Передача через буфер обмена расширенного метафайла

Для помещения расширенного метафайла в буфер обмена необходимо:

- получить описатель требуемого расширенного метафайла с помощью функции GetEnhMetaFile();

- открыть и очистить буфер обмена с помощью функций OpenClipboard() и EmptyClipboard ();

- передать буферу обмена описатель расширенного метафайла функцией SetClipboardData () в формате CF_ENHMETAFILE и закрыть буфер обмена.

Пример 8 помещения в буфер обмена расширенного метафайла, загруженного из файла с именем MyEMF.emf:

HENHMETAFILE hEMF=GetEnhMetaFile("MyEMF.emf");

OpenClipboard(hWnd);

EmptyClipboard();

SetClipboardData(CF_ENHMETAFILE, hEMF);

CloseClipboard();

Для получения расширенного метафайла из буфера обмена в приложении необходимо:

- проверить с помощью функции IsClipboardFormatAvailable(), содержатся ли в буфере обмена данные в формате CF_ENHMETAFILE;

- открыть буфер обмена с помощью функции GetClipboardData() и получить описатель области памяти буфера обмена, содержащей расширенный метафайл;

- получить дескриптор контекста устройства отображения hDC с помощью функции GetDC ();

- воспроизвести в окне полученный из буфера обмена расширенный метафайл с помощью функции PlayEnhMetaFile();

- освободить дескриптор контекста устройства отображения hDC с помощью функции ReleaseDC ();

- закрыть буфер обмена, вызвав функцию CloseClipboard().

Пример 9 получения расширенного метафайла из буфера обмена и воспроизведения его в окне:

HDC hDC;

HENHMETAFILE hClipEMF;

RECT rect;

if(IsClipboardFormatAvailable(CF_ENHMETAFILE))

{

OpenClipboard(hWnd);

// Получение описателя в буфере обмена

hClipEMF = GetClipboardData(CF_ENHMETAFILE);

// Получение дескриптора контекста отображения

hDC = GetDC(hWnd);

GetClientRect(hWnd, &rect);

// Воспроизведение в окне

PlayEnhMetaFile(hDC, hCopyEMF, &rect);

ReleaseDC(hWnd, hDC);

CloseClipboard();

}

 

 

3.3.2. Передача через буфер обмена нескольких объектов данных

Окно приложения может поместить в буфер обмена несколько объектов данных, имеющих различные типы форматов. Сделать это можно путем нескольких вызовов функции SetClipboardData() с разными форматами данных в одном сеансе открытия-закрытия буфера обмена.

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

OpenClipboard(hWnd);

EmptyClipboard();

SetClipboardData(CF_TEXT, hGMemText);

SetClipboardData(CF_BITMAP, hBitmap);

SetClipboardData(CF_ENHMETAFILE, hEMF);

CloseClipboard();

После этого при вызовах функции IsClipboardFormatAvailable() с аргументами CF_TEXT, CF_BITMAP или CF_ENHMETAFILE возвращаемым значением будет TRUE.

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

OpenClipboard(hWnd);

hGMemText = GetClipboardData(CF_TEXT) ;

… // Копирование текста из буфера обмена

HBITMAP hGBitmap = GetClipboardData(CF_BITMAP) ;

… // Копирование битового образа из буфера обмена

HENHMETAFILE hGEMF = GetClipboardData(CF_ENHMETAFILE) ;

… // Копирование расширенного метафайла из буфера обмена

CloseClipboard();

Когда какое-либо приложение откроет буфер обмена и вызовет функцию Em p tyClipboard(), Windows освободит и удалит все три описателя, связанные с этими данными.

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

UINT EnumClipboardFormats(UINT format);

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

Определить все хранящиеся в буфере обмена форматы данных, после его открытия, можно выполнив серию последовательных вызовов функции EnumClipboardFormats(). Для этого начальное значение параметра format должно быть равным 0. Затем в цикле производятся вызовы этой функции, в качестве параметра ей задается формат, возвращенный функцией на предыдущем шаге. Цикл завершит работу, когда будут перечислены все хранящиеся в буфере обмена форматы данных и функция EnumClipboardFormats() возвратит 0.

Например, для определения трех типов форматов данных, находящихся в буфере обмена, и их обработки необходимо записать следующее:

UINT uFormat = 0;

OpenClipboard (hWnd);

while (uFormat = EnumClipboardFormats(uFormat))

{

switch(uFormat)

{

case CF_TEXT:

… // получение и обработка данных в формате CF_TEXT

break;

case CF_BITMAP:

… // получение и обработка данных в формате CF_BITMAP

break;

case CF_ENHMETAFILE:

… // получение и обработка данных в формате CF_ENHMETAFILE

break;

default:

… // обработка ситуации, когда данных в требуемых форматах нет

}

CloseClipboard();

Количество находящихся в данный момент в буфере обмена форматов можно получить с помощью вызова функции CountClipboardFormats():

UINT uCount = CountClipboardFormats();

Таким образом, через буфер обмена можно одновременно передавать несколько объектов данных, имеющих различные типы форматов.

 

 


Дополнительные материалы

к п. 3.5. Уведомление об изменениях буфера обмена

 

3.5.5. Пример окна-наблюдателя за буфером обмена

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

LRESULT CALLBACK WndProc (HWND hWnd, UINT

uMsg, WPARAM wParam, LPARAM lParam)

{

static HWND hWndNextViewer;

HGLOBAL hGMem;

HDC         hDC, hDCMem;

PSTR       pGMem;

PAINTSTRUCT ps;

RECT      rect;

UINT     uFormat;

switch (uMsg)

     {

     case WM_CREATE:

          // Регистрация окна наблюдателем за буфером обмена

          hWndNextViewer = SetClipboardViewer(hWnd);

          return 0;

    case WM_CHANGECBCHAIN:

          // Удаление из цепочки окна, следующего за данным окном

          if ((HWND) wParam == hWndNextViewer)

               hWndNextViewer = (HWND) lParam;

          else if (hWndNextViewer != NULL)

              SendMessage(hWndNextViewer, uMsg, wParam, lParam);

          return 0;

     case WM_DRAWCLIPBOARD:

          // Уведомление окна об изменении содержимого буфера обмена

          if (hWndNextViewer)

               SendMessage(hWndNextViewer, uMsg, wParam, lParam);

          InvalidateRect(hWnd, NULL, TRUE);

          return 0;

     case WM_PAINT:

          hDC = BeginPaint (hWnd, &ps);

          GetClientRect (hWnd, &rect);

          // Извлечение из буфера обмена и отображение данных в разных форматах

          OpenClipboard (hWnd);

          uFormat = 0;

          while (uFormat = EnumClipboardFormats(uFormat))

          {

            switch(uFormat)

            {

              case CF_TEXT:

                // Получение и отображение данных в формате CF_TEXT

                hGMem = GetClipboardData(CF_TEXT);

                pGMem = (PSTR) GlobalLock(hGMem);

                DrawText(hDC, pGMem, -1, &rect, DT_CENTER);

                GlobalUnlock(hGMem);

                break;

              case CF_BITMAP:

                // Получение и отображение данных в формате CF_BITMAP

                hDCMem = CreateCompatibleDC(hDC);

                hGMem = (HBITMAP) GetClipboardData(CF_BITMAP);

                SelectObject(hDCMem, hGMem);

                BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY);

                DeleteDC(hDCMem);

                break;

              case CF_ENHMETAFILE:

                // Получение и отображение данных в формате CF_ENHMETAFILE

                hGMem = GetClipboardData(CF_ENHMETAFILE);

                PlayEnhMetaFile(hDC, hGMem, &rect);

            }

          }

          CloseClipboard();

          EndPaint(hWnd, &ps);

          return 0;

     case WM_DESTROY:

          // Окно удаляет себя из цепочки наблюдателей за буфером обмена

          ChangeClipboardChain(hWnd, hWndNextViewer);

          PostQuitMessage(0);

          return 0;

     }

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

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

 

Обмен данными между процессами через проекцию страничного файла

 

Пример использования проекции страничного файла для передачи массива из 1000 случайных целых чисел между двумя приложениями App1.exe и App2.exe с использованием в качестве средства синхронизации именованного объекта "Событие" (Event) с автосбросом:

а) первое приложение App1.exe создает объект синхронизации типа Event с именем MyEvent в занятом состоянии, затем создает проекцию страничного файла требуемого размера, получает указатель на неё и заполняет случайными целыми числами. После этого освобождает объект синхронизации, тем самым дает сигнал второму приложению о том, что массив в проекции сформирован.

Файл App 1. cpp

HANDLE hEvent; // Дескриптор объекта "Событие"

HANDLE hMapPageFile; // Дескриптор проекции страничного файла

INT *piMapArray; // Указатель на массив целых чисел в проекции файла

INT iMapSize=1000*sizeof(INT); // Размер проекции в байтах

// Создание именованного объекта "Событие" в занятом состоянии

hEvent=CreateEvent(NULL, FALSE, FALSE, "MyEvent");

// Создание именованной проекции страничного файла

hMapPageFile=CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, iMapSize, "MapOfPageFile");

// Отображение проекции файла на адресное пространство процесса

piMapArray = (INT *) MapViewOfFile(hMapPageFile, FILE_MAP_ALL_ACCESS, 0, 0, iMapSize);

// Заполнение массива случайными числами

for (INT i=0; i<1000; i++)

piMapArray[i]=rand();

// Освобождение объекта "Событие"

SetEvent(hEvent);

// Завершение работы с проекцией страничного файла

UnmapViewOfFile(piMapArray); // Освобождение указателя на проекцию файла

CloseHandle(hMapPageFile); // Закрытие дескриптора проекции файла

б) второе приложение App2.exe открывает именованный объект синхронизации типа Event с тем же именем MyEvent, затем открывает проекцию страничного файла требуемого размера, получает указатель на неё, создает новый поток, потоковая функция которого ожидает освобождения объекта типа Event. При освобождении этого объекта в первом приложении указанная функция производит обработку массива в проекции страничного файла – подсчитывает количество четных по значению элементов массива и выводит его в диалоговом окне.

Файл App 2. cpp

HANDLE hEvent; // Дескриптор объекта "Событие"

HANDLE hMapPageFile; // Дескриптор проекции страничного файла

HANDLE hThread; // Дескриптор потока

DWORD ThreadId; // Идентификатор потока

INT *piMapArray; // Указатель на массив целых чисел в проекции файла

INT iMapSize=1000*sizeof(INT); // Размер проекции в байтах

// Функция потока

DWORD WINAPI ThreadFunc(PVOID pvParam)

{

// Получение адреса управляющей структуры потока

struct PARAM *p= (struct PARAM *) pvParam;

INT iCount; // Количество четных элементов массива

while (!p->stop)

{

// Ожидание освождения события hEvent

WaitForSingleObject(hEvent, INFINITE); // Выполнение обработки массива потоком

for (INT i=0, iCount=0; i<1000; i++ )

if(piMapArray[i]%2==0)

iCount++;

// Вывод значения iCount в диалоговом окне

}

}

// Открытие именованного объекта "Событие"

hEvent=OpenEvent(EVENT_ALL_ACCESS, NULL, "MyEvent");

// Открытие именованной проекции страничного файла

hMapPageFile=OpenFileMapping(PAGE_READWRITE, FALSE, "MapOfPageFile");

// Отображение проекции файла на адресное пространство процесса

piMapArray = (INT *) MapViewOfFile(hMapPageFile, FILE_MAP_ALL_ACCESS, 0, 0, iMapSize);

// Создание нового потока

hThread=CreateThread( NULL, 0, ThreadFunc, NULL, 0, &ThreadId);

// Завершение работы с проекцией страничного файла

UnmapViewOfFile(piMapArray); // Освобождение указателя на проекцию файла

CloseHandle(hMapPageFile); // Закрытие дескриптора проекции файла

 

 

Совместный доступ процессов к данным

через проекции файлов

 

4.2. Использование файлов на диске, отображаемых в память

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

1. Первый процесс создает проекцию файла в память, для чего:

а) открывает заданный файл на диске с помощью функции CreateFile (), которая возвращает дескриптор этого файла hFile;

б) с помощью функции CreateFileMapping () создает в памяти именованный объект "проекция файла" с дескриптором hMap File;

в) отображает проекцию файла на своё адресное пространство с помощью функции MapViewOfFile (), которая возвращает указатель lpMapData на регион памяти с отображе­нием проекции файла;

г) доступ к данным в проекции осуществляется через разыменование указателя * lpMapData;

д) по окончании работы с данными в проекции файла с помощью функции UnmapViewOfFile() сделать недействительным указатель lpMapData , а затем с помощью двух вызовов функции CloseHandle () освободить дескрипторы проекции hMap File и файла hFile.

2. В других процессах, которые хотят получить доступ к созданной проекции файла, необходимо:

а) открыть имеющуюся проекцию с помощью функции OpenFileMapping (), задав ей имя этой проекции. Функция возвращает дескриптор объекта "проекция файла" hMap File для этого процесса;

б) отобразить открытую проекцию на адресное пространство процесса с по­мощью функции MapViewOfFile (). Функция возвращает указатель lpMapData на отображение этой проекции в память данного процесса;

в) после выполнения указанных выше действий два и более процессов получат доступ к одному и тому же участку физической памяти, который является проекцией конкретного файла на диске;

г) по окончании работы с данными в проекции файла эти процессы должны с помощью функции UnmapViewOfFile() сделать недействительным указатель lpMapData , а затем, используя функцию CloseHandle (), освободить дескрипторы проекции hMap File. При этом все изменения данных в проекции будут сохранены в файле.

При работе с общим для нескольких процессов объектом "проекция файла" необходимо учитывать следующее:

- такой объект не будет уничтожен, пока не закроются все его дескрипторы во всех процессах;

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

Пример совместного использования проекции файла двумя процессами для чтения и обработки данных. Исходный файл на диске Numb.dat содержит набор из 5000 случайных целых чисел. Рассмотрим отдельно каждый из процессов:

1. Первый процесс – это приложение App1.exe, которое открывает файл, создает объект "проекция файла" с именем MyFile, отображает его в свое адресное пространство, создает второй процесс путем запуска другого приложения App2.exe. Затем первый процесс читает данные из проекции файла и определяет для этого набора целых чисел минимальное и максимальное значения.

Файл App 1. cpp

// Описание дескрипторов и указателя

HANDLE hFile;

HANDLE hMapFile;

INT *piMapData;

// Открытие исходного файла на диске

hFile=CreateFile("Numb.dat", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);

// Создание именованного объекта "проекция файла"

hMapFile=CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, "MyFile");

// Отображение проекции файла на адресное пространство процесса

piMapData = (INT *) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);

// Создание дочернего процесса – запуск приложения App2.exe

STARTUPINFO si;

PROCESS_INFORMATION pi;

ZeroMemory(&si, sizeof(si));

CreateProcess(NULL, "App2.exe", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);

// Обработка данных в проекции файла

INT i, iMin, iMax;

iMin = iMax = *piMapData;

for (i=0; i < 5000; i++)

{

if (*(piMapData+i) < iMin)

iMin = *(piMapData+i);

if (*(piMapData+i) > iMax)

iMax = *(piMapData+i);

}

// Завершение работы с проекцией файла

UnmapViewOfFile(piMapData);

CloseHandle(hMapFile);

CloseHandle(hFile);

2. Второй процесс – это запущенное из первого процесса приложение App2.exe, которое открывает созданную проекцию файла, отображает ее в свое адресное пространство. Затем второй процесс читает данные из проекции файла и определяет для этого набора целых чисел среднее значения и среднеквадратическое отклонение.

Файл App 2. cpp

// Описание дескрипторов и указателя

HANDLE hFile;

HANDLE hMapFile;

INT *piMapData;

// Открытие именованного объекта "проекция файла"

hMapFile=OpenFileMapping(FILE_MAP_READ, FALSE, "MyFile");

// Отображение проекции файла на адресное пространство процесса

piMapData = (INT *) MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);

// Обработка данных в проекции файла

INT i;

FLOAT nAver, nAvSqDev;

for (i=0, nAver=0; i < 5000; i++)

nAver += *(piMapData+i);

nAver /= 5000;

for (i=0, nAvSqDev=0; i < 5000; i++)

nAvSqDev += pow(*(piMapData+i) – nAver, 2);

nAvSqDev /= 5000;

// Завершение работы с проекцией файла

UnmapViewOfFile(piMapData);

CloseHandle(hMapFile);

В результате запуска первого приложения App1.exe и последующего запуска в нем другого приложения App2.exe, оба созданных процесса получат доступ к данным из файла Numb.dat, отображенным в память в виде проекции файла.

 

 

Двухсторонняя передача данных между процессами с использованием именованного канала

Для организации дуплексной передачи данных между сервером и клиентом необходимо: - при создании канала сервером с помощью функции CreateNamedPipe () параметру dwOpenMode задать значение PIPE_ACCESS_DUPLEX; - при открытии канала клиентом с помощью функции CreateFile () параметру dwDesiredAccess задать значение GENERIC_ READ | GENERIC_ WRITE.

Приложение – сервер канала должно содержать следующий код:

// Адрес блока данных для запроса и ответа

PVOID lpRequest, lpResponse;

DWORD dwSize1, dwSize2; // Размеры блоков данных

// Кол-во прочитанных и записанных байтов

DWORD dwRead, dwWritten;

… // Выделение памяти для блоков данных

HANDLE hNamedPipe; // Дескриптор канала

// Создание именованного канала

hNamedPipe = CreateNamedPipe("\\\\.\\pipe\\MyPipe", PIPE_ACCESS_DUPLEX, 0, 1, 0, 0, NMPWAIT_WAIT_FOREVER, NULL);

// Ожидание подключения клиента к каналу

ConnectNamedPipe (hNamedPipe, NULL);

// Чтение в цикле запросов от клиента

while (ReadFile(hNamedPipe, lpRequest, dwSize1, &dwRead, NULL))

{

… // Обработка очередного запроса

// Запись ответа в канал

WriteFile (hNamedPipe, lpResponse, dwSize2, &dwWritten, NULL);

}

// Отключение соединения с клиентом

DisconnectNamedPipe(hNamedPipe);  

// Закрытие дескриптора канала

CloseHandle(hNamedPipe);

Приложение – клиент канала должно содержать следующий код:

// Адрес блока данных для запроса и ответа

PVOID lpRequest, lpResponse;

DWORD dwSize1, dwSize2; // Размеры блоков данных

// Кол-во прочитанных и записанных байтов

DWORD dwRead, dwWritten;

INT nReqCount; // Количество запросов к серверу

… // Выделение памяти для блоков данных

HANDLE hNamedPipe; // Дескриптор канала

// Ожидание освобождения канала

WaitNamedPipe("\\\\.\\pipe\\MyPipe", NMPWAIT_WAIT_FOREVER); 

// Подключение клиента к каналу

hNamedPipe = CreateFile("\\\\.\\pipe\\MyPipe", GENERIC_ READ | GENERIC_ WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

// Обмен с каналом, пока есть запросы

for (int i=0; i< nReqCount; i++)

{

… // Формирование очередного запроса

// Запись запроса в канал

WriteFile (hNamedPipe, lpRequest, dwSize1, &dwWritten, NULL);

// Чтение ответа из канала

ReadFile(hNamedPipe, lpResponse, dwSize2, &dwRead, NULL);

}

// Закрытие дескриптора канала

CloseHandle(hNamedPipe);

Таким образом, приведенные фрагменты исходного кода показывают, как производится дуплексная передача запросов и ответов между клиентом и сервером именованного канала. Если сервер и клиент расположены на разных компьютерах в сети, то в коде клиента при вызове функции CreateFile () в имени канала необходимо указывать сетевое имя компьютера клиента.

 

5.1. Анонимные каналы

 

5.1.2. Однонаправленная передача данных между процессами с использованием анонимного канала

 

Пример. Родительский процесс (приложение Parent1.exe) создает анонимный канал, затем – дочерний процесс (приложение Child1.exe) с передачей ему дескриптора чтения канала. Затем первый процесс записывает в канал блок данных с адресом lpData и размером dwSize байтов. Дочерний процесс читает эти данные из канала и обрабатывает их.

Файл Parent 1. cpp - родительский процесс

PVOID lpData; // Адрес блока данных

// Размер блока данных и кол-во запис. байтов

DWORD dwSize, dwWritten;

…// Выделение памяти и заполнение блока данными

HANDLE hReadPipe, hWritePipe;

SECURITY_ATTRIBUTES PipeSA = {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};

PROCESS_INFORMATION ProcInfo;

STARTUPINFO StartInfo;

// Создание анонимного канала

CreatePipe(&hReadPipe, &hWritePipe, &PipeSA, 0);

// Очистка структуры StartInfo

ZeroMemory(&StartInfo, sizeof(STARTUPINFO));

StartInfo.cb = sizeof(STARTUPINFO);

// Перенаправление стандартного ввода на канал

StartInfo.hStdInput = hReadPipe;

StartInfo.hStdError=GetStdHandle(STD_ERROR_HANDLE);

StartInfo.hStdOutput=GetStdHadle(STD_OUTPUT_HANDLE);

StartInfo.dwFlags = STARTF_USESTDHANDLES;

// Создание дочернего процесса

CreateProcess(NULL, "Child", NULL, NULL, TRUE, 0, NULL, NULL, &StartInfo, &ProcInfo);

// Закрытие дескриптора канала для чтения

CloseHandle(hReadPipe);

// Запись данных в канал

for (; ;)

if (! WriteFile(hWritePipe, lpData, dwSize, &dwWritten, NULL)) break;

// Закрытие дескриптора канала для записи

CloseHandle(hWritePipe);

Файл Child1.cpp - дочерний процесс

// Адрес блока данных, получаемых из канала

PVOID lpData;

// Размер блока данных и кол-во прочит. байтов

DWORD dwSize, dwRead;

…// Выделение памяти для блока данных

HANDLE hStdIn; // Дескриптор уст-ва станд. ввода

BOOL fSuccess; // Флаг успешн. заверш. чтения

// Получение дескриптора канала для чтения

hStdIn = GetStdHandle(STD_INTPUT_HANDLE);

if (hStdIn == INVALID_HANDLE_VALUE)

ExitProcess(1);

// Чтение данных из канала

for (; ;)

{

 fSuccess=ReadFile(hStdIn, lpData, dwSize, &dwRead, NULL);

 if (! fSuccess || dwRead == 0)

    break;

}

… // Обработка данных, прочитанных из канала

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


 Использование почтов ых ячеек для передачи данных между процессами

 

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

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

Сообщения (данные), передаваемые на все почтовые ячейки в сети, могут быть не больше, чем 400 байтов, тогда как сообщения, отправляемые отдельной почтовой ячейке, ограничены только максимальным размером сообщения, указанным mailslot-сервером при создании этой почтовой ячейки.

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

6.1. Функции для работы с почтовыми ячейками

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

HANDLE CreateMailslot(LPCTSTR lpName,

DWORD nMaxMessageSize, DWORD lReadTimeout,

LPSECURITY_ATTRIBUTES lpSecurityAttributes);

Параметры функции:

- lpName указывает на строку, содержащую имя почтовой ячейки, создаваемой на локальном компьютере в виде: \\.\mailslot\имя_ячейки;

- nMaxMessageSize задает максимальный размер (в байтах) сообщений, которые может отправ­лять клиент. При значении 0 ограничения на размер сообщений нет;

- lReadTimeout определяет длительность (в миллисекундах) ожидания операции чтения. При значении 0 произойдет немедленный возврат, a при MAILSLOT_WAIT_FOREVER — бесконеч­ное ожидание;

- lpSecurityAttributes указывает на структуру типа SECURITY_ATTRIBUTES. Обычно этому параметру задают значение NULL.

Функция CreateMailslot () при успешном выполнении возвращает дескриптор для сервера почтовой ячейки, иначе – значение INVALID_HANDLE_VALUE.

Чтение сервером сообщения из почтовой ячейки производится с помощью функции:

BOOL ReadFile(HANDLE hMailslot, …);

Параметр hMailslot указывает дескриптор почтовой ячейки, полученный функцией CreateMailslot (). Остальные параметры задаются, как и при чтении из дисковых файлов.

Функция ReadFile () при успешном чтении сообщения возвращает TRUE и FALSE – когда выполнена попытка чтения из почтовой ячейки, буфер которой недостаточен для размещения сообщения.

Открытие клиентом почтовой ячейки производится с помощью функции:

HANDLE CreateFile(LPCTSTR lpMailslotName, …);

В первом параметре можно использовать следующие формы задания имени почтовой ячейки:

а) \\.\mailslot\имя_ячейки, когда сервер и клиент расположены на одном компьютере;

б) \\имя_сервера\ mailslot\имя_ячейки, когда сервер и клиент расположены на разных компьютерах в сети;

в) \\*\ mailslot\имя_ячейки, когда необходимо посылать по сети сообщения всем серверам почтовых ячеек, имеющих данное имя. В этом случае максимальная длина сообщения составляет 400 байт.

Остальные параметры функции CreateFile () задаются, как и для дисковых файлов.

Функция возвращает дескриптор почтовой ячейки для клиента. В случае неудачной попытки открытия возвращается значение INVALID_HANDLE_VALUE.

Запись клиентом сообщения в почтовую ячейку производится с помощью функции:

BOOL WriteFile(HANDLE hMailslot, …);

Параметр hMailslot указывает дескриптор почтовой ячейки, полученный функцией CreateFile (). Остальные параметры задаются, как и при чтении из дисковых файлов.

Функция WriteFile() при успешном записи сообщения возвращает TRUE и FALSE – в противном случае.

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

6.2. Однонаправленная передача данных между процессами с использованием почтовой ячейки

Рассмотрим использование описанных выше средств Win32 API для организации передачи данных от клиента к серверу на одном компьютере.

Приложение – сервер почтовой ячейки должно содержать следующий код:

// Адрес блока получаемых данных

PVOID lpReceiveData;

// Размер блока данных и кол-во прочит. байтов

DWORD dwSize, dwRead;

…// Выделение памяти для блока данных

HANDLE hMailslot; // Дескриптор почтовой ячейки

// Создание почтовой ячейки

hMailslot = CreateMailslot("\\\\.\\mailslot\\MyMailslot", 0, MAILSLOT_WAIT_FOREVER, NULL);

// Чтение данных из почтовой ячейки

ReadFile(hMailslot, lpReceiveData, dwSize, &dwRead, NULL);

… // Обработка полученных данных

// Закрытие дескриптора почтовой ячейки

CloseHandle(hMailslot);

Приложение – клиент почтовой ячейки должно содержать следующий код:

// Адрес блока передаваемых данных

PVOID lpSendData;

// Размер блока данных и кол-во запис. байтов

DWORD dwSize, dwWritten;

…// Выделение памяти и заполнение блока данными

HANDLE hMailslot; // Дескриптор почтовой ячейки

// Открытие клиентом почтовой ячейки

hMailslot = CreateFile("\\\\.\\mailslot\\MyMailslot", GENERIC_ WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

// Запись данных в канал

WriteFile(hMailslot, lpSendData, dwSize, &dwWritten, NULL);

// Закрытие дескриптора почтовой ячейки

CloseHandle(hMailslot);

Примечание. Для организации передачи данных от клиента к серверу по сети необходимо при открытии почтовой ячейки клиентом с помощью функции CreateFile () в её имени вместо точки задать сетевое имя компьютера сервера.

 

 


Дата добавления: 2019-09-13; просмотров: 141; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!