Исходники
к статье - Source.zip (12.7K)
Введение
В этой статье мы с вами обсудим вопросы взаимодействия с компонентами
.NET при помощи COM. Прочитав и осмыслив то, что здесь изложено, вы
сможете с легкостью использовать компоненты .NET в ваших приложениях при
помощи COM. А значит, сможете из любого вашего старого приложения,
использующего WinApi, использовать практически все современные средства
.NET.
Пишем компонент
Основная задача
Для начала нам с вами придётся написать тот самый компонент, который
мы хотим в дальнейшем научиться использовать при помощи COM. Это будет
простой компонент, который занимается умножением целых чисел и
возведением их в квадрат, а также может сообщить информацию о себе. Я
выбрал такую простую задачу, чтобы не затуманивать код лишними строками,
не относящимися к интересующему нас вопросу. Я не буду детально
описывать компонент, так как считаю, что вам самим лучше посмотреть в
код. Это будет гораздо понятнее и полезнее.
- Visual Basic.NET
Imports System
Imports System.Reflection
<Assembly:AssemblyKeyFile("../Orbit.snk")>
<Assembly:AssemblyVersion("1.0.0.0")>
Namespace TestComponentLib
Public Interface ITestComponent
Function Mul (x As Integer,y As Integer) As Integer
ReadOnly Property About() As String
End Interface
Public class TestComponent
Implements ITestComponent
Public Function Mul (x As Integer,y As Integer) As Integer Implements ITestComponent.Mul
Mul = x * y
End Function
Public ReadOnly Property About() As String Implements ITestComponent.About
Get
Return "This component is written in Visual Basic.NET"
End Get
End Property
End Class
End Namespace
|
- C#
using System;
using System.Reflection;
[assembly:AssemblyKeyFile("../Orbit.snk")]
[assembly:AssemblyVersion("1.0.0.0")]
namespace TestComponentLib
{
public interface ITestComponent
{
int Mul(int First, int Second);
int Square { get; set; }
String About { get; }
}
public class TestComponent : ITestComponent
{
public TestComponent()
{
m_iSquare = 0;
}
public int Mul( int First, int Second)
{
return First * Second;
}
private int m_iSquare;
public int Square
{
get
{
return m_iSquare;
}
set
{
m_iSquare = value * value;
}
}
public String About
{
get
{
return "This component is written in C#";
}
}
}
}
|
- Managed Extensions for C++
#using
using namespace System;
using namespace System::Reflection;
[assembly:AssemblyKeyFile("../Orbit.snk")];
[assembly:AssemblyVersion("1.0.0.1")];
namespace TestComponentLib
{
public __gc __interface ITestComponent
{
int Mul (int First, int Second);
__property System::String* get_About();
__property int get_Square ();
__property void set_Square (int Param);
};
public __gc class TestComponent : public ITestComponent
{
private:
int m_iSquare;
public:
int Mul (int First, int Second)
{
return First * Second;
}
__property System::String* get_About()
{
return L"This component is written in Managed Extension for C++";
}
__property int get_Square ( )
{
return m_iSquare;
}
__property void set_Square (int Param)
{
m_iSquare = Param * Param;
}
};
}
|
- Microsoft Intermediate Language
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
.ver 1:0:2411:0
}
.assembly Some
{
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module Some.dll
.imagebase 0x00400000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000009
.namespace TestComponentLib
{
.class interface public abstract auto ITestComponent
{
.method public newslot virtual abstract
instance int32 Mul(int32 First,
int32 Secont) cil managed
{}
.method public newslot specialname virtual abstract
instance int32 get_Square() cil managed
{}
.method public newslot specialname virtual abstract
instance void set_Square(int32 'value') cil managed
{}
.method public newslot specialname virtual abstract
instance string get_About() cil managed
{}
.property instance int32 Square()
{
.get instance int32 TestComponentLib.ITestComponent::get_Square()
.set instance void TestComponentLib.ITestComponent::set_Square(int32)
}
.property instance string About()
{
.get instance string TestComponentLib.ITestComponent::get_About()
}
}
.class public beforefieldinit auto TestComponent
extends [mscorlib]System.Object
implements TestComponentLib.ITestComponent
{
.field private int32 m_iSquare
.method public specialname
instance void .ctor() cil managed
{
ldarg.0
call instance void [mscorlib]System.Object::.ctor()
ldarg.0
ldc.i4.0
stfld int32 TestComponentLib.TestComponent::m_iSquare
ret
}
.method public newslot final virtual
instance int32 Mul(int32 First,
int32 Second) cil managed
{
.maxstack 2
ldarg.1
ldarg.2
mul
ret
}
.method public newslot specialname final virtual
instance int32 get_Square() cil managed
{
.maxstack 1
ldarg.0
ldfld int32 TestComponentLib.TestComponent::m_iSquare
ret
}
.method public hidebysig newslot specialname final virtual
instance string get_About() cil managed
{
.maxstack 1
ldstr "This component is writen in IL"
ret
}
.method public newslot specialname final virtual
instance void set_Square(int32 'value') cil managed
{
.maxstack 8
ldarg.0
ldarg.1
ldarg.1
mul
stfld int32 TestComponentLib.TestComponent::m_iSquare
ret
}
.property instance int32 Square()
{
.get instance int32 TestComponentLib.TestComponent::get_Square()
.set instance void TestComponentLib.TestComponent::set_Square(int32)
}
.property instance string About()
{
.get instance string TestComponentLib.TestComponent::get_About()
}
}
}
|
Что же мы написали?
Взглянув на код, вы можете увидеть, что мы используем файл
Orbit.snk. (Для IL этот файл задаётся параметром командной строки
/KEY). Это пара криптографических ключей, которые будут
идентифицировать нашу сборку в GAC (Global Assebly Cache - Глобальный
кэш сборок). В него нам впоследствии придется поместить нашу сборку,
чтобы ее увидел COM. А все сборки, помещаемые в GAC, должны, как
известно, идентифицироваться открытым криптографическим ключом. Эту пару
ключей вы можете создать для себя, используя утилиту sn
следующим образом:
sn.exe -k Имя_вашего_файла_с_ключом.sn
|
Теперь сделайте саму сборку. У вас должна получиться DLL, в которой
будет находиться наша с вами сборка. Теперь, когда у нас с вами есть эта
сборка, давайте заглянем ей "под капот". Смело запускайте утилиту
ildasm:
ildasm имя_вашей_сборки.dll /adv
|
Вот что получилось у меня:
ildasm
Я предлагаю вам немного поизучать эту сборку при помощи
ildasm, которою я описывал в статье "Немного о сборках".
Регистрация сборки для COM
Итак, у нас есть сборка, которая несет в себе полную информацию о
типах, в чем мы уже убедились при помощи ildasm. Но эти
типы понятны только для среды исполнения .NET и не несут никакой
полезной информации для COM. Чтобы COM смогла использовать типы,
описанные в сборке, мы с вами создадим и зарегистрируем библиотеку типов
COM (tlb). Исполнить этот замысел нам поможет утилита regasm,
которая как раз для этого и предназначена. Создавать библиотеку будем
так:
regasm /tlb Имя_вашей_борки.dll
|
После выполнения данной команды утилита regasm
создаст библиотеку типов COM из вашей сборки и автоматически
зарегистрирует её в системном реестре. Если у вас нет необходимости
регистрировать библиотеку типов в реестре, вы можете воспользоваться для
её создания утилитой tlbexp.exe. Данная утилита
предназначена для создания библиотек типов COM из сборок. Если вам нужно
создать библиотеку типов программным путём, для этого вам следует
использовать класс TypeLibConverter, расположенный в
пространстве имен System.Runtime.Interop. Для
регистрации же сборок предназначена утилита regasm,
которую я упоминал немного выше.
После регистрации в реестре вырисовывается следующая картина:
Реестр
Желтым цветом здесь обозначены ключи, а темно-желтым значения.
Для начала регистрируется ProgID нашего компонента, в данном случае
это TestComponentLib.TestComponent, который находится в
подключе HKCR\CLSID. По этому ключу COM будет искать
CLSID, если его запросят создать компонент при помощи ProgID. К примеру,
из какого-нибудь скриптового языка, который не поддерживает CLSID
непосредственно. Далее регистрируется CLSID приложения
в подключе HKCR\CLSID\{XXX...XXX}. Кстати, это значение
всегда будет постоянно и не будет меняться от билда к билду. Далее нас
может смутить значение праметра (Default) в ключе
...InprocServer32, оно явно не похоже на нашу
библиотеку. И правильно, это среда исполнения .NET. Как COM с ней
взаимодействует, я расскажу немного позднее, а сейчас давайте вернемся к
реестру. Далее мы с вами видим параметр Assembly,
задающий нашу сборку. Вы можете удивиться почему к ней не указано имя:
оно в данном случае не нужно, так как сборка храниться в GAC, в котором
она будет найдена при помощи параметров Some, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=023eff9be46a57df. Здесь будет
уместно раскрыть смысл параметров данной строки:
- Some - имя сборки, помещенной в GAC.
- VersionInfo - информация о версии сборки.
- Culture - информация о локализации (то есть о
языке).
- PublicKeyToken - контрольная сумма публичного
ключа, помещенного в сборку при её создании. Это контрольная сумма
того ключа, который мы с вами создавали при помощи утилиты
sn.
Далее идет название класса и версия среды исполнения, в которой была
создана сборка. Затем указана потоковая модель, которую поддерживает
компонент. По умолчанию компонент поддерживает обе потоковые модели (STA
и MTA).
Процесс использования .NET компонента в COM среде
Для начала взгляните, как использовать созданный нами .NET-компонент
при помощи COM. Я умышленно привожу пример только на языке VC++, так как
языки более высокого уровня, такие как Visual Basic, практически
полностью скрывают от нас процесс создания COM-компонента. Они весьма
"любезно" исполняют за нас всю черную работу, на которую я хочу обратить
ваше внимание.
#include
#include
#include
#import "../Some.tlb" raw_interfaces_only
using namespace Some;
void main()
{
if (FAILED(CoInitialize(NULL)))
return;
_bstr_t bstrCLSID;
CLSID clsidOrbit;
ITestComponent* pIOrbit;
HRESULT hr;
bstrCLSID = "TestComponentLib.TestComponent";
if (FAILED(CLSIDFromProgID (bstrCLSID,&clsidOrbit)))
return;
if (FAILED(CoCreateInstance(clsidOrbit,0,CLSCTX_ALL,__uuidof(ITestComponent),(void**)&pIOrbit)))
return;
BSTR strAbout;
pIOrbit->get_About(&strAbout);
MessageBoxW(0,strAbout,0,0);
long lResult;
pIOrbit->Mul(5,4,&lResult);
printf("Multiply 5 * 4 = %d\n",lResult);
pIOrbit->put_Square(5);
pIOrbit->get_Square(&lResult);
printf("Printf square of 5 = %d\n",lResult);
pIOrbit->Release();
CoUninitialize();
}
|
Я надеюсь, что вам понятна суть происходящего. Я не хочу
останавливаться на подробностях COM, поскольку это выходит за рамки
данной статьи. Наиболее интересным в этом участке кода является вызов
функции CoCreateInstance. Чтобы уяснить ситуацию,
взгляните на рисунок.
Таким образом, мы видим, что CoCreateInstance
создает в памяти не сам компонент, а библиотеку mscoree.dll,
которая является средой исполнения .NET. Эта библиотека предоставляет
интерфейс фабрики классов IClassFactory для создания
компонентов. При попытке создания компонента через этот интерфейс среда
исполнения автоматически создаёт нужный компонент .NET и предоставляет
его COM-совместимый интерфейс. При обращениях из среды COM, .NET берет
на себя заботу о маршалинге данных их среды в среду.
Дополнительные способы взаимодействия с компонентом
В дополнение я покажу вам, как можно использовать классы поддержки
COM для упрощения работы с компонентами .NET.
#include
#include
#import "../Some.tlb" raw_interfaces_only
using namespace Some;
void main()
{
if (FAILED(CoInitialize(NULL)))
return;
ITestComponentPtr pIOrbit(__uuidof(TestComponent));
BSTR strAbout;
pIOrbit->get_About(&strAbout);
MessageBoxW(0,strAbout,0,0);
long lResult;
pIOrbit->Mul(5,4,&lResult);
printf("Multiply 5 * 4 = %d\n",lResult);
pIOrbit->put_Square(5);
pIOrbit->get_Square(&lResult);
printf("Printf square of 5 = %d\n",lResult);
CoUninitialize();
}
|
Далее я приведу примеры использования нашего компонента на разных
языках. Сразу извиняюсь за отсутствие примера на Visual Basic. Его не
будет, поскольку у меня на компьютере он попросту не установлен. (Прошу
не путать Visual Basic и VBScript).
- JScript
var Orbit;
var WshShell;
WshShell = new ActiveXObject("WScript.Shell")
Orbit = new ActiveXObject("TestComponentLib.TestComponent");
WshShell.Popup(Orbit.About)
WshShell.Popup(Orbit.Mul(5,4));
Orbit.Square = 5;
WshShell.Popup(Orbit.Square);
|
- VBScript
Dim Orbit
Set Orbit = CreateObject("TestComponentLib.TestComponent")
MsgBox(Orbit.About)
Orbit.Square = 5
MsgBox(Orbit.Square)
MsgBox(Orbit.Mul(5,4))
|
- DHTML
<HTML>
<SCRIPT language="VBScript">
Sub OnVB
Dim Orbit
Set Orbit = CreateObject("TestComponentLib.TestComponent")
MsgBox(Orbit.About)
Orbit.Square = 5
MsgBox(Orbit.Square)
MsgBox(Orbit.Mul(5,4))
End Sub
</SCRIPT>
<SCRIPT language="JScript">
function OnJs
{
var Orbit;
var WshShell;
WshShell = new ActiveXObject("WScript.Shell")
Orbit = new ActiveXObject("TestComponentLib.TestComponent");
WshShell.Popup(Orbit.About)
WshShell.Popup(Orbit.Mul(5,4));
Orbit.Square = 5;
WshShell.Popup(Orbit.Square);
}
</SCRIPT>
<BODY>
<CENTER>
<INPUT TYPE="BUTTON" value="VBScript Test" OnClick="OnVB">
<INPUT TYPE="BUTTON" value="Jscript Test" OnClick="OnJS">
</CENTER>
<BR><HR>
</BODY>
</HTML>
|
Ну как, впечатляет? Главное, посмотрите, сколько вы затратили усилий
на создание компонента. Разве много? Вот и я о том же. Теперь вы можете
создавать ваши собственные .NET компоненты и с легкостью, присущей
матёрому профессионалу, использовать их как COM-компоненты.