Протоколы и стандарты объектно-ориентированного программирования
Предисловие
Наиболее распространенным языком программирования последнего десятилетия безусловно является С. Этому способствовали такие его особенности, как лаконичность, мощность, гибкость, мобильность. Вместе с тем, стремительное усложнение приложений, для реализации которых применяются традиционные процедурно-ориентированнные языки программирования и, в частности С, заставляют говорить об определенном кризисе в их использовании, связанном прежде всего с недостаточной надежностью и выразительной способностью.
Подобных недостатков во многом лишены языки объектно-ориентированнго программирования (ООП), в основе которых лежит идея моделирования объектов посредством иерархически связанных классов. Отдельно взятый класс рассматривается как совокупность множества данных и операций над ними, причем доступ к элементам данных класса возможен только посредством операций этого класса. Установление четкой взаимозависимости между данными и операциями ведет к большой целостности данных и значительно повышает надежность программ по сравнению с традиционными языками программирования. Кроме того, идея программирования с помощью классов во многом использует тот же подход, который позволяет людям формировать модели объектов реального мира.
Впервые идеи ООП были реализованы в середине 60-х годов в языке программирования Симула-67. Последний, однако, не нашел в то время широкого распространения как в силу своей относительно меньшей производительности по сравнению с традиционными языками типа FORTRAN, ALGOL, PL/1 так и, возможно, неадекватности предлагаемых средств решаемым в то время задачи. Еще одним важным ограничением для распространения Симулы-67 стали трудности, с которыми пришлось столкнуться большинству программистов при его изучении. Дело в том, что наряду с целым рядом безусловных достоинств, идеи ООП обладают и одним существенным недостатком - они далеко не просты для понимания и особенно для освоения с целью практического использования.
С++ - развитие С.
С++ - это объектно-ориентированыый язык, то есть язык, позволяющий программисту оперировать объектами некоторых типов, предварительно им определенным. Название языка "С++" отражает эволюционный характер изменения языка С (запись "++", в языке С, означает, что к какой-то переменной прибавляется единица). Он имеет еще более мощные и гибкие средства для написания эффективных программ, чем С, от которого он произошел. Человек, программирующий на традиционных языках, может просто потерять голову от тех возможностей, которые предоставляет С++.
Но не менее важным является то, что такой распространенный и универсальный язык, как С, сохранен в качестве основы. С прост, эффективен, переносим. Чего только нет в языке С: строковых данных нет, матриц нет, средств параллельного программирования тоже нет. Нет даже ввода-вывода.
Типы, операции и операторы С очень близки к тому, с чем мы имеем дело в Ассемблере -числа, адреса, арифметические и логические действия, циклы... Кроме того, многие особенности С недвусмысленно намекаю компилятору, как сократить код и время исполнения программы. Эти характерные черты языка С позволяют написать эффективно работающий и не слишком сложный компилятор. И хотя в машинных кодах на разных компьютерах элементарные операции обозначаются по-разному, вряд ли разработчику компилятора придет в голову интерпретировать простейшие выражения каким-нибудь оригинальным способом. Именно поэтому язык С "идет везде и на всем", программы, написанные на нем, работают эффективно, и их можно переносить с одного компьютера на другой.
Большинство языков программирования созданы для решения определенного круга задач. В них не только не хватает определенных типов данных и функций, но и много лишнего с точки зрения человека, далекого от области, на которую ориентирован язык. Специализированные типы данных или операторы, требующие нетривиальной поддержки, затрудняют изучение языка и мешают вашей работе, если вы ими не собираетесь пользоваться. Поэтому С, в котором нет ничего лишнего, популярен среди широкого круга программистов. Соответствующие библиотеки могут добавить к средствам языка специализированные функции для работы со строками, файлами, списками, устройствами ввода-вывода, математическими объектами и т.д. Остается только выбрать то, что нужно лично вам. Заголовочные файлы облегчают использование библиотек, предоставляют полезные типы данных, глобальные переменные, макроопределения... Они во многом устраняют противоречие между эффективностью программы и удобством использования библиотечных функций. Они также позволяют не повторятся и не писать по несколько раз одно и тоже в различных программах. Поскольку С был создан специально для системного программирования, он имеет возможности низкого уровня, позволяющие "играть без правил". В зависимости от устройства и операционной системы вашей машины вы можете "влезть" в видеопамять или использовать системные программы, находящиеся в оперативной памяти.
В любом случае вы можете рассматривать код собственной программы как данные, а массив данных как код функции, квадратную матрицу как вектор, а текст как бинарное дерево. Что бы ни находилось в памяти - это всего лишь последовательная цепочка чисел. Если вы не боитесь риска - можете делать все, что вам вздумается.
Современные программисты выбирают С не только из-за его преимуществ. В настоящее время мы имеем дело с цепной реакцией: чем больше написано на С, тем больше на нем напишут еще. Это одна из причин, почему язык С++ сохраняет С в качестве подмножества.
По мнению автора С++, Бьерна Страуструпа, различие между идеологией С и С++ заключается примерно в следующем: программ на С отражает "способ мышления" процессора, а С++ - способ мышления программиста. Отвечая требованиям современного программирования, С++ делает акцент на разработке новых типов данных, наиболее полно соответствующих концепциям выбранной области знаний и задачам приложения. На С пишут библиотеки функций, С++ позволяет создавать библиотеки классов. Класс является ключевым понятием С++. Описание класса содержит описание данных, требующихся для представления объектов этого типа, и набор операций для работы с подобными объектами.
В отличие от традиционных структур С или Паскаля, членами класса являются не только данные, но и функции. Функции-члены класса имеют привилегированный доступ к данным внутри объектов этого класса и обеспечивают интерфейс между этими объектами и остальной программой. При дальнейшей работе совершенно не обязательно помнить о внутренней структуре класса и механизме работы "встроенных функций". В этом смысле класс подобен электрическому прибору - мало кто знает о его устройстве, но все знают, как им пользоваться.
Часто в целях повышения эффективности и упрощения структуры программы приходится заставлять ее работать с разнородными объектами так, как если бы они имели один и тот же тип. Например, окружность и квадрат естественно рассматривать как варианты геометрической фигуры. Полезно составлять списки фигур, нарисованных на экране, или функций, которые их размножают, двигают и т.д. О точном типе объекта приходится порой забывать. Список геометрических фигур "не знает", что в нем находится - отрезки или звездочки. Не знает этого и компилятор. Но все время, пока вы рисуете эти объекты, неизбежно приходится "помнить", что они из себя представляют. Конечно, возможности низкого уровня позволяют "забывать" и "вспоминать" когда и как нам заблагорассудится, но при этом компилятор теряет контроль над осмысленностью действий.
Использование производных классов и виртуальных функций позволяет избежать рискованной техники и не заботится о том, в какой форме объект типа "геометрическая фигура" хранит информацию о том, круг он или квадрат. (Кроме возможностей ООП, создание типов данных "треугольник" или "квадрат" как производные от базового класса "геометрическая фигура" отражает логическую связь понятий). Виртуальные функции, по существу, определяют, что именно можно делать с объектом, а не то, как это делать. Создавая класс "геометрическая фигура", мы можем включить в него виртуальные функции рисования, увеличения, поворота. С использованием этих функций можно создать еще один член класса.
Затем можно разработать библиотеку программ интерактивной графики, снабдив ее средствами диалога, функциями вроде дополнения некоторой области экрана одинаковыми геометрическими фигурами и т.д. Библиотечные функции будут вызывать функции-члены класса "геометрическая фигура": рисования, движения, поворота, увеличения. А после того, как мы все это напишем, откомпилируем, спрячем текст функций, которые считаем своей интеллектуальной собственностью, начинается самое интересное. Теперь мы можем описать сколько угодно новых типов фигур - многоугольников, звездочек, эллипсов - производных от класса "геометрическая фигура" и объяснить, как их рисовать, увеличивать и поворачивать. Как двигать - объяснять не надо. Это уже есть в базовом классе. Функции нашей библиотеки могут работать с объектами вновь созданных типов, для них это варианты геометрической фигуры. Следует отметить, что в производных классах могут (и, как правило, должны) появляться данные и функции, которых нет в базовом классе. Однако ни одна из функций, обрабатывающих "геометрические фигуры", никогда не узнает о специфических свойствах многоугольника или эллипса, кроме того, что они по-своему рисуются, увеличиваются и поворачиваются. Производный класс сам может быть базовым для других классов, а поздние версии С++ позволяют сделать один класс производным от нескольких других.
При написании программы часто допускаются досадные оплошности, обнаруживающиеся только на стадии выполнения и, увы, слишком поздно. Например, если переменная по смыслу - знаменатель дроби, хотелось бы получить сообщение об ошибке тогда, когда ей присваивается ноль, а не тогда, когда на этот ноль что-нибудь делится. Или, скажем, функция рисования точки. Невозможно удержаться от соблазна вызвать ее хотя бы раз без проверки выхода за границы экрана. В то же время , если мы пишем программу рисования линии, обязательно нужна функция, которая тупо ставит точку - и как можно быстрее. Существует много ситуаций, когда функции и данные следует разрешить использовать только привилегированным функциям, над которыми ва "хорошо подумали". В С++ этого можно добиться, сделав "опасные" данные и функции защищенными членами какого-нибудь класса. К ним имеют доступ только функции-члены этого же класса, а так же друзья класса. Напротив, если данные или функции-члены объявлены public, они являются общедоступными.
С++ предоставляет в распоряжение программиста сложные типы данных. Однако ни аппарат классов, ни перегрузка операций не влияют на эффективность. То, что класс - это класс, известно только компилятору. Если функции-члены классов объявлены inline, на их вызов не требуется время. Фактически это не функции, а подстановки. Лишь виртуальные функции оставляют относительно небольшой след в оперативной памяти.
Из всего выше сказанного вытекает логичный вывод: С++ наиболее удобный, универсальный и необходимый язык. Но все же возникает вопрос, что же было написано на этом языке, используя принципы ООП, что можно было бы "потрогать" любому программисту или пользователю. Ответ очевиден - это Microsoft Windows.
MS Windows и новый метод разработки программ.
Одним из наиболее важных механизмов взаимодействия программ является обмен данными. В MS Windows существует несколько способов взаимодействия приложений:
- почтовый ящик;
- динамический обмен данными;
- встраивание объектов.
Специальный почтовый ящик (clipboard) Windows позволяет пользователю переносить информацию из одного приложения в другое, не заботясь об ее форматах и представлении.
В отличие от профессиональных операциональных операционных систем, где механизм обмена данными между программами доступен только программисту, в Windows это делается очень просто и наглядно для пользователя.
Механизм обмена данных между приложениями - жизненно важное свойство многозадачной среды. И в настоящее время производители программного обеспечения пришли уже к выводу, что для переноса данных из одного приложения в другое почтового ящика уже недостаточно. Появился новый, более универсальный механизм - OLE ( Object Linking and Embedding )
- Встроенная объектная связь, который позволяет переносить из одного приложения в другое разнородные данные. Например, с помощью этого механизма данные, подготовленные в системе сетевого планирования Time Line for Windows ( Symantec ), можно переносить в текстовый процессор Just Write ( Symantec ), а затем, скажем, в генератор приложений Object Vision (Borland). Правда, это уже нестандартное средство Microsoft Windows, но тем не менее реализация OLE стала возможной именно в Windows.
Кроме механизма почтового ящика, предназначенного, в основном, для пользователя, программисту в Windows доступны специальные средства обмена данными между приложениями.
Программным путем можно установить прямую связь между задачами, например, принимая данные из последовательного порта, автоматически помещать их, скажем, в ячейки электронной таблицы Excel, средствами которой можно тут же отображать сложные зависимости в виде графиков или осуществлять их обработку в реальном режиме времени (этот механизм носит название динамического обмена данными - Dynamic Data Exchange, DDE ).
Основные термины
Клиентское приложение DDE - приложение, которому необходимо установить диалог с сервером и получить данные от сервера в процессе диалога.
DDE-диалог - взаимосвязь между клиентским и серверным приложениями.
Сервер-приложение - DDE приложение, которое передает данные клиенту в процессе диалога.
DDE-Транзакция -обмен сообщениями или данными между клиентом и сервером.
Item имя - строка, идентифицирующая некоторое множество данных, которое сервер в состоянии передать клиенту в процессе диалога.
Service имя - строка, генерируемая сервером и используемая клиентом для установления диалога.
Строковый указатель - двойное слово, генерируемое операционной системой, идентифицирующее строку, передающуюся в процессе динамического обмена данными.
Topic имя - строка, которая идентифицирует тип данных, необходимых клиентскому приложению при динамическом обмене данных.
Фильтр транзакции - флаг, который препятствует передаче нежелательных типов транзакций в функцию обратного вызова.
В Microsoft Windows динамический обмен данных является формой связи, которая использует общие области памяти для обмена данными между приложениями. Приложение может использовать DDE в некоторый момент времени для передачи и получения новых данных от сервера.
Механизм DDE схож с механизмом почтового ящика, который является частью операционной системы WINDOWS. Существует лишь незначительная разница в том, что почтовый ящик, в большинстве случае, используется как буфер временного хранения информации. DDE может быть инициализирован пользователем и в большинстве случаев продолжать работать без его вмешательства.
Библиотека DDEML обеспечивает пользователя набором средств, которые упрощают использование механизма DDE в WINDOWS приложениях. Вместо того, чтобы обрабатывать, получать и передавать DDE сообщения напрямую, приложения используют функции DDEML библиотеки. Библиотека DDEML также обеспечивает работу со строками и разделяемыми данными, генерируемыми DDE приложениями. Вместо того, чтобы использовать указатели на общие области памяти, DDE приложения создают и обмениваются строковыми указателями, которые идентифицируют строки и данные.
Уже существующие приложения, использующие протокол DDE, основанный на сообщениях полностью совместимы с теми, которые используют библиотеку DDEML. Вот почему приложение, использующее DDE-протокол могут установить диалог и выполнять транзакции с приложениями, использующими библиотеку DDEML.
Взаимосвязь между клиентом и сервером.
DDE возникает всегда между клиентским приложением и серверным. Клиентское приложение инициализирует обмен данными путем установления диалога с сервером и передачи транзакции. Транзакция необходима для данных и обслуживания. Сервер отвечает на транзакцию и обеспечивает клиента данными. Сервер может иметь сразу несколько клиентов в одно и тоже время, в свою очередь, клиент может получать данные сразу от нескольких серверов. Некоторое приложение одновременно может быть и клиентом и сервером. В добавок к вышесказанному, клиент и сервер могут оборвать диалог в любое удобное для них время.
DDE сервер использует три зарезервированных типа имен, расположенных иерархично: service, topic item - уникально идентифицируют некоторое множество данных, которое сервер может передать клиенту в процессе диалога.
Service имя - это строка, которую генерирует сервер в те промежутки времени, в которые клиент может установить диалог с сервером.
Topic имя - это строка, которая идентифицирует логический контекст данных. Для сервера, который манипулирует файлами, topic имена это просто названия файлов; для других серверов - это специфические имена конкретного приложения. Клиент обязательно должен указывать topic имя вместе с service именем, когда он хочет установить диалог с сервером.
Item имя - это строка, которая идентифицирует некоторое множество данных, которое сервер может передать клиенту в процессе транзакции. Например, item имя может идентифицировать ЦЕЛОЕ ( int, integer ), СТРОКУ ( string, char * ), несколько параграфов текста, или BITMAP образ.
Все вышеуказанные имена позволяют клиенту установить диалог с сервером и получить от него данные.
Системный режим
Системный режим работы обеспечивает клиента всей необходимой информацией о сервере.
Для того, чтобы определить, какие серверы доступны в данный момент времени, а также какой информацией они могут обеспечить клиента, последний, находясь в начальном режиме работы, должен установить имя устройства, равное NULL. Такой шаблон диалога максимально увеличивает эффективность работы, а также работу с сервером в системном режиме. Сервер, в свою очередь, должен поддерживать нижеописанные item имена, а также другие, часто используемые клиентом:
SZDDESYS ITEM TOPICS - список item имен, с которыми может работать сервер в данный момент времени. Этот список может изменяться время от времени.
SZDDESYS ITEM SYSITEMS - список item имен, с которыми может работать сервер в системном режиме.
SZDDDESYS ITEM STATUS - запросить текущий статус сервера. Обычно, данный запрос поддерживается только в формате CF_TEXT и содержит строку типа Готов/Занят.
SZDDE ITEM ITEMLIST - список item имен, поддерживаемых сервером в несистемном режиме работы. Этот список может меняться время от времени.
SZDDESYS ITEM FORMATS - список строк, представляющий собой список всех форматов почтового ящика, поддерживаемых сервером в данном диалоге. Например, CF_TEXT формат представлен строкой TEXT.
Основное назначение и работа функции обратного вызова
Приложение, которое использует DDEML, должно содержать функцию обратного вызова, которая обрабатывает события, полученные приложением. DDEML уведомляет приложение о таких событиях путем посылки транзакций в функцию обратного вызова данного приложения.
В зависимости от флага фильтра транзакции, сформированного при вызове функции DdeInitialize, функция обратного вызова получает отсортированные транзакции вне зависимости от того, является ли данное приложение клиентом, сервером или тем и другим одновременно. Следующий пример демонстрирует наиболее типичное использование функции обратного вызова.
HDDEDATA CALLBACK DdeCallback( uType, uFmt, hconv, hsz1, hsz2, hdata, dwData1, dwData2 )
UINT uType; // Тип транзакции
UINT uFmt; // Формат почтого ящика
HCONV hconv; // Идентификатор диалога
HSZ hsz1; // Идентификатор строки #1
HSZ hsz2; // Идентификатор строки #2
HDDEDATA hdata; // Идентификатор глобального объек та памяти
DWORD dwData1; // Данные текущей транзакции #1
DWORD dwData2; // Данные текущей транзакции #2
{
switch (uType)
{
case XTYP_REGISTER:
case XTYP_UNREGISTER:
. . .
return (HDDEDATA) NULL;
case XTYP_ADVDATA:
. . .
return (HDDEDATA) DDE_FACK;
case XTYP_XACT_COMPLETE:
. . .
return (HDDEDATA) NULL;
case XTYP_DISCONNECT:
. . .
return (HDDEDATA) NULL;
default:
return (HDDEDATA) NULL;
}
}
Параметр uType идентифицирует тип посланной транзакции в функцию обратного вызова при помощи DDEML. Значения оставшихся параметров зависят от типов транзакции. Типы транзакций будут обсуждены нами в разделе "Обработка Транзакций".
Диалог между приложениями
Диалог между клиентом и сервером всегда устанавливается по требованию клиента. Когда диалог установлен, оба партнера получают идентификатор, который описывает данный диалог.
Партнеры используют этот идентификатор в большинстве функций DDEML для посылки транзакций и для их обработки. Клиенту может потребоваться диалог как с одним сервером, так и с несколькими.
Рассмотрим подробно как приложение устанавливает диалог и получает информацию о уже существующих каналах связи.
Простой Диалог
Клиентское приложение устанавливает простой диалог с сервером путем вызова функции DdeConnect и определяет идентификаторы строк, которые содержат всю необходимую информацию о service имени текущего сервера и интересующем клиента в данный момент topic имени.
DDEML отвечает на вызов этой функции посылкой соответствующей транзакции XTYP_CONNECT в функцию обратного вызова каждого доступного в данный момент времени сервера, зарегистрированное имя которого совпадает с именем, переданным при помощи функции DdeConnect при условии, что сервер не отключал фильтр service имени вызовом функции DdeServiceName.
Сервер может также установить фильтр на XTYP_CONNECT транзакцию заданием соответствующего флага CBF_FAIL_CONNECTIONS при вызове функции DdeInitialize.
В процессе обработки транзакции типа XTYP_CONNECT DDEML передает полученные от клиента service и topic имена серверу. Сервер должен проверить эти имена и возвратить TRUE, если он в состоянии работать с такими именами, и FALSE в противном случае. Если ни один из существующих серверов не отвечает на CONNECT-запрос клиента, функция DDeConnect возвращает ему NULL с информацией о том, что в данный момент времени НЕ возможно установить диалог.
Однако, если сервер возвратил TRUE, то диалог был успешно установлен и клиент получает идентификатор диалога
- двойное слово, посредством которого и ведется обмен данными с сервером.
Затем сервер получает транзакцию вида XTYP_CONNECT_CONFIRM (в случае, если он НЕ описывал флаг фильтра CBF_FAIL_CONFIRMS при вызове соответствующей функции).
В нижеприведенном примере производится попытка установить диалог с сервером, который в состоянии работать с service именем 'My Server' в системном режиме. Считаем, что параметры hszSysTopic и hszServName уже предварительно созданы нами ранее.
HCONV hConv;
HWND hwndParent;
HSZ hszServName;
HSZ hszSysTopic;
. . .
hConv = DdeConnect(
idInst, // Копия приложения
hszServName, // Идентификатор
service-имени
handle hszSysTopic,// Идентификатор
system-topic-имени
(PCONVCONTEXT) NULL); // Используем контекст
по умолчанию
if( hConv == NULL )
{
MessageBox( hwndParent, "MyServer НЕ доступен!",
(LPSTR) NULL, MB_OK );
return FALSE;
}
. . .
В этом примере функция DdeConnect заставляет DDEML посылать транзакцию вида XTYP_CONNECT в функцию обратного вызова сервера MyServer.
А теперь приведем пример функции обратного вызова сервера, который обрабатывает транзакцию XTYP_CONNECT и сравнивает свое зарегистрированное имя с именем, полученным от клиента. Как уже было отмечено ранее, если они совпадают, то сервер в состоянии установить диалог с клиентом.
#define CTOPICS 5
HSZ hsz1; // Идентификатор строки,
полученный от DDEML.
HSZ ahszTopics[CTOPICS]; // Массив поддреживаемых
topic имен
int i; // Счетчик цикла
.
. // Для обработки транзакций используем стандартную
ANSI C
. // конструкцию switch --> case --> default.
.
case XTYP_CONNECT:
for (i = 0; i < CTOPICS; i++)
{
if (hsz1 == ahszTopics[i])
return TRUE; // Установка диалога
}
return FALSE; // Topic имя НЕ поддерживается,
диалог запрещен.
.
. // Обработка других типов транзакций.
.
Если сервер возвращает TRUE в ответ на транзакцию XTYP_CONNECT, DDEML посылает транзакцию вида XTYP_CONNECT_CONFIRM в функцию обратного вызова данного сервера. Обработав эту транзакцию, сервер может получить идендификатор диалога.
Вместо конкретного имени сервера клиент может установить шаблон диалога путем установки идентификаторов service и topic имен в NULL при вызове функции DdeConnect.
Если хотя бы один из вышеперечисленных идентификаторов равен NULL, DDEML посылает транзакцию типа XTYP_WILDCONNECT в функцию обратного вызова всех активных в данный момент DDE-приложений (исключения составляют лишь те, кто при вызове соответствующей функции указал флаг фильтрации XTYP_WILDCONNECT).
Любое сервер-приложение должно ответить на данную транзакцию и возвратить указатель на массив структур типа HSZPAIR, оканчивающийся нулем.
Если сервер-приложение НЕ вызывает функцию DDeNameService для регистрации собственного service имени в системе и фильтр обработки транзакций включен, то сервер НЕ получит транзакцию вида XTYP_WILDCONNECT.
Вышеописанный массив должен содержать одну структуру для каждого service и topic имен. DDEML выбирает одну пару из массива для установления диалога и возвращает его идентификатор клиенту. Затем DDEML посылает серверу транзакцию вида XTYP_CONNECT_CONFIRM (исключения составляют лишь те серверы, которые при инициализации установили фильтр обработки транзакций).
Продемонстируем использование транзакции вида XTYP_CONNECT.
#define CTOPICS 2
UINT uType;
HSZPAIR ahszp[(CTOPICS + 1)];
HSZ ahszTopicList[CTOPICS];
HSZ hszServ, hszTopic;
WORD i, j;
if (uType == XTYP_WILDCONNECT)
{
// Сканируем список topic имен и создаем мас сив структур типа HSZPAIR
j = 0;
for (i = 0; i < CTOPICS; i++)
{
if (hszTopic == (HSZ) NULL ||
hszTopic == ahszTopicList[i])
{
ahszp[j].hszSvc = hszServ;
ahszp[j++].hszTopic = ahszTopicList[i];
}
}
//
// Последний элемент массива всегда NULL.
//
ahszp[j].hszSvc = NULL;
ahszp[j++].hszTopic = NULL;
//
// Возвращаем дискриптор глобального объекта
// памяти,содержащий структуры типа HSZPAIR.
//
return DdeCreateDataHandle(
idInst, // Копия приложения
(LPBYTE) &ahszp, // Указатель на массив
типа HSZPAIR
sizeof(HSZ) * j, // Длина массива
0, // Начальное смещение
(HSZ) NULL, // item-имя не существует
0, // формат item-имени также
// не существует
0); // Возлагаем все работу
// с массивом на систему
}
Любой сервер или клиент может оборвать диалог в любое время путем вызова функции DdeDisconnect. Это означает, что партнер по обмену данными получает транзакцию типа XTYP_DISCONNECT в функции обратного вызова (если, конечно, партнер не установил фильтр обработки транзакций вида CBF_SKIP_DISCONNECTIONS).
Обычно приложение реагирует на транзакцию XTYP_DISCONNECT вызовом функции DdeQueryInfo для получения информации о прекращенном диалоге. После того, как функция обратного вызова обработала транзакцию типа XTYP_DISCONNECT, идентификатор диалога больше не существует.
Клиентское приложение, которое получает транзакцию типа XTYP_DISCONNECT в своей функции обратного вызова может попытаться возобновить диалог при помощи вызова функции DdeReconnect. Клиентское приложение может вызывать эту функцию только находясь внутри своей собственной функции обратного вызова.
Сложный диалог
Клиентское приложение может использовать функцию DdeConnectList для того, чтобы определить какие сервер-приложения существуют в системе в данный момент времени.
Клиент обязательно должен описывать service и topic имена, когда он вызывает эту функцию; это означает, что DDEML должна послать транзакцию вида XTYP_CONNECT все функции обратного вызова всех имеющихся в данный момент сервер-приложений, чьи зарегистрированные имена совпадают с именами, указанными клиентом (исключение составляют лишь те серверы, которые фильтруют получаемые транзакции).
В добавление к вышесказанному, можно отметить, что клиент, при вызове функции DdeConnectList, может указать NULL в качестве service или topic имени, либо же сразу для обоих. Все доступные в системе серверы, чьи зарегистрированные имена совпадают с именами, указанными клиентом, отвечают на его запрос. Диалог устанавливается со всеми такими серверами, даже если в системе запущено одно и тоже сервер-приложение несколько раз.
Клиент может использовать функции DdeQueryNextServer и DdeQueryConvInfo для того, чтобы понять, какой сервер находится в списке, полученный при вызове функции DdeConnectList. DdeQueryNextServer возвращает идентификатор диалога для следующего сервера, находящегося в списке; DdeQueryConvInfo заполняет структуру CONVINFO информацией о диалоге.
Клиент может сохранить полученные идентификаторы диалогов и отказаться от просмотра оставшихся серверов в списке.
Приведем пример использования функции DdeConnectList для установления диалога со всеми серверами, которые поддерживают имя 'system topic', затем будем использовать функции DdeQueryConvInfo и DdeQueryNextServer для получения их идентификаторов service имен, одновременно не забывая сохранить последние во временном буфере.
HCONVLIST hconvList; // Список диалогов
DWORD idInst; // Дискриптор приложения
HSZ hszSystem; // System topic
HCONV hconv = NULL; // Идентификатор диалога
CONVINFO ci; // Информация о диалоге
UINT cConv = 0; // Количество идентификаторов
диалогов
HSZ *pHsz, *aHsz; // Указатель на идентификатор
строки
// Присоединяемся ко всем серверам, поддерживающим
// System topic.
hconvList = DdeConnectList(idInst, NULL, hszSystem,
NULL, NULL);
// Вычисляем количество серверов в списке.
while((hconv = DdeQueryNextServer(hconvList,hconv))
!= NULL)
cConv++;
// Выделяем буфер для сохранения идентификаторов строк.
hconv = NULL;
aHsz = (HSZ *) LocalAlloc(LMEM_FIXED, cConv * sizeof(HSZ));
// Копируем идентификатор строки в буфер.
pHsz = aHsz;
ile((hconv = DdeQueryNextServer(hconvList,hconv)) != NULL)
{
DdeQueryConvInfo(hconv, QID_SYNC, (PCONVINFO) &ci);
DdeKeepStringHandle(idInst, ci.hszSvcPartner);
*pHsz++ = ci.hszSvcPartner;
}
.
. // Используем идентификатор: 'общаемся' с сервером.
.
// Освобождаем память и прекращаем диалог.
LocalFree((HANDLE) aHsz);
DdeDisconnectList(hconvList);
Приложение может оборвать индивидуальный диалог, находящийся в списке диалогов путем вызова функции DdeDisconnect; приложение может оборвать все диалоги, находящиеся в списке путем вызова функции DdeDisconnectList.
Обе вышеуказанные функции указывают DDEML о необходимости посылки транзакции вида XTYP_DISCONNECT во все функции партнеров по диалогу данного приложения (в случае использования функции DdeDisconnectList будет посылаться транзакция XTYP_DISCONNECT для каждого элемента в списке диалогов).
Обмен данными между приложениями
Так как DDE использует области памяти для передачи данных из одного приложения в другое, DDEML обеспечивает конечного программиста функциями, при помощи которых DDE-приложения могут создавать и обрабатывать DDE-объекты.
Весь спектр транзакций, который вызывает обмен данными, требует от приложения, экспортирующего их, создания некоторого буфера, содержащего эти данные, а затем вызова функции DdeCreateDataHandle.
Эта функция создает DDE-объект, копирует данные из буфера в этот объект и возвращает идентификатор данных для данного приложения.
Идентификатор данных-это двойное слово, которое использует DDEML для обеспечения доступа к данным в DDE-объекте.
Для того, чтобы разделять данные в DDE-объекте, приложение передает идентификатор данных DDEML, а затем DDEML передает его в функцию обратного вызова приложения, получающего данные.
В нижеприведенном примере показано, как создать DDE-объект и получить его идентификатор. В процессе обработки транзакции типа XTYP_ADVREQ, функция обратного вызова конвертирует текущее время в ASCII строку, копирует строку в вспомогательный буфер, а затем создает DDE-объект, содержащий вышеуказанную строку. Функция обратного вызова возвращает идентификатор DDE-объекта DDEML, которая передает этот идентификатор клиентскому приложению.
typedef struct tagTIME
{
INT hour; // 0 - 11 формат времени для
часов.
INT hour12; // 12-ой формат.
INT hour24; // 24-ой формат.
INT minute;
INT second;
INT ampm; // 0 --> AM , 1 --> PM
} TIME;
HDDEDATA EXPENTRY DdeCallback
(uType, uFmt, hconv, hsz1, hsz2, hdata,
dwData1, dwData2)
UINT uType;
UINT uFmt;
HCONV hconv;
HSZ hsz1;
HSZ hsz2;
HDDEDATA hdata;
DWORD dwData1;
DWORD dwData2;
{
CHAR szBuf[32];
switch (uType)
{
case XTYP_ADVREQ:
case XTYP_REQUEST:
if ((hsz1 == hszTime && hsz2 == hszNow)
&& (uFmt == CF_TEXT))
{
// Копируем строку в буфер.
itoa(tmTime.hour, szBuf, 10);
lstrcat(szBuf, ":");
if (tmTime.minute < 10)
lstrcat(szBuf, "0");
itoa(tmTime.minute,
&szBuf[lstrlen(szBuf)], 10);
lstrcat(szBuf, ":");
if (tmTime.second < 10)
strcat(szBuf, "0");
itoa(tmTime.second,
&szBuf[lstrlen(szBuf)], 10);
szBuf[lstrlen(szBuf)] = '\0';
// Создаем глобальный объект и
// возвращаем его идентификатор
return (DdeCreateDataHandle(
idInst, // копия
приложения
(LPBYTE) szBuf, // исходный
буфер
lstrlen(szBuf) + 1,
0, // смещение
от его начала
hszNow, // item-имя
CF_TEXT, // формат
почтого ящика
0));
}
else return (HDDEDATA) NULL;
.
. // Обработка других типов транзакций.
.
}
}
Клиентское приложение получает указатель на DDE-объект путем передачи идентификатора данных функции DdeAccessData. Указатель, возвращаемый этой функцией, обеспечивает доступ к данным в формате 'ТОЛЬКО НА ЧТЕНИЕ'. Клиент должен просмотреть полученные данные при помощи этого указателя и вызвать функцию DdeUnaccessData для его уничтожения. Клиент может скопировать полученные данные в заранее приготовленный буфер посредством вызова функции DdeGetData.
В следующем примере мы получим указатель на DDE-объект, сохраним его в параметре hData, скопируем содержимое во временный буфер и уничтожим указатель:
HDDEDATA hdata;
LPBYTE lpszAdviseData;
DWORD cbDataLen;
DWORD i;
char szData[32];
. . .
case XTYP_ADVDATA:
lpszAdviseData = DdeAccessData(hdata,
&cbDataLen);
for (i = 0; i < cbDataLen; i++)
szData[i] = *lpszAdviseData++;
DdeUnaccessData(hdata);
return (HDDEDATA) TRUE;
. . .
Обычно, когда приложение, создающее идентификатор данных, передает его DDEML, этот идентификатор портится внутри вышеуказанного приложения. В этом нет ничего страшного, если сервер должен разделять данные только с одним клиентом. Если же сервер должен разделять данные сразу с несколькими клиентами одновременно, ему придется указывать флаг HDATA_APPOWNED при вызове функции DdeCreateDataHandle.
Это делает возможным получение прав собственности на DDE-объект сервер-приложения и предотвращает порчу идентификатора данных DDEML. Приложение может передавать DDEML идентификатор данных любое количество раз, однако вызывать функцию DdeCreateDataHandle можно лишь однажды.
Если приложение указывает флаг HDATA_APPOWNED в параметре atCmd при вызове функции DdeCreateDataHandle, оно обязательно должно вызывать функцию DdeFreeDataHandle для очистки памяти вне зависимости от того, передавался ли идентификатор данных DDEML или нет. Перед тем как оборвать диалог, приложение должно вызывать функцию DdeFreeDataHandle для очистки всех созданных идентификаторов, но которые так и не были переданы DDEML.
Если приложение еще не передало идентификатор DDE-объекта DDEML, то оно может добавить данные к уже существующему объекту или полностью заменить их в нем. Все эти сервисные функции обслуживаются функцией DdeAddData.
Обычно приложение использует эту функцию для новой инициализации старых не уничтоженных DDE-объектов. После того, как приложение передает идентификатор данных DDEML, DDE-объект, идентифицирующий этот идентификатор НЕ может быть изменен, однако он может быть уничтожен.
OLE-технология
Как видно из описанного выше протокола DDE, приложения должны обязательно знать типы передаваемых данных, уметь их обрабатывать, а в основном даже могут работать только с символьными строками. Это, конечно, не очень удобно, когда необходимо, например, создать небольшой текст с различными картинками, пиктограммами и другими наглядными или не очень иллюстрациями. В этом случае на помощь программисту происходит OLE - встраивание объектов. Вместе с данными мы получаем машинный код, который эти данные может обрабатывать.
Способы упорядочивания, источники и целевые документы
При использовании OLE-технологии пользователь всегда имеет дело с одним ведущим приложением (главным) и одним ведомым (подчиненным), а точнее, с одним ведомым.
Приложение, с помощью которого получен объект для встраивания всегда играет роль подчиненного. Это особенно характерно для случаев передачи объектов при встраивании и связывании через буфер промежуточного обмена.
Часто используемые термины Приложение-источник и Целевое приложение касаются не подчинения приложений, а определяют генеалогию объектов.
Некоторые Windows-приложения могут выступать только в роли подчиненных, а некоторые только в роли ведущих. Например, Paintbrush в OLE технологии может играть только роль подчиненного приложения, служащего для создания и модификации отдельных объектов. Другие приложения, например, Write или Cardfile можно считать оправданным с точки зрения, что гораздо чаще приходится вставлять иллюстрации в сложные по структуре текст, чем текст в иллюстрации. Новые приложения, такие как Word, могут выполнять в рамках OLE обе эти функции.
Употребление термина объект считается престижным в кругах программистов, хотя часто он употребляется и не к месту. Всякий разработчик почитает своим долгом применить в своем продукте ООП без особой на то необходимости. В среде Windows в термин объект вкладывается несколько специфический смысл. Пользователя не приглашают постигать азы ООП, или заняться конструированием объектов на С++.
Когда об объектах говорят в рамках Windows, то имеют в виду возможность встраивания в некоторый документ фрагмента, порожденного другим приложением. Вот это "инородное тело" и называется объектом.
В таком подходе нет ничего нового. Когда в текст, подготавливаемый Write, вставляется рисунок из Paintbrush посредством Clipboard или таблиц Exсel, в документ, подготавливаемый в Word, то результатом действия будет как раз появления объекта.
Традиционные объекты всегда представляют собой копии. Работа с ними основывается на том, что все Windows приложения поддерживают не только свой собственный формат , но и некоторый обобщенный, стандартный, играющий роль общеизвестного международного языка. Если, например, в текстовый документ вставляется таблица из табличного процессора, то буфер промежуточного обмена преобразует ее в формат к стандартному, и тем самым обеспечивает вставку. Такая копия в текстовом редакторе по виду не отличается от оригиналу, но она недоступна для внесения изменений. Невозможно, вставив таким способом копию из Paintbrush в Write документ, изменить цвет, толщину линий или масштаб.
Новые объекты, доступные в рамках Windows 3.1 очень похожи на традиционные, но они не являются копиями - это оригиналы. Они имеются в единственном экземпляре и находятся непосредственно в целевых документах. Там они существуют одновременно в двух форматах - в стандартном и в формате приложения-источника.
Благодаря стандартному формату объект может индицироваться и сохранять в рамках целевого документа. Имеется возможность обработки объекта также, как и любого файла оригинала. Ситуация выглядит так, словно внутри объекта встроен другой. Это обеспечивает доступ к средствам обработки нового объекта (приложению-источнику) посредством простого двойного щелчка на объекте.
Встроенные объекты
Информация, вставленная в документ целевого приложения, представляет собой объект. Такой объект встраивается в документ, обрабатываемый ведущим приложением. Это значит, что он рассматривается как составная часть данного документа, может распечатываться и сохраняться вместе с ним. Такие объекты могут содержать информацию любого типа: текст, таблицы, графики и др.
Встроенные объекты существуют только в единственном экземпляре и только там, где они встроены - в целевом документе. Обрабатываются они своими "родительскими" программами, вызываемыми весьма эффективным способом, в отличие от традиционного.
Связывание с родительским приложением
Следующей весьма удобной особенностью встроенных объектов является то, что они остаются связанными с породившим их приложениями. Благодаря этому пользователь избавляется от необходимости помнить имена и директории файлов-источников. Достаточно двойного щелчка на объекте - и родительская программа запускается.
Важным достоинством подобного связывания встроенных объектов является мобильность документов. Можно легко перенести такой документ с одной машины на другую (необходимо только чтобы на них обеих была установлена оболочка и были необходимые приложения или динамические библиотеки от них). Для обработки встроенных объектов достаточно будет щелкнуть по ней дважды и на другой машине произойдет тоже самое, что и на вашей: вызовется соответствующее приложение. В этом случае необходимым условием переноса является наличие на другой машине текстового редактора Write и графического редактора Paintbrush.
При работе в рамках DDE такой перенос не возможен, точнее он будет включать в себя не только перенос самого файла-документа, но и связанных с данным файлом файлов-источников и целевых файлов - всей структуры.
Перспективы развития OLE
Технология OLE делает только первые шаги. Пока только некоторые Windows приложения являются OLE совместимыми. Среди утилит группы Accessories версии 3.1 такими на сегодняшний день являются только Write, Paintbrush и Cardfile. Но даже они "в своем кругу" не допускают вставки в произвольном направлении (т.е. из любой в любую другую). В настоящее время речь идет о поддержке наиболее оправданного с практической точки зрения "направления встраивания" - из Paintbrush в Write и Сardfile документа.
Чтобы определить какие из приложений поддерживаю OLE интерфейс, необходимо из OLE-совместимого приложения выполнить директиву "ВСТАВИТЬ ОБЪЕКТ" в меню "Edit". В отрывшемся окне будет продемонстрирован список доступных встраиваемых объектов.
В настоящий момент многие компиляторы уже ввели поддержку OLE в свои библиотеки: Borland C++ ver4.5. Пример использования OLE технологии приведен в приложении 1. Данная программа использует созданный рисунок Paintbrush в виде файла или копирует его из Clipboard.
Заключение
В заключении хотелось бы отметить, что существующие способы обмена информации возникали вместе с развитием Windows. Как сама суть Windows, они являются продолжением заложенной в нее цель: способность работать с файлами любых форматов, на любом оборудовании. В отличие от стандартного решения, когда фирма-производитель оболочки (типа Windows) пыталась сама написать различные драйверы для поддержки устройств и различные библиотеки для поддержки форматов многочисленных файлов других пакетов, фирма Microsoft возложила эту обязанность на производителей оборудования и программного обеспечения. Таким образом, последовательное развитие Clipboard-->DDE-->OLE является продолжением воплощения идеи "сам изобрел - сам внедряй". Естественно, наибольшие надежды сейчас возлагаются на OLE (ее новый стандарт OLE.2), так как этот стандарт позволяет включать в себя очень мощные средства, такие как Multimedia. В одном файле может находится не только текст, рисунок, а и даже целый фильм, полностью озвученный и готовый к показу.
Приложение 1. Пример использования OLE технологии
// ObjectWindows - (C) Copyright 1992 by Borland International // // oleclnt.cpp
// Пример Ole Client программы, испльзующей OWL. Она показывает // пример использования Ole functions, и C++ классов .
// Основное окно позволяет пользователю создать paint brush // object, или копировать его из clipboard.
#include
#include "oleclnte.h" #include "oleclntr.h" #include "oleclnt.h"
// статические данные класса LPOLECLIENTVTBL TOwlClient::lpClientVtbl = NULL; int TOleDocWindow::nNextObjectNum = 0;
void TOleApp::InitInstance() {
TApplication::InitInstance();
vcfLink = RegisterClipboardFormat( "ObjectLink" );
vcfNative = RegisterClipboardFormat( "Native" );
vcfOwnerLink = RegisterClipboardFormat( "OwnerLink" );
// comments in owlole.h mention these ole clipboard formats }
// описание функций OWL Object, которые // позволяют хранить описание Ole Object
int FAR PASCAL _export StdCallBack(LPOLECLIENT lpClient,
OLE_NOTIFICATION notification,
LPOLEOBJECT lpObject ) {
return (( PTOwlClient )lpClient)->TOleDocWindowThis->
CallBack( lpClient ,
notification,
lpObject ); }
TOwlClient::TOwlClient( PTOleDocWindow owner , HINSTANCE hInst ) {
TOleDocWindowThis = owner;
if ( !lpClientVtbl )
{
lpClientVtbl = new OLECLIENTVTBL;
if ( hInst == 0 ) {
lpClientVtbl->CallBack = StdCallBack;
} else {
lpClientVtbl->CallBack = (TCallBack) MakeProcInstance( (FARPROC)StdCallBack,
hInst );
}
}
lpvtbl = lpClientVtbl; }
void TOleDocWindow::WMURedraw( RTMessage ) {
bObjectLoaded = TRUE;
InvalidateRect( HWindow, NULL, TRUE );
UpdateWindow( HWindow ); }
#pragma argsused int TOleDocWindow::CallBack( LPOLECLIENT lpOleClient ,
OLE_NOTIFICATION oleNot, LPOLEOBJECT lpOleObject ) {
switch ( oleNot ) {
case OLE_CHANGED:
case OLE_SAVED:
PostMessage( HWindow , WM_U_REDRAW, 0, 0L );
break;
case OLE_CLOSED:
break;
case OLE_QUERY_PAINT:
break;
case OLE_RELEASE:
break;
case OLE_RENAMED:
break;
default:
break;
}
return TRUE; }
void TOleDocWindow::CMAbout( RTMessage ) {
MessageBox( HWindow , "OLE Client Program\n Written using ObjectWindows\nCopyright (c) 1992 Borland",
GetApplication()->Name, MB_OK ); }
// создание новой paint brush void TOleDocWindow::CMPBrush( RTMessage ) {
BackupObject();
bObjectLoaded = FALSE;
lstrcpy( lpszObjectName, GetNextObjectName() );
ret = OleCreate( "StdFileEditing",
(LPOLECLIENT)pOwlClient,
"PBRUSH",
lhClientDoc,
GetApplication()->Name,
&lpObject,
olerender_draw,
0 );
// Создание Ole Object - асинхронная операция.
// Необходимо ожидать его создание, иначе
// могут возникнуть ошибки при обработке
// сообщений этого объекта.
wait( ret , lpObject );
// OleSetHostNames устанавливает имя в сервере OLE.
ret = OleSetHostNames( lpObject, GetApplication()->Name, lpszObjectName );
wait( ret , lpObject ); }
void TOleDocWindow::CMUndo( RTMessage msg) {
if ( lpUndoObject )
if ( lpUndoObject != lpObject )
{
LPOLEOBJECT lpObjectToDelete = lpObject;
lpObject = lpUndoObject;
lpUndoObject = NULL;
ret = OleDelete( lpObjectToDelete );
wait( ret , lpObjectToDelete );
bObjectLoaded = bUndoObjectLoaded;
WMURedraw( msg );
}
}
void TOleDocWindow::CMCut( RTMessage msg) {
CMCopy( msg );
CloseCurrentOle(); }
void TOleDocWindow::CMCopy( RTMessage ) {
if ( OpenClipboard( HWindow ) && EmptyClipboard() )
{
ret = OleCopyToClipboard( lpObject );
check( ret );
CloseClipboard();
} }
void TOleDocWindow::BackupObject() {
if ( lpObject )
{
ret = OleClone( lpObject, (LPOLECLIENT)pOwlClient,
lhClientDoc, GetApplication()->Name,
&lpUndoObject );
wait( ret, lpObject );
lstrcpy( lpszLastObjectName, lpszObjectName );
lstrcpy( lpszObjectName , GetNextObjectName() );
bUndoObjectLoaded = bObjectLoaded;
} }
void TOleDocWindow::CMPaste( RTMessage ) {
if ( OpenClipboard( HWindow ) )
{
BackupObject();
lstrcpy( lpszObjectName, GetNextObjectName() );
ret = OleCreateFromClip( "StdFileEditing",
(LPOLECLIENT)pOwlClient,
lhClientDoc,
lpszObjectName,
&lpObject,
olerender_draw,
0 );
check( ret );
ret = OleSetHostNames( lpObject,
GetApplication()->Name, lpszObjectName );
wait( ret , lpObject );
bObjectLoaded = TRUE;
CloseClipboard();
PostMessage( HWindow , WM_U_REDRAW, 0, 0L );
}
}
LPSTR TOleDocWindow::GetNextObjectName() {
static char buffer[ MAXPATH ];
wsprintf( buffer, "object #%03d", nNextObjectNum++ );
return buffer; }
void TOleDocWindow::Paint( HDC hdc, PAINTSTRUCT _FAR &) {
LPOLEOBJECT lpObjectToDraw = NULL;
if ( bObjectLoaded )
lpObjectToDraw = lpObject;
else if ( lpUndoObject )
lpObjectToDraw = lpUndoObject;
if ( lpObjectToDraw ) {
RECT rect;
GetClientRect( HWindow, &rect );
// Замечание по OleDraw: // OleDraw должен возвращать OLE_ERROR_OBJECT, если // object был нарисован неверно.
ret = OleDraw( lpObjectToDraw , hdc, &rect ,
NULL, 0 );
wait( ret, lpObjectToDraw );
} }
TOleDocWindow::TOleDocWindow( PTWindowsObject parent,
LPSTR title )
: TWindow( parent, title ) {
ret = OLE_OK;
lhClientDoc = 0;
bObjectLoaded = FALSE;
bUndoObjectLoaded = FALSE;
bUndoObjectLoaded = FALSE;
pOwlClient = NULL;
lpObject = NULL;
lpUndoObject = NULL;
strcpy( lpszDocName , "noname.ole" );
*lpszLastObjectName = 0;
*lpszObjectName = 0;
bDefDocName = TRUE; }
void TOleDocWindow::SetupWindow() {
TWindow::SetupWindow();
RegisterClientDoc();
pOwlClient = new TOwlClient( this );
}
void TOleDocWindow::RegisterClientDoc() {
ret = OleRegisterClientDoc(
GetApplication()->Name,
lpszDocName,
0,
&lhClientDoc );
check( ret ); }
void TOleDocWindow::ShutDownWindow() {
CloseCurrentOle();
if ( pOwlClient ) delete pOwlClient;
TWindow::ShutDownWindow(); }
void TOleDocWindow::RegFileName( LPSTR FileName ) {
lstrcpy( lpszDocName , FileName );
ret = OleRegisterClientDoc( GetApplication()->Name,
lpszDocName ,
0,
&lhClientDoc );
check ( ret ); }
void TOleDocWindow::CMActivate( RTMessage ) {
BackupObject();
RECT rect;
GetClientRect( HWindow, &rect );
ret = OleActivate( lpObject , OLEVERB_PRIMARY, TRUE,
TRUE ,
HWindow , &rect );
wait ( ret, lpObject );
PostMessage( HWindow , WM_U_REDRAW, 0, 0L ); }
void TOleDocWindow::WMInitMenu( RTMessage msg ) {
HMENU hMenu = (HMENU)msg.WParam;
WORD wEnableUndo;
if ( (lpObject != lpUndoObject) &&
( lpUndoObject != NULL ))
wEnableUndo = MF_ENABLED;
else wEnableUndo = MF_GRAYED;
EnableMenuItem( hMenu, CM_UNDO ,
wEnableUndo );
EnableMenuItem( hMenu, CM_COPY ,
( bObjectLoaded ? MF_ENABLED : MF_GRAYED ));
EnableMenuItem( hMenu, CM_CUT ,
( bObjectLoaded ? MF_ENABLED : MF_GRAYED ));
ret = OleQueryCreateFromClip( "StdFileEditing",
olerender_draw, 0 );
EnableMenuItem( hMenu, CM_PASTE ,
(( ret == OLE_OK ) ? MF_ENABLED : MF_GRAYED ));
EnableMenuItem( hMenu, CM_ACTIVATE ,
( bObjectLoaded ? MF_ENABLED : MF_GRAYED ));
EnableMenuItem( hMenu, CM_CLEAR ,
( bObjectLoaded ? MF_ENABLED : MF_GRAYED ));
DrawMenuBar( HWindow ); }
LPSTR TOleDocWindow::GetClassName() { return "OLEDOCWINDOW"; }
void TOleDocWindow::GetWindowClass(WNDCLASS _FAR &wc ) {
TWindow::GetWindowClass( wc );
wc.lpszMenuName = "MENU_DOCWINDOW"; }
void TOleDocWindow::CMClear( RTMessage ) {
CloseCurrentOle(); }
void TOleDocWindow::CloseCurrentOle() {
// окончательное сохранение
if ( lpObject ) {
ret = OleDelete( lpObject );
wait( ret , lpObject );
}
if ( lpUndoObject ) {
ret = OleDelete( lpUndoObject );
wait( ret , lpObject );
}
lpObject = lpUndoObject = NULL;
bObjectLoaded = FALSE;
InvalidateRect( HWindow , NULL, TRUE );
UpdateWindow( HWindow ); }
void TOleApp::InitMainWindow() {
MainWindow = new TOleDocWindow(NULL, "OWL OLE Application" ); }
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmd, int nCmdShow) {
TOleApp OleApp ("OleApp", hInstance, hPrevInstance,
lpCmd, nCmdShow);
OleApp.Run();
return (OleApp.Status); }