Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект лекций РСОИ.doc
Скачиваний:
20
Добавлен:
04.11.2018
Размер:
1.93 Mб
Скачать

3.1.1. Здійсненя викликань з типів .Net до типів сом

Для вирішения завдання взаємодії збірок .NET із серверами СОМ необхідно забезпечити деякий проміжний рівень, який був би здатний транслювати виклики .NET у відповідні виклики СОМ (подаючи типи СОМ у вигляді типів .NET). Роль подібного проміжного рівня в .NET виконують служби RCW (Runtime Callable Wrapper – оболонка, що викликається під час виконання).

Кожному класу СОМ (тобто сокласу), до якого здійснюються виклики з клієнта .NET, необхідна своя оболонка — свій RCW. Отже, якщо ми працюємо з одним застосунком .NET, який звертається до трьох сокласів СОМ, нам будуть потрібні три окремі RCW, які будуть перетворювати виклики .NET у виклики СОМ. Зазначимо, що кількість RCW не залежить від того, до якої кількості інтерфейсів у кожному з трьох со-класів будуть здійснюватися звернення. Крім того, модуль RCW використовується також для відстеження посилань на об’єкти СОМ, які створені в .NET збірці.

З агальна схема процесу функціонування RCW зображена на рис. 3.1.

Створюється RCW автоматично за допомогою утиліти tlbimp.exe (від type library importer — імпортер бібліотеки типів). Крім того, зазначимо, що для нормальної взаємодії з клієнтами .NET немає потреби змінювати СОМ-класи. Усю необхідну роботу беруть на себе модулі RCW.

Подання типів СОМ як типів .NET. Завдання подання типів СОМ як типи .NET виконують програмні модулі RCW. Розглянемо на прикладі. Припустимо, що у модулі СОМ визначено метод (такий, що входить до складу інтерфейсу), опис якого подано мовою IDL:

//Визначення методу СОМ в IDL

HRESULT DisplayThisString([in] BSTR msg);

Модуль RCW подає цей метод клієнту .NET наступним чином:

// Подання методу СОМ в С#

void DisplayThisString(string msg);

У більшості типів СОМ (включаючи oleautomation – сумісні типи) існують відповідні типи в .NET. Відповідність типів СОМ типам в .NET подано у таблиці 3.2.

Зазначимо, що при роботі із вказівниками в IDL (наприклад, int* замість Int) такий вказівник буде відображатися у відповідний базовий клас (System.Int32/int).

Таблиця 3.2

Таблиця відповідності типів СОМ типам в .NET

Тип COM (IDL)

Тип .NET

Псевдоним в С#

1

2

3

char, boolean, small

System.SByte

sbyte

wchar_t, short

System.Intie

short

long, int

System. Int32

int

hyper

System. Int64

long

unsigned char, byte

System.Byte

Byte

unsigned short

System. UIntl6

ushort

unsigned long,

unsigned int

System. UInt32

Uint

unsigned hyper

System.UInt64

ulong

single

System.Singte

float

Double

System.Double

double

Таблиця 3.2 (закінчення)

1

2

3

VARIANT_BOOL

Heт

bool

HRESULT

System.Int32

int

BSTR

System.String

string

LPSTR или char*

System.String

string

LPWSTR или wchar_t*

System.String

string

VARIANT

System.Object

object

DECIMAL

System. Decimal

Немає

DATE

System. DateTime

Немає

GUID

System.Guid

Немає

CURRENCY

System. Decimal

Немає

IUnknown*

System.Object

Object

IDispatch*

System.Object

Object

Керування посиланнями на об’єкти. Ще одна важлива функція модуля RCW полягає у відстеженні посилань на соклас СОМ. Зазвичай, при використання сокласу у середовищі СОМ у цей процес залучені клієнти сокласу і власне сам соклас, а керування посиланнями виконується за допомогою викликань методів AddRef() та Release(). Класи СОМ самознищуються тоді, коли більше не існує зовнішніх посилань на них.

Однак у технології .NET задачу кеширування всіх посилань на інтерфейси, викликання методу Release() для сервера СОМ, на який відсутні активні посилання зі сторони клієнтів .NET, виконує модуль RCW. Як результат, клієнтам .NET не потрібно явним чином викликати методи AddRef(), Release() або QueryInterface().

Приховування низькорівневих інтерфейсів СОМ. Оскільки модуль RCW повинен подати типи СОМ для клієнта .NET так, ніби вони є звичайними типами .NET, він має вміти також приховувати низьковірневі інтерфейси СОМ.

Наприклад, коли створено клас СОМ, який підтримує інтерфейс IConnectionPointContainer (а також підлеглий об’єкт або два допоміжних інтерфейси IConnectionPoint), соклас має можливість пересилати повідомлення про події назад клієнтові СОМ.

При створені клієнта СОМ більша частина цього процесу повністю прихована (див. п. 2.10.2). Модуль RCW також приховує багато функцій СОМ при створенні клієнта .NET. Усі низьковірневі інтерфейси СОМ-сервера повністю приховані від клієнта СОМ, тому він може працювати лише з користувацькими інтерфейсами, які реалізовані в сокласі.

Деякі інтерфейси СОМ, які приховує модуль RCW, подано у таблиці 3.3.

Таблиця 3.3

Приховані інтерфейси СОМ

Інтерфейс

Опис

ICIassFactory

Забезпечує незалежний від мови та місцезнаходження метод активації класу СОМ

IConnectionPointContainer IConnectionPoint

Забезпечує можливість відсилання сокласом подій клієнту (зворотні виклики)

IDispatch

IDispatchEx IprovideClassInfo

Використовуються для реалізації пізнього зв'язування

IEnumVariant

Забезпечує можливість представлення сокласом власного набору внутрішніх типів

IErrorInfo ISupportErrorInfo ICreateErrorInfo

Забезпечують можливість для клієнтів та сокласів СОМ генерування повідомлень про помилки та реагування на такі повідомлення

IUnknown

Керує лічильником посилань та дозволяє клієнтові отримувати посилання на конкретний інтерфейс сокласу

Розглянемо простий приклад для демонстрації здійснення викликань зі збірок .NET. Для цього необхідно створити вбудований СОМ-сервер автоматизації SimpleComServer (див. п. 2.8) з СОМ-класом СоСаlс і базовим інтерфейсом ІСоСаlс відповідно, який міст метод Add для знаходження суми двох цілих чисел. Опис цього класу мовою IDL подано нижче:

[odl, uuid(DDA5B80E-8DA4-45DF-B8FF-B6BFFFBCD9E6),

version(1.0), hidden, dual, nonextensible, oleautomation]

interface _CoCalc : IDispatch

{

[id(0x60030000)]

HRESULT Add([in] short x, [in] short y, [out, retval] short *);

};

[uuid(D1D1660X-88D9-4C40-961A-365121C43AF1), version(1.0)]

coclass CoCalc

{

[default] interface _CoCalc;

};

Примітка. Зазначимо, що в наведеному описі мовою IDL інтерфейс має ім’я, що складається із символу «_» +ім’я класу, тобто «_»+CoCalc=_ CoCalc. Це відрізняється від того, як формується ім’я інтерфейсу у середовищі Delphi (а саме: літера «I»+ім’я класу).

Імпорт бібліотеки типів. Перед тим, як звертатися до СОМ-серверу зі збірки .NET, необхідно створити проміжний клас, який буде містити всю необхідну інформацію для передавання запиту СОМ-серверу. Створення такого проміжного класу виконується за допомогою утиліти tlbimp.exe (type library importer — імпортер библиотеки типов). Для цього активізувати режим роботи з командного рядка, перейти в каталог, в якому міститься двійковий модуль СОМ-сервера (тобто DLL), а потім виконати команду такого вигляду:

tlbimp SimpleComServer.dll /out:SimpleAssembly

Тепер можна відкрити створену збірку за допомогою утиліти IDLasm.exe (рис. 3.2). Відзначимо, що всім елементам СОМ-сервера автоматично підібрані еквіваленти .NET.

Рис. 3.2. Типи створеної збірки

Раннє зв'язування з СОМ-класом. Головна задача створеної збірки SimpleAssembly.dll .NET – забезпечити передавання запитів з модулів .NET традиційному СОМ-серверові. Для демонстації її використання створимо СОМ-клієнта (консольний застосунок) на С# під назвою CSharpCalcClient. Потім додамо посилання на створену за допомогою tlbimp.exe збірку SimpleAssembly.dll. Посилання додається через діалогове вікно Add Reference (Додати посилання). Після додавання посилання збірка SimpleAssembly має з’явитися у вікні Solution Explorer у розділі Reference.

Після виконання вищезазначених дій можна скористатися раннім зв’язуванням. У наведеному нижче прикладі програмного коду видно, що для клієнта С# СоСаlс — це звичайний тип .NET, для звертання до якого нічого спеціального не потрібно. Насправді запити з CoCalс будуть передаватися COM-серверу SimpleAssembly.

namespace CSharpCalcClient

{

using System;

// Додано для спрощення доступу до ComCalc

using SimpleAssembly;

public class CalcClient

{

public static int Main(string[] args)

{

// Створюємо об’єкт CoCalc

CoCalc с = new CoCalc();

// Викликаємо метод СОМ-класу CoCalc

Console.WriteLine("30 + 99 is: " + c.Add(30, 99));

return 0;

}

}

}

Як видно, усі члени інтерфейсу за замовчуванням ([default]) сокласа представляються безпосередньо як члени класу CoCalc. Якщо нам буде потрібно явно звернутися до якого-небудь інтерфейсу, це можна зробити за допомогою наступного коду (у прикладі будемо звертатися до того самого _СоСаlс):

public class CalcClient

{

public static int Main(string[] args)

{

// Створюємо об’єкт CoCalc

CoCalc с = new CoCalc();

// Явним чином отримаємо посилання на інтерфейс

_CoCalc icalc = с;

Console. WriteLine(icalc.Add(9, 80));

return 0;

}

}

Раннє зв’язування за допомогою Visual Studio.NET. Раннє зв’язування можна виконати і засобами середовища Visual Studio.NET. Це середовище розроблення дозволяє нам просто додати СОМ-сервер за допомогою діалогового вікна Add Reference (Додати посилання) і закладинки СОМ (рис. 3.3).

У такому випадку автоматично буде викликана утиліта tLbimp.exe, що сторить нову збірку в каталозі Debug (або Release), і посилання на неї буде додано до проекту.

Пізнє зв’язування з СОМ-класом. У просторі імен System.Reflection передбачено способи одержання інформації про типи збірки безпосередньо під час виконання. У СОМ-технології аналогічні можливості реалізуються за допомогою набору стандартних інтерфейсів (ITypeLib, ITypeInfо тощо). Нагадаємо, що зв'язування клієнта із СОМ-сервером під час виконання програми (на противагу зв'язуванню під час компіляції) називається пізнім зв'язуванням.

Процес пізнього зв'язування починається з одержання клієнтом від сокласу посилання на інтерфейс IDispatch. Після чого працює така сама схема, що і при роботі з СОМ-сервером із звичайного Windows-застосунка (див. п. 2.7.3). Нагадаємо основні епати роботи через інтерфейс IDispatch, які будуть необхідні для розуміння алгоритму пізнього зв’язування, що реалізовано у .NET.

Метод GetIDsOfNames() дозволяє клієнтові, що застосовує метод пізнього зв'язування, одержувати числове значення (DISPID), яке використовується для ідентифікації методу, який клієнт збирається викликати. У COM IDL ідентифікатор DISPID для члена класу призначається за допомогою атрибута [Id]. Числове значення, позначене атрибутом [id], метод GetIDsOfNames() повертає клієнтові, який застосовує метод пізнього зв'язування. Отримавши це значення, клієнт може скористатися методом Invoke( ). Цей метод інтерфейсу IDispatch приймає декілька параметрів, один з яких — DISPID методу, що викликається. Крім того, метод Invoke() приймає масив типів COM VARIANT, який являє собою параметри, які треба передати методові, що викликається.

Для класу ComCalc з методом Add() – це масив, що буде містити два значення типу short. Останній параметр методу Invoke() — ще один тип VARIANT, який являє собою значення, що повертається клієнтові (у випадку методу Add() класу ComCalc – це одне значення типу short).

Клієнт .NET, що використовує пізнє зв’язування, застосовує схему викликання методу диспетчерського інтерфейсу через методи інтерфейсу IDispatch за допомогою RCW та типів із простору імен System.Reflection.

Нижче наведено приклад клієнта С#, який використовує пізнє зв’язування для виклику методу Add() СОМ-класу ComCalc. Звернемо увагу на те, що у такому випадку не треба мати проміжну збірку, яка генерується за допомогою утиліти tlbimp.exe:

using System;

using System.Reflection;

public class LateBinder

{

public static int Main(string[] args)

{

//Отримаємо посилання на інтерфейс IDispatch від сокласу

Type calcObj = Type.GetTypeFromProgID( "SimpleComServer.ComCalc") ;

object calcDisp = Activator.Createlnstance(calcObj);

// Створюємо масив параметрів

object[] addArgs = { 100, 34 };

// Викликаємо метод Add() і отримаємо результат

object sum = null ;

sum = calcObj.InvokeMember("Add", BindingFlags.InvokeMethod, null,calcDisp, addArgs);

// Виведення результату на екран

Console.WriteLine("Late bound adding:\nlOO + 24 is: (0}",sum);

return 0;

}

}

Зазначимо декілька особливостей створеної нами збірки, яка використовується для зв’язку з клієнтом .NET.

1. У маніфесті створеної збірки SimpleAssembly.dll у значенні атрибуту ImportedFromTypeLibAttribute прописано шлях до СОМ-сервера. Це означає, що при переміщенні або зміні назви СОМ-сервера, треба наново генерувати збірку SimpleAssembly.dll.

2. Створений на основі СОМ-класу відповідний клас технології .NET автоматично стає нащадком класу System.Object.

Обробка подій СОМ (зворотний виклик). Архітектура моделі подій .NET ґрунтується на делегуванні логіки виконання від однієї частини застосунка до іншої. Для подібного делегування використовуються типи, похідні від System.MulticastDelegate. Клієнт може додавати й видаляти приймачі подій із внутрішнього списку за допомогою перевантажених операторів += та -=.

Коли утиліта tlbimp.exe у процесі перетворення зустрічає інтерфейс [source] (тобто інтерфейс підтримки подій) у бібліотеці типів СОМ-сервера, вона створює набір типів .NET, які послужать оболонкою для низькорівневої архітектури точок з'єднання СОМ. Таким чином в проксі-збірці створюються еквіваленти .NET для подій СОМ.

Приклад. Розглянемо соклас Саr, який описує рух об’єкту автомобіль і в якому визначений наступний вихідний інтерфейс, що містить подію Exploded (опис подано мовою IDL):

dispinterface _ICarEvents {

properties:

methods:

[id(1), helpstring("method Exploded")]

HRESULT Exploded([in] BSTR deadMsg); };

Інтерфейс _ICarEvents використовується для реалізації механізму генерування подій на основі інтерфейсів диспетчирування (докладні інструкції щодо створення серверів автоматизації, що підтримують обробку подій на основі інтерфейсів диспетчирування, містяться у п. 2.10). Також клас СоСаr містить інтерфейс за замовчуванням [default], що має ім’я ІСаr, та метод SpeedUp, який генерує подію Exploded при перевищенні значення властивості швидкість (понад 150 км/год), яка повідомляє про вибух автомобіля. Текст методу мовою ObjectPascal середовища Delphi наведено нижче:

procedure TCar.SpeedUp(pSpeed: Integer);

begin

if pSpeed > 150 then

begin

FEvents.Exploded(“автомобіль вибухнув!”);

end;

end;

При генерування проміжної збірки для підключення розробленого сервера СОМ до збірки .NET утиліта tlbimp.exe створює набір типів для точного подання системи подій СОМ-сервера (архітектури точок з'єднання) у системі подій .NET. Ці типи наведені в таблиці 3.4.

Таблиця 3.4

Відповідність згенерованих елементів системи подій .NET елементам системи подій СОМ

Згенерований тип

(для інтерфейсу _CarEvents [source])

Опис

ICarEvents

Інтерфейс .NET – аналог вихідного інтерфейсу СОМ-сервера. Зазвичай безпосередньо не використовується

ICarEvents_Event

Інтерфейс .NET, що визначає члени для додавання і видалення методів із вбудованого списку System.MulticastDelegate. Зазвичай також безпосередньо не використовується

ICarEvents_ExplodedEventHandler

Делегат .NET (тип, похідний від System.MulticastDelegate). Обробник події повинен обов'язково повертати значення типу int. Цей тип відповідає вихідній події СОМ

ICarEvents_SinkHelper

Цей згенерований клас реалізує вихідний інтерфейс в об'єкті-приймачі .NET. Цей клас привласнює значення cookie, що згенероване типом СОМ, змінній m_dwCookie. Крім того, в цьому класі передбачена внутрішня змінна m_ExplodedDelegate, що представляє вихідний інтерфейс (ICarEvents_ExplodedEventHandler)

Крім генерованих типів, поданих у табл. 3.4 для системи подій вносяться зміни й у визначення класу .NET СагClass, у нього з’являється подія Exploded.

У класі .NET СагClass передбачено два методи, визначені як private, які обслуговують з'єднання із джерелом подій СОМ – add_Х і remove_Х (рис. 3.4).

Рис 3.4. Події СОМ обслуговуються за допомогою набору з двох функцій

У коді інструкцій IL для методу add_Exploded можна виявити, що при його виконанні створюється новий об'єкт ICarEvents_SinkНelper. Після цього проксі-збірка отримує посилання на інтерфейс IConnectionPoint з СОМ-сервера Cаг, викликає метод Advise() і кешує значення cookie, що повертається.

Захоплення події COM проксі-збіркою .NET. Розглянемо на прикладі як збірка .NET реагує на подію СОМ-сервера. Цей процес майже ідентичний процесу роботи з делегатами .NET:

//Підключаємо бібліотеку СОМ-севера, яка в нашому прикладі //зветься CarServer

using CarServer;

public class CoCarClient

{

// Цей mетод буде викликано при виникненні події на СОМ-

// сервері. Метод повинен мати набір параметрів ідентичний //тому, який визначено для події

public static int ExplodedHandler(String msg)

{

Console.Writeline("\nCar says:(COM Events)\n->"+msg+"\n");

return 0;

}

public static int Main(str1ng[] args)

{

CarСlass viper = new CarClass();

// Встановлюємо делегат для події Exploded

viper.Exploded += new ICarEventsЕxplodedEventHandler(ExplodedHandler);

// Наступні дії змушують СОМ-сервер згенерувати подію

for (int i=0; i < 5; i++)

{

try

{

//Метод SpeedUp генерує подію Exploded

viper.SpeedUp(150);

Console. WnteUne("->Curr speed is: " + viper. GetCurSpeedO);

}

catch (Exception ex)

{

Console. WnteLine("->CQM error!"+ex.Message+"\n");

}

}

}

}

Примітка. Делегат для події Exploded встановлюється за допомогою перевантаженого оператора += таким рядком:

viper.Exploded += new ICarEventsЕxplodedEventHandler(ExplodedHandler);

Зверніть увагу, що в дужках зазначається назва методу, який буде обробником події Exploded, тобто буде викликаний при виникненні цієї події. Опис параметрів такого методу має співпадати з описом параметрів події!

Результат роботи розробленого клієнта .NET подано на рис 3.5.

Рис. 3.5. Звернення з клієнта С# до сервера СОМ