Рейтинговые книги
Читем онлайн Программирование на Visual C++. Архив рассылки - Алекс Jenter

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 79 80 81 82 83 84 85 86 87 ... 156

Для каждой точки соединения, реализованной в нашем классе CMyInterface, мы должны объявить connection point part, которая реализует точку соединения. Если вы реализуете одну или более точек соединения вы также должны объявить простую карту соединений в вашем классе. Карта соединений – это таблица точек соединения, поддерживаемых ActiveX-контролом. Наш пример демонстрирует простую карту соединений и одну точку соединения. Для этого откройте файл объявления класса MyInterface.h и сразу после макроса DECLARE_INTERFACE_MAP() добавьте нижеприведенный код:

DECLARE_CONNECTION_MAP()

BEGIN_CONNECTION_PART(CMyInterface,ObjCP)

 CONNECTION_IID(IID_IFireClassEvents

END_CONNECTION_PART(ObjCP)

Таким образом мы объявляем карту соединений. BEGIN_CONNECTION_PART и END_CONNECTION_PART — это макросы, объявленные во встроенном классе и наследуемые от CConnectionPoint, который реализует эту точку соединения. Если вы хотите переопределить какую-либо функцию класса CConnectionPoint или добавить функцию-член вашего родителя, тогда объявите её между двумя этими макросами. К примеру, макрос CONNECTION_IID, расположенный между двумя этими макросами, переопределяет функцию CConnectionPoint::GetIID.

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

extern const IID IID_IFireClassEvents;

interface IFireClassEvents : public IDispatch {};

Первая строка объявляет константу IID_IFireClassEvents, которую мы использовали в макросе CONNECTION_IID(IID_IFireClassEvents). Ведь все должно работать без ошибок. Однако перед описанием этой константы стоит служебное слово extern. Это значит, что она уже где-то объявлена. Поэтому я и сказал, что слегка непоследователен. Здесь все правильно. Сейчас мы закончим с заголовочным файлом и объявим этот идентификатор интерфейса в файле реализации класса CMyInterface.

Вторая строка есть объявление интерфейса. Теперь сохраните файл MyInterface.h и закройте его.

Следующим этапом добавим в файл MyInterface.cpp то, о чем собственно говорили только что выше. Найдите в коде декларацию идентификатора IID_IMyInterface и сразу после него вставьте следующие строки (только не забывайте вставлять правильные числовые значения, если вы используете uuid, отличный от того, что создала мне утилита guidgen.exe):

#pragma warning( disable : 4211)

static const IID IID_IFireClassEvents = //(F7222740-2296-11d5-964D-00001CDC1022)

 {0xf7222740, 0x2296, 0x11d5, {0x96, 0x4d, 0x0, 0x0, 0x1c, 0xdc, 0x10, 0x22}};

#pragma warning(default : 4211)

Обратите внимание на две директивы препроцессора, которые окаймляют данное объявление. Первая из них гасит ненужное предупреждение компилятора, а вторая снова его разрешает. Теперь добавьте в карту интерфейсов интерфейс IConnectionPointContainer (MyInterface.cpp):

BEGIN_INTERFACE_MAP(CMyInterface, CCmdTarget)

 INTERFACE_PART(CMyInterface, IID_IMyInterface, Dispatch)

 INTERFACE_PART(CMyInterface, IID_IConnectionPointContainer, ConnPtContainer)

END_INTERFACE_MAP()

И наконец, сразу за картой интерфейсов вставьте карту соединений:

BEGIN_CONNECTION_MAP(CMyInterface, CCmdTarget)

 CONNECTION_PART(CMyInterface, IID_IFireClassEvents, ObjCP)

END_CONNECTION_MAP()

Затем добавьте в конструктор нашего класса вызов функции EnableConnections. Эта функция является недокументированной функцией класса CCmdTarget, однако если вы не вызовете её из конструктора класса, работающего с Connection point и являющегося потомком CCmdTarget, то у вас ничего не получится.

CMyInterface::CMyInterface() {

 EnableAutomation();

 EnableConnections();

 // To keep the application running as long as an OLE automation

 // object is active, the constructor calls AfxOleLockApp.

 AfxOleLockApp();

}

Сохраните редактируемый файл и постройте наш проект. Если вы делали все правильно, то не должно быть ни ошибок, ни предупреждений. На данный момент у нас готов основной каркас. Займемся реализацией непосредственно самих функций. Однако сначала я хотел бы обратить ваше внимание на следующий факт. Я думаю, ни для кого не секрет, что COM-сервер может иметь сразу нескольких клиентов. Может случиться так, что этот сервер должен посылать событие клиентам в ответ на какое-либо действие пользователя. Например, сервер имеет графический интерфейс, и когда пользователь выбирает меню Файл, сервер должен уведомить ВСЕХ ЗАИНТЕРЕСОВАННЫХ клиентов об этом. Естественно возникает 2 вопроса:

1. Каким образом сервер сможет послать событие сразу всем клиентам.

2. Как определить, какой клиент заинтересован в получении событий, а какой нет?

Начнем со второго вопроса. Для этих целей интерфейс IConnectionPoint предусматривает специальные методы подписки на события Advise и Unadvise. С помощью первого из них клиент сообщает серверу, что он желает получать уведомления относительно какого-либо события, а вторая функция, наоборот, сообщает серверу, что данный клиент больше в таких сообщениях не нуждается. Соответственно, сервер должен иметь некий список, в который он заносит клиентов, подписанных на те или иные сообщения. И из которого он потом будет удалять отписавшихся клиентов. Таким образом, когда серверу нужно будет послать некое событие клиентам, то он в цикле пробежится по этому списку и отправит сообщения всем заинтересованным клиентам. Это и есть ответ на первый вопрос. Теперь приведем процедуру, которая реализует все выше сказанное. Вставьте её код в файл MyInterface.cpp а также не забудьте объявить её в MyInterfcae.h.

void CMyInterface::FireEvent() {

 const CPtrArray* pConnections=m_xObjCP.GetConnections();

 int nConnections=pConnections->GetSize();

 for (int i=0; i<nConnections; i++) {

  IDispatch* pClient=(IDispatch*)(pConnections->GetAt(i));

  if (pClient) {

   VARIANT varResult;

   DISPPARAMS disp={0,0,0,0};

   HRESULT hr = pClient->Invoke(0x1, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &disp, &varResult, NULL, NULL);

  }

 }

}

Итак, что же мы имеем? Скажу сразу, что MFC сильно облегчила нам жизнь. Здесь const CPtrArray* pConnections=m_xObjCP.GetConnections(); мы объявляем указатель на CPtrArray и присваиваем ему значение полученное из m_xObjCP.GetConnections(). Вот и все. Теперь мы имеем указатель на список всех наших потенциальных клиентов. Объект m_xObjCP поддерживается средствами CCmdTarget и самостоятельно, «без нашего участия» заносит в список подписавшихся и удаляет из него отписавшихся клиентов. Дальше все просто: мы перебираем в цикле всех наших клиентов и с помощью метода Invoke им события.

Еще раз напомню: не забудьте добавить описание этой функции в наш класс. А теперь измените тело функции FireMyEvent, как показано ниже.

void CMyInterface::FireMyEvent() {

 FireEvent();

}

Таким образом, мы заставим метод сгенерировать событие. Сохраните все сделанные вами изменения и постройте проект. Ошибок быть не должно. После чего найдите полученную DLL и с помощью утилиты regsvr32.exe (или любого другого инструмента, способного вызвать функцию DllRegisterServer) зарегистрируйте её в вашей операционной системе. На этом с сервером все!

Часть 3. Реализация клиента.

Программа-пример Client

Теперь напишем клиента. Выполните пункты меню File->New и заполните опции согласно рисунку 8.

Рисунок 8

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

Рисунок 9

Отредактируйте диалоговую форму. Чтобы она приняла вид, как на рисунке 10.

Рисунок 10

Сразу создайте обработчик для кнопки "Fire event", он нам пригодится, а также для кнопки ОК. Теперь необходимо в функции InitInstance класса CPointClientApp добавить инициализацию COM. В нашем случае вполне сойдет инициализация для STA, поэтому воспользуемся функцией CoInitialize(NULL):

BOOL CPointClientApp::InitInstance() {

 AfxEnableControlContainer();

 CoInitialize(NULL);

Сразу же добавьте в этот класс с помощью ClassWizard функцию ExitInstance, а в её обработчике поставьте деинициализацию COM:

int CPointClientApp::ExitInstance() {

 CoUninitialize();

 return CWinApp::ExitInstance();

}

Все, с этим классом закончили. Сохраните все изменения. Следующим шагом мы должны хорошенько поработать с классом CPointClientDlg. Для этого откройте файл PointClientDlg.h, в котором находится определение этого класса. Сейчас мы снабдим компилятор информацией об интерфейсах нашего COM-сервера. Для этого добавьте в PointClientDlg.h до определения самого класса следующую строку (после чего постройте проект):

#import "..ServerDebugPointServer.tlb" no_namespace named_guids

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

Теперь давайте добавим в класс CPointClientDlg (файл PointClientDlg.h) две приватные переменные:

1 ... 79 80 81 82 83 84 85 86 87 ... 156
На этой странице вы можете бесплатно читать книгу Программирование на Visual C++. Архив рассылки - Алекс Jenter бесплатно.
Похожие на Программирование на Visual C++. Архив рассылки - Алекс Jenter книги

Оставить комментарий