Протоколы и стандарты объектно-ориентированного программирования
OLE
DDE
Clipboard
РЕФЕРАТ
по дисциплине "Теория проектирования трансляторов"
на тему Протоколы и стандарты объектно-ориентированного программирования
Выполнен студентом группы АП-1-91 Маслюковым А.О.
СОДЕРЖАНИЕ
Предисловие
MS Windows и новый метод разработки программ
Динамический обмен данными
OLE-технология
Заключение
Список литературы
Приложение 1.Пример использования OLE-технологии
Предисловие
Наиболее распространенным языком программирования последне-
го десятилетия безусловно является С. Этому способствовали такие
его особенности, как лаконичность, мощность, гибкость, мо-
бильность. Вместе с тем, стремительное усложнение приложений, для
реализации которых применяются традиционные процедурно-ориентиро-
ваннные языки программирования и, в частности С, заставляют гово-
рить об определенном кризисе в их использовании, связанном преж-
де всего с недостаточной надежностью и выразительной способностью.
Подобных недостатков во многом лишены языки объектно-ориен-
тированнго программирования (ООП), в сонове которыхлежит идея мо-
делирования объектов посредством иерархически связанных классов.
Отдельно взятый класс рассматривается как совакупность множества
данных и операций над ними, причем доступ к элементам данных
класса возможен только посредством операций этого класса. Уста-
новление четкой взаимозависимости между данными и операциями ве-
дет к большой целостности данных и значительно повышает надеж-
ность программ по сравнению с традиционными языками программиро-
вания. Кроме того, идея программирования с помощью классов во
многом использует тот же подход, который позволяет людям формиро-
вать модели объектов реального мира.
Впервые идеи ООП были реализованы в середине 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, они являются продолжением заложенной в нее цель:
cпособность работать с файлами любых форматов, на любом оборудовании.
В отличие от стандартного решения, когда фирма-производитель обо-
лочки (типа Windows) пыталась сама написать различные драйверы
для поддержки устройств и различные библиотеки для поддержки
форматов многочисленных файлов других пакетов, фирма Microsoft
возложила эту обязанность на производителей оборудования и
программного обеспечения. Таким образом, последовательное разви-
тие Clipboard-->DDE-->OLE является продолжением воплощения
идеи "сам изобрел - сам внедряй". Естесственно, наибольшие на-
дежды сейчас возлагаются на OLE (ее новый стандарт OLE.2), так как
этот стандарт позволяет включать в себя очень мощные средства, такие
как Multimedia. В одном файле может находится не только текст,
рисунок, а и даже целый фильм, полностью озвученный и готовый
к показу.
СПИСОК ЛИТЕРАТУРЫ
1. Гладков С.А. Фролов Г.В. Программирование в Microsoft Windows:
В 2-х частях. М.:"ДИАЛОГ-МИФИ", 1992.
2. Фойц С. Windows 3.1 для пользователя. Пер. с немецкого
Киев:BHV, 1992.
3. Microsoft Windows Software Development Kit. Version 3.
Programmer's Reference, Programming Tools, Windows Extensions.
4. Charles Petzold. Programming Windows. Microsoft Press.
5. Библия Windows 3.X. М.: И.В.К. - Софт, 1992.
6. Borland C++. Usres manual.
Приложение 1. Пример использования OLE технологии
// ObjectWindows - (C) Copyright 1992 by Borland International
//
// oleclnt.cpp
// Пример Ole Client программы, испльзующей OWL. Она показывает
// пример использования Ole functions, и C++ классов .
// Основное окно позволяет пользователю создать paint brush
// object, или копировать его из clipboard.
#include <owl.h>
#include <listbox.h>
#include <string.h>
#include <edit.h>
#include <bwcc.h>
#include <filedial.h>
#include <ole.h>
#include <shellapi.h>
#pragma hdrstop
#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);
}