Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Framework »

Обсудить на форуме Обсудить на форуме

Использование .NET компонентов при помощи COM
В этой статье мы с вами обсудим вопросы взаимодействия с компонентами .NET при помощи COM. Прочитав и осмыслив то, что здесь изложено, вы сможете с легкостью использовать компоненты .NET в ваших приложениях при помощи COM. А значит, сможете из любого вашего старого приложения, использующего WinApi, использовать практически все современные средства .NET.

 

Исходники к статье - Source.zip (12.7K)
 

Введение

В этой статье мы с вами обсудим вопросы взаимодействия с компонентами .NET при помощи COM. Прочитав и осмыслив то, что здесь изложено, вы сможете с легкостью использовать компоненты .NET в ваших приложениях при помощи COM. А значит, сможете из любого вашего старого приложения, использующего WinApi, использовать практически все современные средства .NET.

Пишем компонент

Основная задача

Для начала нам с вами придётся написать тот самый компонент, который мы хотим в дальнейшем научиться использовать при помощи COM. Это будет простой компонент, который занимается умножением целых чисел и возведением их в квадрат, а также может сообщить информацию о себе. Я выбрал такую простую задачу, чтобы не затуманивать код лишними строками, не относящимися к интересующему нас вопросу. Я не буду детально описывать компонент, так как считаю, что вам самим лучше посмотреть в код. Это будет гораздо понятнее и полезнее.

  • Visual Basic.NET
    'File:   Some.vb
    'Author: Copyright (C) 2001 Dubovcev Aleksey
    
    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#
    /*
    File:   Some.cs
    Author: Copyright (C) 2001 Dubovcev Aleksey
    */
    
    using System;
    using System.Reflection;
    
    //Данный атрибут определяет файл, в котором находится 
    //пара личных криптографических ключей, 
    //однозначно идентифицирующих сборку
    [assembly:AssemblyKeyFile("../Orbit.snk")]
    [assembly:AssemblyVersion("1.0.0.0")]
    
    namespace TestComponentLib
    {
      
      //Этот интерфейс нужен для взаимодействия
      //с COM 
      public interface ITestComponent
      {
        int Mul(int First, int Second);
        int Square { get; set; }
        String About { get; }
      }
    
      //Данный класс реализует интерфейс ITestComponent
      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++
    /*
    File:   Some.cpp
    Author: Copyright (C) 2001 Dubovcev Aleksey
    */
    
    #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
    /*
    File:   Some.il
    Author: Copyright (C) 2001 Dubovcev Aleksey
    */
    
    .assembly extern mscorlib
    {
      .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                         // .z\V.4..
      .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
    {
      //Этот интерфейс нужен для взаимодействия
      //с COM 
      .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
        {}
    
        //Данные объявления свойств нужны нам только для того, чтобы с компонентом
        //можно было бы общаться через интерфейс автоматизации IDispatch,
        //так как языки типа VC будут скорее всего вызывать функции напрямую через
        //таблицу виртуальных функций (интерфейс).
    
        //Скажу даже больше: если вы хотите, чтобы данные свойства были недоступны
        //для языков высокого уровня, просто не объявляйте их при помощи
        //ключевого слова .property.
    
        .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()
        }
      }
    
      //Данный класс реализует интерфейс ITestComponent
      .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   //Загружаем This
          call      instance void [mscorlib]System.Object::.ctor()
          ldarg.0   //Загружаем This
          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   //Загружаем This
          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     //Загружаем This
          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()
{
  //Инициализируем СOM как STA, хотя значения это, в общем-то, не имеет,
  //так как наш компонент автоматически поддерживает обе потоковые модели.

  if (FAILED(CoInitialize(NULL)))
    return;

   _bstr_t bstrCLSID;
  CLSID   clsidOrbit;

  ITestComponent* pIOrbit;

  HRESULT hr;

  //Это ProgID нашего компонента.
  //Как видите, он складывается из области видимости и имени
  //самого компонента, записанных через точку.
  bstrCLSID = "TestComponentLib.TestComponent";

  //Получаем CLSID (Class ID) через ProgID
  if (FAILED(CLSIDFromProgID (bstrCLSID,&clsidOrbit)))
    return;

  //Создаём объект
  //и сразу же получаем интересующий нас интерфейс ITestComponent
  if (FAILED(CoCreateInstance(clsidOrbit,0,CLSCTX_ALL,__uuidof(ITestComponent),(void**)&pIOrbit)))
      return;


  BSTR  strAbout;

  //Запрашиваем информацию о компоненте
  pIOrbit->get_About(&strAbout);

  //Для вывода используем UNICODE-версию MessageBox
  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;

  //Это интеллектуальный указатель, который объявляется в файле Some.tlh
  //при помощи макроса _COM_SMARTPTR_TYPEDEF.
  //Данный указатель базируется на классе _com_ptr,
  //который поставляется вместе с компилятором VC.

  //Мы получаем CLSDID при помощи оператора __uuidof,
  //который извлекает CLSDID, определённый в файле Some.tlh.
  
  //Вся идея в том, что мы не создаем компонент самостоятельно.
  //За нас это делает интеллектуальный указатель, он-то и вызывает
  //CoCreateInstance.
  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);


  //Внимание: освобождать интерфейс путём вызова Release()
  //нет необходимости, так как за нас это сделает
  //интеллектуальный указатель.

  CoUninitialize();
}

Далее я приведу примеры использования нашего компонента на разных языках. Сразу извиняюсь за отсутствие примера на Visual Basic. Его не будет, поскольку у меня на компьютере он попросту не установлен. (Прошу не путать Visual Basic и VBScript).

  • JScript
    var Orbit;
    var WshShell;
    
    //Данный объект нам понадобиться для отображения
    //всплывающего окна (MessageBox по русски)
    WshShell = new ActiveXObject("WScript.Shell")
    
    //Создаём наш компонент по ProgID
    //Как видите нам не нужна библиотека типов,
    //так мы используем автоматизацию через  интерфейс IDispatch
    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;
    
          //Данный объект нам понадобиться для отображения
          //всплывающего окна (MessageBox по русски)
          WshShell = new ActiveXObject("WScript.Shell")
    
          //Создаём наш компонент по ProgID
          //Как видите нам не нужна библиотека типов,
          //так мы используем автоматизацию через  интерфейс IDispatch
          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-компоненты.


Может пригодится:


Автор: Алексей Дубовцев
Прочитано: 9645
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру