Протоколи і стандарти об'єктно-орієнтованого програмування
Передмова
Найбільш поширеним мовою програмування останнього десятиліття
безумовно є С. Цьому сприяли такі його особливості, як
лаконічність, потужність, гнучкість, мобільність. Разом з тим, стрімке
ускладнення програм, для реалізації яких застосовуються традиційні
процедурно-оріентірованнние мови програмування і, зокрема С, змушують
говорити про певну кризу в їх використанні, пов'язаний насамперед з
недостатньою надійністю і виразною здатністю.
Подібних недоліків багато в чому позбавлені мови об'єктно-оріентірованнго
програмування (ООП), в основі яких лежить ідея моделювання об'єктів
за допомогою ієрархічно пов'язаних класів. Окремо взятий клас
розглядається як сукупність безлічі даних та операцій над ними, причому
доступ до елементів даних класу можливий тільки за допомогою операцій цього
класу. Встановлення чіткої взаємозалежності між даними та операціями веде до
великий цілісності даних і значно підвищує надійність програм з
порівняно з традиційними мовами програмування. Крім того, ідея
програмування за допомогою класів багато в чому використовує той самий підхід, який
дозволяє людям формувати моделі об'єктів реального світу.
Вперше ідеї ООП були реалізовані в середині 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
< br />
(
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 < br />
(
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, ":");< br />
if (tmTime.minute <10)>
lstrcat (szBuf, "0");
itoa (tmTime.minute,
& szBuf [lstrlen (szBuf)], 10);
lstrcat (szBuf, ":");< br />
if (tmTime.second <10)>
strcat (szBuf, "0");
itoa (tmTime.second,
& szBuf [lstrlen (szBuf)], 10);
szBuf [lstrlen (szBuf)] = '