Программирование на Visual C++. Архив рассылки - Алекс Jenter
Шрифт:
Интервал:
Закладка:
Для каждой точки соединения, реализованной в нашем классе 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) две приватные переменные: