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

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

Закон Мерфи

Цель работы – освоение техники разработки локального сервера СОМ и клиентского приложения для него (4 час.).

Задание. Работа состоит из двух частей:

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

2. Разработка клиентского приложения для локального сервера.

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

Важно. СОМ объект сервера должен иметь методы и свойства, предназначенные для выполнения таких действий:

· отображения главного (и, по всей видимости, единственного) окна приложения;

· прекращения выполнения приложения;

· очистки окна приложения;

· рисования какой-нибудь фигуры (не линии и не эллипса);

· задания координат и размеров выводимой фигуры.

Часть 1. Разработка локального сервера СОМ

Методические указания. Предлагается следующая последовательность создания локального COM сервера в MVS-2010:

· создание каркаса «обычного» приложения, класс вида которого является наследником класса CFormView;

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

· добавление СОМ объекта, разработка его методов и свойств.

Шаг 1. Создание каркаса приложения.

Рекомендуется создать папку с именем вроде Lab4 и в ней разместить, в недалеком уже будущем, два проекта: локальный сервер с именем проекта LocSrv и клиентское приложение для него LocSrvClient.

Выполнить тему меню FileèNewèProject., выбрать Project Types=MFC и Templates=MFC Application. Дадим проекту имя LocSrv и нажмем кнопку ОК.

На следующем шаге (вкладка Application Type) выберем архитектуру приложения с единственным документом (Single Document) и поддержкой технологии документ/вид (Document/View architecture support). Для облегчения каркаса приложения сделайте также остальные установки в соответствии с рис.1.

Установки на вкладках Compound Document Support, Document Template Strings, и DataBase Support можно оставить все установки без изменения.

На вкладке User Interface Features выберите радиокнопку Use classic menu и не пугайтесь предупреждений ИС – ничего она вам не сделает, вы же в танке!

На вкладке Advanced Features важно установить флажки “Automation” и “Common Control Manifest”, а остальные флажки лучше снять.

На вкладке Generated Classes в качестве базового класса (Base class) для класса вид (в нашем случае мастер присвоил ему имя CLocSrvView) выберите, обязательно, класс CFormView, который вы до сих пор не использовали (или уже использовали?). Закончим создание каркаса приложения победоносным нажатием кнопки Finish.

 
  Разработка и использование локального сервера - student2.ru

Рис. 1. Свойства приложения

Шаг 2. Любуемся пока еще работоспособным приложением.

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

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

Шаг 3. Учимся рисовать.

Прежде чем пытаться создать СОМ объект, который умеет рисовать, давайте попробуем что-нибудь нарисовать «по-простому», без использования методов СОМ объекта. Для этого необходимо в классе вида CLocSrvView создать (перекрыть функцию CFormView::OnDraw()) виртуальную функцию OnDraw(). Чтобы это сделать, надо на вкладке ClassView выбрать CLocSrvView, вызвать окно свойств этого класса (Alt+Enter) и в этом окне щелкнуть кнопку Overrides (рис. 2). В появишемся списке функций надо выбрать OnDraw и сгенерировать ее (с помощью, как мы любим говорить, интуитивно понятного интерфейса).

Разработка и использование локального сервера - student2.ru

Рис.2. Генерация виртуальной функции CLocSrvView::OnDraw()

Искусник ИС создаст заготовку функции OnDraw() такого нехитрого вида:

void CLocSrvView::OnDraw(CDC* /*pDC*/)

{

// TODO: Add your specialized code here and/or call the base class

}

Снимите комментарий с имени формального параметра pDC, так как он нам понадобится. Кстати, а зачем имя параметра закомментировано, а? Отгадайте.

Рисование в программах на С++, как вы знаете, немножко сложновато, но зато о нем пишут почти в любой книге. Например, в работе [1] о нем можно почитать на с.27-28, в главах 5 и 11. Рекомендуется прямо сейчас, как говорится, в своем же присутствии, ознакомиться с разделами конспекта «6.5. Рисование внутри окна представления: Windows GDI» и «7. Интерфейс графического устройства, цвет и шрифт».

В следующем листинге приведен пример рисования линии и эллипса, занимающих всю клиентскую область окна.

Разработка и использование локального сервера - student2.ru

Введите текст функции OnDraw(), откомпилируйте и запустите приложение на выполнение. вы должны увидеть окно, похожее на рис. 3. Если в Вашем окне присутствует лишний элемент управления с текстом-подсказкой – удалите его. Попробуйте изменять размеры окна приложения и понаблюдайте при этом за панелью задач: заметили что-нибудь доселе невиданное?

 
  Разработка и использование локального сервера - student2.ru

Рис.3. Неповторимый рисунок в окне приложения

Шаг 4. Создание нового класса.

Теперь создадим класс, который и будет представлять собой СОМ объект. В л.р. «Разработка внутрипроцессного сервера и клиентского приложения с использованием ATL» мы создавали СОМ объект с использованием библиотеки ATL, а в этой работе воспользуемся, для разнообразия, другой возможностью – создадим СОМ объект на базе класса MFC.

Выполните команду ProjectèAdd Class и в появившемся окне выберите Categories==MFC и Templates==MFC Class, натисните Add. В следующем окне задайте свойства нового класса в соответствии с рис. 4.

Разработка и использование локального сервера - student2.ru

Рис. 4. Свойства нового класса CPainter

Особо обратите внимание на то, что в качестве базового надо указать класс CCmdTarget и выбрать радиокнопку Createable by type ID. Выбор последнего свойства позволит использовать сервер из программ на языке VBA (Visual Basic for Application). Кстати, в окошке рядом с этим кнопарем мы увидим внешнее имя нашего сервера LocSrv.Painter, тот самый ProgID, который полезен в клиентском приложении. Шлепайте по кнопке Finish!

Шаг 5. Подготовка сервера.

Ознакомьтесь с содержимым файлов проекта, в частности, сгенерированными мастером файлами Painter.h, Painter.cpp и LocSrv.idl. Первые два файла содержат описания сокласса и его интерфейсов, а методов у СОМ объекта пока нет, так как туповатый мастер ИС не может угадать наши намерения и методы придется создавать самостоятельно.

Если вы посмотрите на вкладку ClassView рабочего пространства, то должны там увидеть класс CPainter и интерфейсы ILocSrv и IPainter. В принципе на этом этапе можно было бы снабдить СОМ объект методами и свойствами, но прежде лучше бы выполнить некоторые подготовительные работы.

Во-первых, для того чтобы в методах класса CPainter можно было рисовать, надо как-то «добраться» до окна вида серверного приложения (класс CLocSrvView) и контекста устройства CDC для него.

Во-вторых, приведите функцию CLocSrvApp::InitInstance(), которая находится в файле LocSrv.cpp, к подобающему виду. Внимательно просмотрите ее текст, в котором есть некоторые изменения по сравнению с исходным текстом, и прочтите комментарии.

 
  Разработка и использование локального сервера - student2.ru

Разработка и использование локального сервера - student2.ru
 
  Разработка и использование локального сервера - student2.ru

 
  Разработка и использование локального сервера - student2.ru

В принципе в таком состоянии приложение должно компилироваться и работать, почти как и прежде. Можете проверить, что если приложение вызвать на выполнение с параметром командной строки /Unregserver, то главное окно приложения не появится и программа завершится после разрегистрации сервера, что и будет доведено до Вашего сведения в соответствующем оконце. Подобным образом приложение будет себя вести и при его запуске с ключом командной строки /Regserver, только при этом оно будет регистрироваться в реестре, для чего вам понадобятся соответствующие полномочия, и окно с сообщением о регистрации отображаться не будет. Добавить параметр командной строки к приложению можно, например, так:

· запустить ехе-файл приложения на выполнение из командной строки, например, воспользовавшись какой-нибудь оболочкой вроде Far или командой «Выполнить» из меню «Пуск»;

· в свойствах проекта (Alt+F7) выбрать ветвь Debugging и задать параметр(ы) командной строки в поле Command Arguments.

Если вы пойдете по второму пути, то не забудьте удалить параметр из командной строки, когда он уже не будет нужен!

Если приложение запустить на выполнение с ключом /Embedding (предварительно сняв комментарий с оператора return true), т.е. как будто оно было запущено клиентом, то мы выйдем из обсуждаемой функции CLocSrvApp::InitInstance() без прорисовки окна представления. В результате мы сможем узнать о том, что наше приложение выполняется, либо путем просмотра выполняющихся процессов с помощью Диспетчера задач или из заголовка окна ИС, в котором будет присутствовать желанное слово Running.

Шаг 6. Добавление методов в СОМ объект сервера.

Добавление методов (и свойств тоже) в СОМ объект локального сервера выполняется так же, как для внутрипроцессного сервера, что вы уже проделывали. Для добавления метода надо выбрать вкладку ClassView в окне решений, в ней выбрать ветвь LocSrv и в ней подветвь IPainter, помеченную значком интерфейса Разработка и использование локального сервера - student2.ru . Вызвав контекстное меню для IPainter, выберите команду AddèAdd method для добавления метода к интерфейсу IPainter. В появившемся окне надо объявить метод void PDraw(), т.е. выбрать Return type==void и задать Method name==PDraw. Маг ИС добавит метод к интерфейсу объекта и создаст его заготовку в файле Painter.cpp. Подобным же образом создайте еще один метод void Stop().

Замечание. Добавляйте методы интерфейса СОМ объекта обязательно с помощью мастера, т.е. с помощью команды AddèAdd method. В принципе это можно сделать и вручную, но надо знать в какие файлы и куда именно нужно добавить необходимые описания.

Когда мы «рисовали» в методе CLocSrvView::OnDraw(CDC* pDC) класса представления, то указатель pDC на контекст устройства нам был предоставлен «на блюдечке с голубой каемочкой». А где его взять в методе CPainter::PDraw()? Один из возможных выходов – использование глобальной переменной. Для этого в файле LocSrvView.cpp надо описать соответствующий указатель и инициализировать его в конструкторе класса CLocSrvView:

CView * glView;

CLocSrvView::CLocSrvView():CFormView(CLocSrvView::IDD)

{

glView=this;

}

Необходимые изменения выделены полужирным шрифтом. Теперь в файле Painter.cpp мы опишем этот же указатель как внешний (extern) и, таким чудодейственным образом, получим указатель на окно вида, с помощью которого, в свою очередь, получим вожделенный указатель на контекст устройства pDC.

Вот пример реализации методов интерфейса IPainter:

 
  Разработка и использование локального сервера - student2.ru

 
  Разработка и использование локального сервера - student2.ru

 
  Разработка и использование локального сервера - student2.ru

Метод Stop() предназначен для остановки работы сервера клиентским приложением, так как по-хорошему сервер завершаться, по крайней мере у меня, не захотел. Функция PostQuitMessage(), как следует из ее имени, просто посылает главному окну приложения-сервера сообщение Quit. В принципе приложение может его и проигнорировать, если его кто-нибудь этому научит.

Операторы

CWnd * pWnd=AfxGetApp()->GetMainWnd();

pWnd->ShowWindow(SW_SHOW);

предназначены для получения указателя на окно приложения и его прорисовки. Так как мы в функции CLocSrvApp::InitInstance() окно показываем всегда, то вызов функции ShowWindow() у нас в данном примере закомментирован.

На этом этапе серверное приложение должно собираться и работать, хотя собственно СОМ объект создаваться и использоваться не будет. Теперь можно удалить код из метода CLocSrvView::OnDraw(), так как дальше мы будем строить клиентское приложение и с его помощью рисовать в окне сервера (рекомендую закомментировать код этого метода или в самом его начале вставить оператор return). С другой стороны, этого можно, конечно, и не делать, если такое поведение приложения не противоречит логике его функционирования.

Замечание. Если оставить какой-либо код в методе CLocSrvView::OnDraw(), то этот код будет выполняться всякий раз, когда изображение в окне должно быть перерисовано. Например, если вы свернете окно и затем его развернете, то будет выполняться код метода CLocSrvView::OnDraw(), а не IPainter::PDraw(). В то же время метод PDraw() вызывается именно и только клиентским приложением.

В каталоге Debug у вас должен присутствовать файл LocSrv.tlb, содержащий библиотеку типов, которая нам понадобится для разработки клиентского приложения.

При запуске сервера как самостоятельного приложения он должен зарегистрировать себя в реестре сам (если, конечно, у вас есть высочайшее разрешение на запись в реестр, а именно в ветвь HKEY_CLASSES_ROOT).

Еще пару комментариев к тексту функции PDraw(). Вызов функции GetClientRect(rect) предназначен для того, чтобы получить размеры действительного прямоугольника окна клиентского приложения. Для того чтобы получить корректный прямоугольник и указатель на контекст устройства pDC, надо передать методу PDraw() указатель на объект вида CView нашего приложения-сервера, что мы и сделали.

Метод AfxGetApp()->GetMainWnd() возвращает указатель на главное окно приложения (окно-рамку). Этот указатель часто бывает весьма полезен не только для отображения окна, как это мы сделали в методе PDraw(), но и для доступа к другим многочисленным свойствам и методам окна-рамки, например:

/*Изменение заголовка главного окна приложения*/

AfxGetApp()->m_pMainWnd->SetWindowTextW(_T("Ку-ку!"));

/*Включение режима «мигания» (flash) заголовка приложения в панели

задач*/

AfxGetApp()->m_pMainWnd->FlashWindow(true);

Часть 2. Разработка клиентского приложения для локального сервера СОМ

Естественно, что клиентское приложение локального сервера может быть любым, в том числе и написанным на отличном от С++ языке программирования. Ниже описывается создание консольного приложения, а вы можете создать приложение другого типа.

Шаг 7. Создание каркаса приложения.

Запустите MVS-2010 и выполните тему меню FileÞNewÞProject. В появившемся окне выберите Project Types==Win32 и Templates=Win32 Console Application. Дадим проекту имя LocSrvClient. На вкладке Application Settings выберите Application type=Console application и отметьте флажок MFC.

Шаг 8. Импорт библиотеки типов. Скопируем в каталог текущего проекта файл LocSrv.tlb (из каталога проекта локального сервера LocSrv) и добавим директиву импорта в файл stdafx.h:

#include <afxext.h> // MFC extensions

#import "LocSrv.tlb" no_namespace

#include <afxdtctl.h> // MFC support for …

Шаг 9. Модификация главной функции. В главной функции опишем интеллектуальный указатель на интерфейс нашего сервера и опробуем его методы. Пример реализации главной функции приведен ниже.

 
  Разработка и использование локального сервера - student2.ru

 
  Разработка и использование локального сервера - student2.ru

 
  Разработка и использование локального сервера - student2.ru

Хочу еще раз обратить Ваше внимание на работу с символами кириллицы в консольном приложении, собранном с использованием кодировки Unicode. Для вывода символов кириллицы на монитор (в окно консольного приложения) с помощью объекта cout класса ostream их надо преобразовать в кодировку MS DOS с помощью функции CharToOemA(), параметры которой имеют тип char*. В данной программе введена функция-оболочка Rus() исключительно для удобства. Поток cout «умеет» выводить только строки символов char*, т.е. в кодировке ANSI. (Есть еще и поток wcout, который выводит символы Unicode, но он нам все равно в данном случае не помощник. См. подраздел «Русификация приложений» в конспекте лекций.) Другим способом кириллизации приложения является вызов функции setlocale(LC_ALL,”rus”), который достаточно сделать один раз в начале работы программы. Окрім того, в праці [4] ви знайдете функцію Ukr, що забезпечує можливість використання ураїнської мови у консольних застосуваннях.

Функция СОМ ErrorMessage() возвращает строку с сообщением об ошибке типа TCHAR*, т.е. в кодировке Unicode, и для преобразования этой строки в ANSI-строку кодировки MS DOS (Code Page 866) функция CharToOemA() или CharToOemW() не годится и приходится использовать функцию WideCharToMultiByte() с длинным списком параметров и неоднозначным именем. Если бы мы захотели показать сообщение об ошибке с помощью окна AfxMessageBox(), то это можно было бы сделать намного проще:

AfxMessageBox(_T("Элементарно, Ватсон!"));

AfxMessageBox(ex.ErrorMessage());

Здесь макрос _T обеспечивает приведение типа константы char * к типу Unicode (если это необходимо делать в соответствии с настройками проекта).

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

Для того чтобы проверить, что обработка ошибок COM (блоки try и catch) «работает», добавьте оператор pPnt->PDraw(); после pPnt->Stop(); и проследите за выводом в окно клиентского приложения (вывод сообщения об ошибке будет сделан с некоторой задержкой по времени).

Шаг 10. Решение возможных проблем. Если при вызове методов сервера его окно не появится или же возникнет ошибка, значит надо искать ошибку, по всей видимости, на стороне серверного приложения.

Как можно выполнять отладку сервера, реализованного в виде самостоятельного приложения? Для этого необходимо:

· загрузить проект сервера в ИС;

· добавить в командную строку параметр /embedding (см. выше шаг 5);

· если требуется, то поставить в нужных местах методов классов сервера контрольные точки;

· запустить сервер в отладочном режиме (F5);

· запустить клиентское приложение любым «внешним» способом, например, с помощью проводника.

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

Шаг 11. Ура, самостоятельная работа!

Здесь, наконец-то, вы сможете отказаться от навязываемого вам сценария и в полной мере проявить свои творческие и другие выдающиеся способности и качества. Предлагается сделать из сервера конфетку, такой продукт, от которого шпионов из Microsoft приходилось бы отгонять битами или танками как назойливых мух. Например, клиентское приложение спрашивает у пользователя: «Свет мой юзер, ты скажи, что тебе нарисовать?». А пользователь отвечает: «Я тебе подсказывать не буду, догадайся сама, но чтобы мне понравилось. Но это должно быть … цвета, на … фоне, размером …х… и т.д. Зрозумiло? Do you understand me?». После этого клиентское приложение вызывает соответствующие методы сервера (вы их элементарно добавите) и … пользователь вне себя от счастья, а преподаватель за такую Лабу ставит не обычные 5 баллов, а целых (точнее, нецелых) 5.1! Среди методов сервера должен найтись хотя бы один, который способен очистить окно сервера.

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

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

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

Шаг 12.Использование строк в качестве параметров методов СОМ объекта. Как надо написать метод сервера, который в качестве параметра должен получить (или возратить) строку символов? Невзирая на то, что типом параметра метода можно объявить char *, делать этого не стоит. Для передачи строк методам сервера надо использовать тип BSTR или тождественный ему LPCTSTR. Эти типы описаны в файлах WinNT.h и WTypes.h:

typedef wchar_t WCHAR; // wc, 16-bit UNICODE character

typedef WCHAR OLECHAR;

typedef /* [wire_marshal] */ OLECHAR *BSTR;

typedef __nullterminated CONST WCHAR *LPCWSTR, *PCWSTR;

typedef LPCWSTR PCTSTR, LPCTSTR;

Когда вы будете выбирать тип параметра с помощью мастера добавления метода к интерфейсу, то будете указывать тип BSTR и будете видеть этот тип в описании метода в файле .idl, а вот в файле реализации метода мастер укажет тип параметра LPCTSTR. Вот пример реализации метода:

Наши рекомендации