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

3.1.2. Звернення клієнта сом до збірки .Net

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

Класичний СОМ-сервер активізується за допомогою спеціального програмного модуля – Service Control Manager (SCM). SCM активно працює з реєстром операційної системи, отримуючи з нього інформацію про ProgID, CLSID, IID тощо. Але інформація про збірки .NET взагалі не заноситься до системного реєстру. Отже, для створення збірки .NET, до якої зможе звертатися клієнт СОМ, нам необхідно буде виконати наступні дії:

• зареєтрувати збірку в реєстрі, щоб її міг знайти модуль SCM;

• створити бібліотеку типів COM (*.tlb), яка основана на метаданих .NET і через яку клієнти СОМ зможуть взаємодіяти з типами .NET;

• помістити збірку до каталогу, в якому розташований клієнт СОМ, або встановити збірку в глобальному кеші збірок (Global Assembly Cache – GAC, спеціальному каталозі для збірок загального доступу).

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

У CCW передбачені засоби керування оперативною пам'яттю, тобто відстеження створюваних об'єктів і видалення їх з оперативної пам'яті, коли потреба в них відпаде. Це обов'язкова умова, оскільки модулі CCW є справжніми типами СОМ і тому вони повинні вміти правильно реагувати на виклики методів AddRef() та Release(), що надходять від клієнтів. Коли клієнт СОМ повністю звільнить всі ресурси, модуль CCW автоматично звільнить посилання на використовувані ним типи .NET, надаючи можливість збирачеві сміття1 зайнятися своєю справою.

Модулі CCW автоматично реалізують множину службових інтерфейсів СОМ, щоб створити у клієнтів СОМ повну ілюзію того, що вони мають справу з справжнім сокласом. Ці стандартні інтерфейси подані в таблиці 3.5.

Крім них модулі CCW, звичайно ж, реалізують і користувацькі інтерфейси типів .NET, включаючи так званий інтерфейс класу.

Таблиця 3.5

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

Службовий інтерфейс СОМ

Опис

IConnectionPointContainer

IConnectionPoint

Використовуються для реалізації точок з'єднання СОМ, якщо в типі .NET визначені будь-які події

IEnumVariant

Реалізується, якщо тип .NET підтримує інтерфейс IEnumerable

ISupportErrorInfo

IErrorInfo

Дозволяють передавати об'єкти помилок СОМ

ITypeInfo

IProvideClassInfo

Дозволяють клієнтові СОМ звертатися до інформації типів СОМ модуля CCW. Насправді клієнтові СОМ передається інформація, яка згенерована на основі метаданих .NET

IUnknown

IDispatch

IDispatchEx

Найважливіші інтерфейси СОМ, які забезпечують можливість застосування раннього та пізнього зв'язування до типів .NET. Інтерфейс IDispatchEx реалізується модулем CCW у тому випадку, якщо тип .NET реалізує інтерфейс IExpando

Поняття інтерфейсу класу. У класичному СОМ єдиний спосіб, за допомогою якого клієнт СОМ може взаємодіяти з об'єктом СОМ, – це одержання посилання на інтерфейс. Однак деякі мови програмування, що підтримують технологію СОМ (у тому числі, наприклад, Visual Basic 6.0), приховують цю особливість від програмістів — в основному, для простоти сприйняття. Visual Basic автоматично створює інтерфейс за замовчуванням ([default]) для кожного сокласу у двійковому модулі СОМ. У цей інтерфейс вміщуються всі члени, оголошені як рublic.

На відміну від типів СОМ, типи .NET можуть взагалі обійтися без інтерфейсів. Однак, якщо нам буде потрібно забезпечити клієнтам СОМ можливість звертатися до збірки .NET, нам необхідно буде враховувати той факт, що клієнти СОМ можуть працювати тільки через посилання на інтерфейс. Тому в проміжному модулі CCW створюється спеціальний інтерфейс класу, у який вміщуються всі властивості, методи, поля і події, які визначені в типі .NET як publiс. Можна сказати, що в модулях CCW використовується той самий підхід, що й у Visual Basic 6.0.

Щоб втрутитися у процес автоматичного створення інтерфейсу класу, можна використати атрибут ClassInterface, який не є обов'язковим. За замовчуванням для інтерфейсу класу в модулі CCW обирається тип диспінтерфейсу (тобто інтерфейсу, похідного від IDispatch). Отже, якщо залишити все за замовчуванням, всім клієнтам СОМ, які мають намір викликати методи зі збірки .NET, треба буде використовувати пізнє зв'язування! Щоб змінити тип створюваного інтерфейсу класу, використовується атрибут ClassInterface. Він може приймати значення з перерахування ClassInterfaceType (таблиця 3.6).

Таблиця 3.6

Значення перерахування ClassInterfaceType

Значення

Опис

AutoDispatch

Визначає, що для інтерфейсу класу буде обрано тип диспінтерфейсу (тобто інтерфейсу, нащадка від IDispatch)

AutoDual

Визначає, що для інтерфейсу класу буде обраний тип dual

None

Інтерфейс класу взагалі створюватися не буде

Створення збірки .NET як сервера СОМ. Як СОМ-сервер будемо розглядати біблиотеку класів С# (File→New→Class Library), в якій визначено один клас — CSharpCalс (ім’я інтерфейсу цього класу складається з префіксу у вигляді символа підкреслення та власне імені класу – _CSharpCalс), що має два методи — Add() (для додавання чисел) та Subtract() (для віднімання). Крім того, цей клас буде реалізовувати інтерфейс IAdvancedMath, в якому передбачено методи для виконання множення і ділення. Для визначення типу інтерфейсу класу застосовується атрибут ClassInterface, який у наведеному прикладі буде мати значення ClassInterfaceType.AutoDual:

namespace DotNetClassLib

{

using System;

using System.Runtime.InteropServices;

public interface lAdvancedMath

{

int Multiply(int x, int y);

int Divide(int x, int y);

}

[ClassInterface(ClassInterfaceType.AutoDual)]

public class CSharpCalc : lAdvancedMath

{

public CSharpCalc(){}

public int Add(int x, int y) {return x + y;}

public int Subtract(int x, int y) {return x - y;}

int lAdvancedMath.Multiply(int x, int y) {return x*y;}

int lAdvancedMath.Divide(int x, int y)

{

if(y == 0)

//Виключення буде перехоплено як об’єкт помилки СОМ

throw new DivideByZeroException();

return x / у;

}

}

}

Генерування бібліотеки типів і реєстрація типів .NET. Відкомпілюймо проект для отримання готової збірки.

Наступним кроком необхідно створити для цієї збірки проміжний модуль CCW та занести інформацію про нього у реєстр як про СОМ-сервер. Це можна зробити двома способами. Перший спосіб — скористатися утилітою regasm.exe, яка входить у поставку .NET SDK. За замовчуванням цей спосіб тільки заносить дані про реєстрації модуля CCW до реєстру, однак якщо вказати параметр /tlb, буде згенерована необхідна бібліотека типів (тобто модуль CCW):

regasm DotNetClassLib.dll /tlb:simpledotnetserver.tlb

Другий спосіб — створити бібіліотеку типів за допомогою утиліти tlbexp.exe, а інформацію про створений модуль CCW занести до реєстру за допомогою утиліти regasm.exe. У результаті, незалежно від способу генерування модуля CCW та реєстрації збірки, буде створено проміжний модуль CCW для збірки .NET, а інформація про нього буде занесена до реєстру операційної системи.

Важливо! Для того, щоб класи, що визначені у збірці .NET, було вміщено у згенеровану бібліотеку типів, необхідно встановити прапорець Make Assembly COM-Visible. Це можна зробити виконавши такі дії:

  • викликати вікно властивостей проекту (виділити Solution Explorer→ DotNetClassLib (ім’я проекту), правою кнопкою миші викликаємо меню, що спливає, в якому обираємо пункт Properties);

  • на відкритій вкладинці викликати вікно інформації збірки (кнопка Assembly Information);

  • встановити прапорець Make Assembly COM-Visible.

Це також можна зробити, якщо відредагувати модуль AssemblyInfo.cs, встановивши значення параметра збірки ComVisible(true):

// Setting ComVisible to false makes the types in this assembly not visible

// to COM components. If you need to access a type in this assembly from

// COM, set the ComVisible attribute to true on that type.

[assembly: ComVisible(true)]

Аналіз створеної бібліотеки типів. Щоб зрозуміти, що було автоматично згенеровано, необхідно завантажити бібліотеку типів (файл simpledotnetserver.tlb) в OLE/COM Object Viewer (утиліта olewiew.exe). В Object Viewer можна знайти визначення IDL для інтерфейсу класу CShаrрСа1с (він має назву _CSharpCalc):

[uuid(AA165958-53F3-3129-83AE-7AE174FE923F), hidden, dual, nonextensible.

custom( {0F21F359-AB84-41E8-9A78-36D110E6D2F9},

"DotNetClassLib.CSharpCalc")]

Interface _CSharpCalc : Idispatch

{

// Методи System.Object!

[id(00000000). propget] BSTR ToStringO;

[id(0x60020001] VARIANT_BOOL Equals([in] VARIANT obj); [id(0x60020002)] long GetHashCode():

[id(0x60020003)] _Type* GetTypeO;

// Методи iнтерфейсу класу

[id(0x60020000)] HRESULT Add([in ] long x, [in] long y, [out, retval] long* pRetVal);

[id(0x60020001] HRESULT Subtract [in] long x, [in] long y, [out, retval] long* pRetVal);

Відповідно до заданого значення атрибуту ClassInterfасе інтерфейс за замовчуванням визначено як [dual]. Зверніть увагу, що всім членам автоматично призначені ідентифікатори DISPID. Для інтерфейсу класу також явним чином представлені записи для всіх членів, які успадковуються від System.Object.

Зазначимо, що в інтерфейсі класу не визначені ті члени, які відносяться до інтерфейсу IAdvancedMath. Причиною цьому є те, що цей інтерфейс реалізовано явним чином, однак у згенерованій бібліотеці типів, зазвичай, міститься і визначення IDL для цього користувацького інтерфейсу:

interface IAdvancedMath : IDispatch

{

[id(0x60020000)] HRESULT Multiply([in] long x, [in] long y, [out, retval] long* pRetVal);

[id(0x60020001] HRESULT Divide([in] long x, [in] long y, [out, retval] long* pRetVal);

};

Інтерфейс _Object. Згенерований код IDL містить інтерфейс з ім’ям _Object. У цьому інтерфейсі можна знайти всі члени System.Object:

[uuid(98417C7D-32E8-3FA0-A548-0F0B2EF8E91F), hidden, dual, nonextensible< custom({0F21F359-AB84-41EB-9A78-36D110E6D2F9}, "System.Object")]

dispinterface _Object

{

properties:

methods:

[id(00000000), propget] BSTR ToString();

[(id(0x60020001)] VARIANT_ВOOL Equals([in] VARIANT obj);

[(id(0x60020002)] long GetHashCode();

[(id(0x60020003)] _Type* GetType();

};

У визначенні сокласу IDL підтримка цього інтерфейсу додається автоматично:

coclass CSharpCalc {

interface IManageObject:

[default] interface _CSharpCalc;

interface _Object;

interface IAdvancedMath;

};

Згенерований текст бібліотеки. Розглянемо в згенерованому модулі CCW текст бібліотеки (library statement). У класичному СОМ він використовується для представлення усіх типів, що визначені в IDL і мають бути вміщені у двійковий файл *.tlb. У цьому двійковому файлі містяться всі типи, інформація про які вміщена в IDL, і саме цей файл забезпечує можливості взаємодії між різними мовами програмування. Утиліта tlbexp.exe при створенні модуля CCW генерує текст бібліотеки на основі простору імен .NET.

Відмітимо, що крім стандартної інформації про типи OLE, у текст бібліотеки також вміщується інформація про mscorlib.dll (головну збірку бібліотеки базових класів .NET) та mscoree.dll (бібліотеку середовища виконання .NET):

[uuid(5C202075-222E-30A4-BF50-4EFC20DDCD27), version(l.O) ]

// На основі назви прострору імен

library DotNetClassLib

{

//TLib: {BED7F4EA-1A96-11D2-8F08-00A0C9A686D}

importlib("mscorlib.tlb");

...

importlib("mscoree.tlb");

...

}

Перегляд записів у реєстрі. Перед створенням клієнта СОМ, який буде звертатися до збірки .NET, треба впевнитися, що необхідні для неї записи (точніше, для проміжного модуля CCW) внесені до реєстру. Для цього необхідно здійснити пошук у реєстрі за назвою збірки та отримати Prog ID для кожного сокласу, визначеного у збірці. За допомогою отриманого ProgID можна знайти значення CLSID.

Коли клієнт СОМ звертається із запитом на активацію СОМ-сервера до середовища виконнання COM (Service Control Manager, SCM), SCM звертається до розділу реєстра HKCR\CLSID для визначення місцязнаходження зареєстрованого СОМ-сервера. У CLSID СОМ-сервера існує декілька підкаталогів реєстру, один з яких – InprocServer32. У цьому підкаталогові має міститися шлях до двійкового файла СОМ-сервера, завантаження якого SCM виконує за запитом клієнта. Однак для збірки .NET DotNetClassLib.dll замість двійкового файла вказано шлях до файла середовища виконання .NET — mscoree.dll.

Слід звернути увагу також на параметр Assembly у тому самому підкаталозі InprocServer32. Значення цього параметру – повне ім’я збірки.

Утиліта regasm.exe вносить до реєстру й інші записи. Наприклад, окільки для типів .NET завжди використовуються інтерфейси, що є похідними від IDispatch, кожен інтерфейс налаштований на використання oleaut32.dll. Інформація про типи вноситься до розділу реєстра HKCR\TypeLib.

Створення клієнта у Visual Basic 6.0. Оскільки маємо збірку .NET та проміжний модуль CCW для неї, можемо створювати клієнтів СОМ, які будуть до неї звертатися. Для початку створимо кієнт на Visual Basic 6.0. Для цього необхідно:

  • створити новий проект Visual Basic за допомогою шаблона StandardEXE;

  • встановити посилання на згенеровану бібліотеку типів — модуль CCW;

  • зберегти проект на диску і скопіювати збірку С# у той самий каталог, в якому будемо створювати клієнтський застосунок Visual Basic (або встановити збірку в GAC).

Для передавання інформації збірці .NET в клієнті буде використовуватися кнопка Calc (об’єкт Button). Результати обчислень, що повертаються збіркою .NET, будуть виводитися за допомогою об'єктів MsgBox. Слід звернути увагу на наявність генерування виключення DivideByZeroException на випадок, якщо другий переданий параметр методу Divide() інтерфейсу IAdvancedMath буде дорівнювати нулю. Отже, код клієнта повинен буде приймати виключення .NET як об'єкт помилки СОМ. Власне код клієнта наведений нижче (зверніть увагу, що використовуються деякі успадковані методи, визначені в інтерфейсі _Object):

Private Sub btCalc_Click()

On Error GoTo OOPS:

'Створюємо об’єкт .NET та додаємо два числа

Dim о As New CSharpCalc

MsgBox о.Add(30, 30), , "Adding"

' Викликаємо деякі методи інтерфейсу _Object

MsgBox o.ToString, , "To String"

MsgBox o.GetHashCode, , "Hash code"

Dim t As Object

Set t = o.GetType()

MsgBox t, , "Type"

' Отримаємо посилання на користувацький інтерфейс

' та змушуємо спрацювати виключення

Dim i As IAdvancedMath

Set i = 0

MsgBox i.Multiply(4, 22), , "MultiPly"

MsgBox i.Divide(20, 2), , "Divide"

MsgBox i.Divide(20, 0) ' Генеруємо помилку

OOPS:

' Виводимо інформацію про виключення

MsgBox Err.Description, , "Error!"

End Sub

Важливо! Visual Basic 6.0 не дозволяє одержати доступ до інтерфейсу _Турe, що повертається за допомогою методу _0bject.GetType(), оскільки цей інтерфейс позначений як схований ([hidden]). Замість цього треба буде обмежитися набагато скромнішимим можливостями System.Object.

Деякі особливості відображення типів .NET у СОМ. Загальна система типів (Common Type System, CTS) допускає застосування деяких можливостей, які в СОМ не передбачені. Наприклад, класи С# можуть підтримувати будь-яку кількість конструкторів, перевантажених операторів, перевантажених методів. Крім того, для класів С# може використовуватися класичне спадкування. Однак жодна із цих технологій програмування не може використовуватися в СОМ. Тому переведення типів .NET, в яких використовуються такі можливості, у типи СОМ при створенні проміжного модуля CCW утилітою tlbexp.exe відбувається з деякими особливостями.

Розглянемо відображення типів .NET у СОМ на прикладі. Припустимо, що в нас є бібліотека коду С#, у якій визначений один базовий клас і один похідний клас. У базовому класі є змінні, визначені як publiс (поля), набір конструкторів та єдиний віртуальний метод:

[ClassInterface(ClassInterfaceType.AutoDual)]

public class BaseClass

{

// Змінні

private int memberVar;

public string fieldOne;

// Конструктори

public BaseClass(){}

public BaseClass(int m, string f)

{ memberVal = m; fieldOne = f; }

// Віртуальний метод

public virtual void VirMethodO

{ Console.WriteLine("Base VirMethod impl");}

}

Похідний клас заміщує віртуальний метод і визначає ще один метод, який кілька разів перевантажений:

[ClassInterface(ClassInterfaceType.AutoDual)]

public class DerivedClass : BaseClass

{

// Змінна

public float fieldTwo;

// Конструктор

DerivedClass(int m, string f) : base(m, f) {}

// Заміщений метод

public override void VirMethodO

{

Console.WriteLinet("Derived VirMethod impl");

base.VirMethod();

}

// Перевантажені члени

public void SomeMethod(){}

public void SomeMethod(int x){)

public void SomeMethod(int x, object o){}

public void SomeMethod(int x, float f){}

}

Згенеруємо за допомогою утиліти tlbexp.exe файл бібліотеки типів і проаналізуємо його.

Почнемо з визначення сокласу для базового класу .NET (який в нашому прикладі зветься BaseClass). Для цього завантажимо згенерований файл *.tlb в OLE/COM Object Viewer (утиліта oleview.exe) і знайдемо там інформацію про соклас:

coclass BaseClass

{

interface IManagedObject;

[default] interface _BaseClass: interface _Object;

};

Цікавим є визначення інтерфейсу класу:

interface _BaseClass : IDispatch

{

// Методи об’єкту...

[id(0x60020004)] HRESULT VirMethodO:

[id(0x60020005), propget] HRESULT fieldOne([out, retval] BSTR* pRetVal);

[id(0x60020005), propput] HRESULT fieldOne([in] BSTR pRetVal); };

Як видно, змінні, оголошені як publiс (тобто поля), подані у згенерованій збірці як властивості СОМ. Це цілком зрозуміло, оскільки клієнти СОМ ніколи не одержують посилання на об'єкти і їм завжди доводиться працювати через посилання на інтерфейси. А тепер подивимося, що створено для похідного класу.

У СОМ не передбачено класичного успадкування між класами, тому просто перенести відношення "is-a" між базовим та похідним класом неможливо. Замість цього утиліта tlbexp.exe реалізує в сокласі, створеному для похідного класу, інтерфейс базового класу:

coclass DerivedClass

{

interface IManagedObject;

[default] interface _DerivedClass: interface _BaseClass;

interface _Object;

};

Такий підхід гарантує, що у похідному класі будуть реалізовани ті самі можливості, що і в базовому. Інтерфейс класу для класу DerivedClass також має свої особливості.

Оскільки у визначенні С# класу DerivedClass було передбачено перевантаження одного з методів, а в СОМ перевантаження методів взагалі не передбачено, то tlbexp.exe замість одного перевантаженого методу SomeMethod() генерує чотири, до того ж три з них — із числовими суфіксами, тобто іменами, які їм не давали розробники класу:

interface _DerivedClass : IDispatch

{

// Методи інтерфейсу _Object...

// Методи, що "успадковані від базового класу"

[id(0x60020004)] HRESULT VirMethod():

[id(0x60020005), propget] HRESULT fieldOne([out, retval] BSTR* pRetVal);

[id(0x60020005). propput] HRESULT f1eldOne([in] BSTR pRetVal);

// "Перевантажений метод"

[id(0x60020007)] HRESULT SomeMethod();

[id(0x60020008)] HRESULT SomeMethod_2([in] long x);

[id(0x60020009)] HRESULT SomeMethod_3([in] long x,

[in] VARIANT o);

[id(0x6002000a)] HRESULT SomeMethod_4([in] long x,

[in] single f);

// Змінна FieldTwo, визначена як public (поле)

[id(0x6002000b), propget] HRESULT fieldTwo([out, retval] single* pRetVal);

[id(0x6002000c), propput] HRESULT fieldTwo([in] single pRetVal);

};

Подібна ситуація спостерігається і при перетворенні вкладених просторів імен, абстрактних базових класів, структурних типів (перерахувань і структур тощо). Однак розгляд цих питань виходить за межі посібника, а додаткову інформацію можна одержати за допомогою електронної документації і шляхом аналізу згенерованих даних для типів .NET модулів CCW.

Однак існує можливість керування процесом генерування коду IDL або вплинути на те, що робить утиліта tlbexp.exe. При використанні утиліти tlbimp.exe для генерування проксі-зборки в маніфест цієї збірки потрапляє велика кількість атрибутів. Якщо створюється збірка .NET, до якої, як планується, будуть активно звертатися традиційні клієнти СОМ, можна використовувати множину атрибутів (один з них - ClassInterfaceAttribute – вже розглядався раніше). Зазвичай ці атрибути використовуються для того, щоб керувати процесом створення модуля CCW утилітою tlbimp.exe.

Розглянемо на прикладі. Створимо новий простір імен (AttribDotNetObjects), у якому буде визначений єдиний інтерфейс IBasicMath і єдиний клас Calc. Зверніть увагу на те, як будуть використовуватися в цьому просторі імен атрибути. За допомогою їх можна явно визначити значення GUID для типів, що генеруються, і втрутитися в процес створення аналогів СОМ для інтерфейсу IBasicMath і методу Add(). Крім того, атрибути будуть використані й для керування процесом подання двох статичних функцій, визначених у класі Calc. Нижче наведено код простору імен з атрибутами:

namespace AttribDotNetObjects

{

using System;

using System.Runtime.InteropServices;

using System.Windows.Forms;

//Для цього інтерфейса .NET застосовуємо декілька атрибутів //Вони будуть використані утилітой tlbexp.exe при генерування //модуля CCW

[GuidAttribute("47430E06-718D-42C6-9E45-78A99673C43C"),

InterfaceTypeAttributeCComInterfaceType.InterfacelsDual)]

public interface IBasicMath

{

[Displd(777)] int AddCint x, int y);

}

[GuidAttribute("C08F4261-C0C0-46AC-87F3-EOE306984ACC")]

public class DotNetCalc : IBasicMath

{

public DotNetCalc(){}

public int AddCint x, int y) { return x + y; }

// Цей атрибут означає, що данний метод повинен викликатися

// при реєстрації модуля CCW

[ComRegisterFunctionAttribute]

public static void AddExtraRegLogiс(string regLoc)

{

// Виконуємо додаткові дії при реєстрації MessageBox.Show("Inside AddExtraLogic f(x)",".NET assembly says:");

}

// Цей атрибут означає, що данний метод буде викликано при

// видаленні реєстрації модуля CCW

[ComUnregisterFunctionAttribute]

public static void RemoveExtraRegLogiс(string regLoc)

{

// Виклонуємо дії при видаленні реєстрації

MessageBox.Show(“Inside RemoveExtraRegLogic f(x)", ".NET assembly says:");

}

}

}

При створенні для наведеної вище збірки проміжного модуля CCW за допомогоюю утиліти tlbexp.exe можна побачити, як подіяли атрибути. Для IID і CLSID було використано явним чином вказані значення, а інтерфейс IBasicMath визначено як [dual]. Нижче наведено код IDL для інтерфейсу IBasicMath:

// У збірці використовувалися атрибути:

[GuidAttribute("47430E06-718D-42C6-9E45-78A99673C43C"), InterfaceTypeAttribute(CoraInterfaceType.InterfacelsDual)]

// Отриманий код IDL:

[odl, uuid(47430E06-718D-42C6-9E45-78A99673C43C), dual, oleautomation, custom({0F21F359-AB84-41E8-9A76-36D110E6D2F9}. "AttribDotNetObjects.IBasicMath")]

interface IBasicMath : IDispatch

{

[id(0x00000309)] // У збірці визначено: [Displd(777)]

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

};

Якщо для інтерфейсу IBasicMath у коді С# змінити значення атрибута InterfaceTypeAttribute наступним чином:

[GuidAttribute("47430E06-718D-42C6-9E45-78A99673C43C"),

InterfaceTypeAttribute(ComInterfaceType.InterfacelsUnknown)]

public interface IBasicMath

int Add(int x, int y):

}

то і код IDL також зміниться:

//Інтерфейс тепер не [dial]!

[odl, uuid(47430E06-718D-42C6-9E45-78A99673C43C), oleautomation, custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "AttribDotNetObjects.IBasicMath")]

interface IBasicMath : IUnknown

{

// Визначення DispID немає

HRESULT _stdcall Add([in] long x, [in] long y, [out, retval] long* pRetVal);

};

Керування реєстрацією проміжного модуля CCW. Розглянемо докладніше використання двох атрибутів ComRegisterFunctionAttribute та ComUnregisterFunctionAttribute. Як відомо, в класичних СОМ-серверах визначено дві функції — DllRegisterServer і DllUnregisterServer. Ці функції задіяні у процесі реєстрації сервера СОМ у реєстрі та видаленні такої реєстрації. У збірках .NET таких функцій немає.

Якщо ж треба буде виконувати реєстрацію проміжного модуля CCW якимсь спеціальним способом, можна визначити статичні методи з вищевказаними атрибутами. Наприклад, при реєстрації проміжного модуля для збірки з наведеного вище прикладу отримаємо повідомлення “Inside AddExtraLogic f(x)".

Необхідно відзначити два моменти, пов'язані із застосуванням цих атрибутів. По-перше, назви методів, позначених цими атрибутами, можуть бути якими завгодно, але метод, позначений атрибутом ComRegisterFunctionAttribute, повинен отримувати параметр типу String. По-друге, якщо визначено один метод, позначений атрибутом ComRegisterFunctionAttribute, то треба визначити і другий метод для ComUnregisterFunctionAttribute.

Контрольні запитання та завдання:

  1. Які види сумісності з успадкованим кодом підтримує платформа .NET?

  2. Назвіть основні типи простору імен System.Runtime.InteropServices та їх призначення.

  3. Для чого використовуються служби RCW?

  4. Опишіть загальну схему процесу функціонування RCW.

  5. Як здійснюється керування посиланнями на СОМ-об’єкти?

  6. Опишіть процес генерування бібліотеки типів і реєстрації типів СОМ.

  7. Опишіть процес раннього та пізнього зв’язування з СОМ-класом.

  8. Як здійснюється обробка подій СОМ збіркою .NET?

  9. Для чого використовуються модулі СCW?

  10. Назвіть деякі особливості відображення типів .NET у СОМ.

  11. Опишіть процес створення збірки .NET як сервера СОМ.