COM+-компонент для 1С на C#

Автор: Алексей Столповских
Источник: RSDN Magazine #3-2004
Опубликовано: 20.11.2004
Версия текста: 1.0
Введение
Немного истории
Специфика .NET
System.EnterpriseServices
System.Runtime.InteropServeces
System.Reflection
Технология создания внешних компонентов 1С
Интерфейсы
Подключение компонента к 1С
Конфигурирование COM+
Использование
Заключение
Литература

Введение

Данная статья описывает технологию создания COM+-компонента для 1С на C#. Код компонента был написан с использованием Microsoft Visual Studio.NET 2003, классов Microsoft .NET Framework 1.1 и протестирован на операционной системе Windows Server 2003 System.

Немного истории

Столкнувшись с задачей написания внешнего компонента для интеграции сканера штрих-кодов с 1C, я решил сделать это на C#, так как последнее время в основном работал с этим языком. Попытался найти какой-нибудь пример в Internet, но кроме проектов на MFC, ATL и Delphi ничего не обнаружил. Зато помогли статьи описывающие технологию создания внешних компонентов, особенно потому, что опыта работы с 1С у меня практически не было.

Специфика .NET

System.EnterpriseServices

Пространство имен System.EnterpriseServices предоставляет ряд классов и атрибутов для реализации .NET-компонентов, работающих с сервисами COM+. Базовым классом для этих компонентов является ServicedComponent, благодаря которому написание СOM+-приложения сводится к следующим тривиальным шагам [1]:

1. Декларации интерфейсов, реализуемых компонентом, и создание класса, производного от ServicedComponent:

using System;
using System.EnterpriseServices;

public interface IInitDone {}
public interface ILanguageExtender {}

public class Component : ServicedComponent,
  IInitDone,
  ILanguageExtender
{
}

2. Конфигурированию приложения, компонента и его методов с использованием атрибутов. Код, представленный ниже, показывает применение атрибутов:

using System.Reflection;
using System.Runtime.InteropServices;
. . .
[assembly: ApplicationAccessControl(AccessChecksLevel=AccessChecksLevelOption.Application)]
[assembly: ApplicationName("SimpleExternalComponent Application")]
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: AssemblyKeyFile("filename.snk")]
. . .
[ClassInterface(ClassInterfaceType.None)]
[ProgId("AddIn.SimpleExternalComponent")]
[Description("SimpleExternalComponent.Component class")]
public class Component : ServicedComponent . . .
. . .
СОВЕТ

Атрибуты ClassInterface и ProgId определены в System.Runtime.InteropServices, а AssemblyKeyFile - в System.Reflection.

3. Компиляции кода. Я предпочитаю делать это в Visual Studio .NET, но можно компилировать и из командной строки.

csc /t:library SimpleExternalComponent.cs

4. Регистрации и инсталляции компонента, которая может быть осуществлена несколькими способами.

ПРИМЕЧАНИЕ

В MSDN прямо говорится, что для того, чтобы использоваться в COM+, сборка должна быть подписана. Но на самом деле это необязательно, достаточно в свойствах проекта указать, что требуется регистрация сборки как COM-объекта.

System.Runtime.InteropServeces

Задача реализации и импорта unmanaged COM-компонентов в .NET-приложение связана с использованием классов из пространства имен System.Runtime.InteropServices. Функции интерфейсов должны быть декларированы таким образом, чтобы параметры соответствовали unmanaged-декларации в IDL-файле. Для этого используется атрибут MarshalAs, применение которого описано в MSDN [3,4]. Ниже приведен пример одной из функций интерфейса IInitDone в unmanaged-коде:

HRESULT GetInfo(SAFEARRAY * * pInfo);

В .NET эта функция декларируется следующим образом:

void GetInfo([MarshalAs(UnmanagedType.SafeArray,
  SafeArraySubType=VarEnum.VT_VARIANT)]
  ref object[] info);

System.Reflection

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

Представленный ниже код показывает использование классов System.Reflection для реализации метода RegisterExtentionAs интерфейса ILanguageExtender. Данный метод кэширует информацию о методах и свойствах пользовательских интерфейсов.

/// <summary>
/// Register component in 1C
/// </summary>
/// <param name="extensionName"></param>
public void RegisterExtensionAs(
  [MarshalAs(UnmanagedType.BStr)]
  ref String extensionName)
{
  try 
  {
    // initialize data members
    nameToNumber      = new Hashtable();
    numberToName      = new Hashtable();
    numberToParams    = new Hashtable();
    numberToRetVal    = new Hashtable();

    propertyNameToNumber  = new Hashtable();
    propertyNumberToName  = new Hashtable();

    numberToMethodInfoIdx  = new Hashtable();
    propertyNumberToPropertyInfoIdx  = new Hashtable();

    // Заполнение хэш-таблиц
    Type type = this.GetType();
    Type[] allInterfaceTypes = type.GetInterfaces();

    // Определение идентификатора
    int Identifier = 0;

    foreach(Type interfaceType in allInterfaceTypes)
    {
      if (
        !interfaceType.Name.Equals("IDisposable") 
        && !interfaceType.Name.Equals("IManagedObject")
        && !interfaceType.Name.Equals("IRemoteDispatch")
        && !interfaceType.Name.Equals("IServicedComponentInfo")
        && !interfaceType.Name.Equals("IInitDone")
        && !interfaceType.Name.Equals("ILanguageExtender")
        )
      {
        // Обработка методов
        MethodInfo[] interfaceMethods = interfaceType.GetMethods();
        foreach (MethodInfo interfaceMethodInfo in interfaceMethods)
        {
          nameToNumber.Add(interfaceMethodInfo.Name, Identifier);
          numberToParams.Add(Identifier,
            interfaceMethodInfo.GetParameters().Length);
          if (typeof(void) != interfaceMethodInfo.ReturnType)
            numberToRetVal.Add(Identifier, true);

            Identifier++;
          }

        // Обработка свойств
        PropertyInfo[] interfaceProperties = interfaceType.GetProperties();
        foreach (PropertyInfo interfacePropertyInfo in interfaceProperties)
        {
          propertyNameToNumber.Add(interfacePropertyInfo.Name, Identifier);

          Identifier++;
        }
      }
    }

    foreach (DictionaryEntry entry in nameToNumber)
      numberToName.Add(entry.Value, entry.Key);
    foreach (DictionaryEntry entry in propertyNameToNumber)
      propertyNumberToName.Add(entry.Value, entry.Key);

    // Сохранение информации о методах класса 
    allMethodInfo = type.GetMethods();

    // Сохранение информации о свойствах класса
    allPropertyInfo = type.GetProperties();

    // Отображение номера метода на индекс в массиве
    foreach (DictionaryEntry entry in numberToName)
    {
      bool found = false;
      for(int ii = 0; ii < allMethodInfo.Length; ii++)
      {
        if (allMethodInfo[ii].Name.Equals(entry.Value.ToString()))
        {
          numberToMethodInfoIdx.Add(entry.Key, ii);
          found = true;
        }
      }
      if (false == found)
        throw new COMException("Метод не реализован ");
    }

    // Отображение номера свойства на индекс в массиве
    foreach (DictionaryEntry entry in propertyNumberToName)
    {
      bool found = false;
      for(int ii = 0; ii < allPropertyInfo.Length; ii++)
      {
        if (allPropertyInfo[ii].Name.Equals(entry.Value.ToString()))
        {
          propertyNumberToPropertyInfoIdx.Add(entry.Key, ii);
          found = true;
        }
      }
      if (false == found)
        throw new COMException("The property is not implemented");
    }

    // Компонент инициализирован успешно
    // Возвращаем имя компонента
    extensionName  = componentName;
  }
  catch (Exception)
  {
    return;
  }
}
ПРИМЕЧАНИЕ

Интерфейсы IDisposable, IManagedObject, IRemoteDispatch и IServicedComponentInfo реализованы в классе ServicedComponent.

Технология создания внешних компонентов 1С

Интерфейсы

Все описываемые 1С-интерфейсы имеют предопределенные GUID. Глобальный идентификатор может быть присвоен приложениям, классам, интерфейсам, структурам и другим конструкциям .NET с помощью атрибута GuidAttribute. В противном случае GUID генерируется автоматически. Рекомендуется использовать GuidAttribute только в случаях, где это необходимо, так как его использование затрудняет контроль версий.

Внешний компонент 1С должен реализовывать, по крайней мере, два интерфейса – IInitDone и ILanguageExtender. Первый служит для инициализации компонента, а второй - для вызова его функций, процедур и свойств из среды 1С [5]. Другие интерфейсы, описанные в этой статье, реализованы 1С и используются внешним компонентом для генерации событий и получения доступа к строке состояния.

IInitDone - {AB634001-F13D-11D0-A459-004095E1DAEA}

Функции данного интерфейса вызываются при подключении компонента. Представленный ниже код декларирует интерфейс IInitDone в .NET приложении:

[Guid("AB634001-F13D-11d0-A459-004095E1DAEA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IInitDone
{
  /// <summary>
  /// Инициализация компонента
  /// </summary>
  /// <param name="connection">reference to IDispatch</param>
  void Init(
    [MarshalAs(UnmanagedType.IDispatch)]
    object connection);

  /// <summary>
  /// Вызывается перед уничтожением компонента
  /// </summary>
  void Done();

  /// <summary>
  /// Возвращается инициализационная информация
  /// </summary>
  /// <param name="info">Component information</param>
  void GetInfo(
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)]
    ref object[] info);
}

Ниже показано, как функции Init и GetInfo реализованы в классе компонента:

private IAsyncEvent asyncEvent    = null;
private IStatusLine statusLine    = null;

/// <summary>
/// Инициализация компонента
/// </summary>
/// <param name="connection">reference to IDispatch</param>
public void Init(
  [MarshalAs(UnmanagedType.IDispatch)]
  object connection)
{
  asyncEvent  = (IAsyncEvent)connection;
  statusLine  = (IStatusLine)connection;
}

/// <summary>
/// Возвращается информация о компоненте
/// </summary>
/// <param name="info">Component information</param>
public void GetInfo(
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)]
  ref object[] info)
{
  info[0] = 2000;
}

ILanguageExtender - {AB634003-F13D-11D0-A459-004095E1DAEA}

Данный интерфейс определяет логику вызова функций, процедур и свойств внешнего компонента из 1С.

ПРИМЕЧАНИЕ

Аналогом ILanguageExtender является интерфейс IDispatch, используемый в OLE Automation. В действительности, 1С может выступать в качестве клиента OLE Automation. В этом случае вызов функций сервера автоматизации осуществляется с помощью функции Invoke интерфейса IDispatch, что замедляет процесс взаимодействия в связи с преобразованием передаваемых параметров из типа Variant и обратно [6]. Поэтому рекомендуется использовать пользовательские интерфейсы.

/// <summary>
/// При вызове функции из 1С производится следующая последовательность вызовов:
///    1. FindMethod
///    2. GetNParams
///    3. HasRetVal
/// </summary>
[Guid("AB634003-F13D-11d0-A459-004095E1DAEA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ILanguageExtender
{
  /// <summary>
  /// Регистрация компонента в 1C
  /// </summary>
  /// <param name="extensionName"></param>
  /// <remarks>
  /// <prototype>
  /// [helpstring("method RegisterExtensionAs")]
  /// HRESULT RegisterExtensionAs([in,out]BSTR *bstrExtensionName);
  /// </prototype>
  /// </remarks>
  void RegisterExtensionAs(
    [MarshalAs(UnmanagedType.BStr)]
    ref String extensionName);

  /// <summary>
  /// Возвращается количество свойств
  /// </summary>
  /// <param name="props">Количество свойств </param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetNProps([in,out]long *plProps);
  /// </prototype>
  /// </remarks>
  void GetNProps(ref Int32 props);

  /// <summary>
  /// Возвращает целочисленный идентификатор свойства, соответствующий 
  /// переданному имени
  /// </summary>
  /// <param name="propName">Имя свойства</param>
  /// <param name="propNum">Идентификатор свойства</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT FindProp([in]BSTR bstrPropName,[in,out]long *plPropNum);
  /// </prototype>
  /// </remarks>
  void FindProp(
    [MarshalAs(UnmanagedType.BStr)]
    String propName,
    ref Int32 propNum);

  /// <summary>
  /// Возвращает имя свойства, соответствующее 
  /// переданному целочисленному идентификатору
  /// </summary>
  /// <param name="propNum">Идентификатор свойства</param>
  /// <param name="propAlias"></param>
  /// <param name="propName">Имя свойства</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetPropName([in]long lPropNum,[in]long lPropAlias,[in,out]BSTR *pbstrPropName);
  /// </prototype>
  /// </remarks>
  void GetPropName(
    Int32 propNum,
    Int32 propAlias,
    [MarshalAs(UnmanagedType.BStr)]
    ref String propName);

  /// <summary>
  /// Возвращает значение свойства.
  /// </summary>
  /// <param name="propNum">Идентификатор свойства </param>
  /// <param name="StringpropVal">Значение свойства</param>
  /// <remarks>
  /// <prototype>
  /// </prototype>
  /// </remarks>
  void GetPropVal(
    Int32 propNum,
    [MarshalAs(UnmanagedType.Struct)]
    ref object propVal);

  /// <summary>
  /// Устанавливает значение свойства.
  /// </summary>
  /// <param name="propName">Имя свойства</param>
  /// <param name="propVal">Значение свойства</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT SetPropVal([in]long lPropNum,[in]VARIANT *varPropVal);
  /// </prototype>
  /// </remarks>
  void SetPropVal(
    Int32 propNum,
    [MarshalAs(UnmanagedType.Struct)]
    ref object propVal);

  /// <summary>
  /// Определяет, можно ли читать значение свойства
  /// </summary>
  /// <param name="propNum"> Идентификатор свойства </param>
  /// <param name="propRead">Флаг читаемости</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT IsPropReadable([in]long lPropNum,[in,out]BOOL *pboolPropRead);
  /// </prototype>
  /// </remarks>
  void IsPropReadable(Int32 propNum, ref bool propRead);
    
  /// <summary>
  /// Определяет, можно ли изменять значение свойства
  /// </summary>
  /// <param name="propNum">Идентификатор свойства</param>
  /// <param name="propRead">Флаг записи</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT IsPropWritable([in]long lPropNum,[in,out]BOOL *pboolPropWrite);
  /// </prototype>
  /// </remarks>
  void IsPropWritable(Int32 propNum, ref Boolean propWrite);

  /// <summary>
  /// Возвращает количество методов
  /// </summary>
  /// <param name="pMethods">Количество методов</param>
  /// <remarks>
  /// <prototype>
  /// [helpstring("method GetNMethods")]
  /// HRESULT GetNMethods([in,out]long *plMethods);
  /// </prototype>
  /// </remarks>
  void GetNMethods(ref Int32 pMethods);

  /// <summary>
  /// Возвращает идентификатор метода по его имени
  /// </summary>
  /// <param name="methodName">Имя метода</param>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT FindMethod(BSTR bstrMethodName,[in,out]long *plMethodNum);
  /// </prototype>
  /// </remarks>
  void FindMethod(
    [MarshalAs(UnmanagedType.BStr)]
    String methodName,
    ref Int32 methodNum);

  /// <summary>
  /// Возвращает имя метода по его идентификатору
  /// </summary>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <param name="methodAlias"></param>
  /// <param name="methodName">Имя метода</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetMethodName([in]long lMethodNum,[in]long lMethodAlias,[in,out]BSTR *pbstrMethodName);
  /// </prototype>
  /// </remarks>
  void GetMethodName(Int32 methodNum,
    Int32 methodAlias,
    [MarshalAs(UnmanagedType.BStr)]
    ref String methodName);

  /// <summary>
  /// Возвращает количество параметров метода по его идентификатору
  /// </summary>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <param name="pParams">Количество параметров</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetNParams([in]long lMethodNum, [in,out]long *plParams);
  /// </prototype>
  /// </remarks>
  void GetNParams(Int32 methodNum, ref Int32 pParams);

  void GetParamDefValue(
    Int32 methodNum,
    Int32 paramNum,
    [MarshalAs(UnmanagedType.Struct)]
    ref object paramDefValue);

  /// <summary>
  /// Указывает, что у метода есть возвращаемое значение
  /// </summary>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <param name="retValue">Наличие возвращаемого значения</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT HasRetVal([in]long lMethodNum,[in,out]BOOL *pboolRetValue);
  /// </prototype>
  /// </remarks>
  void HasRetVal(Int32 methodNum, ref Boolean retValue);

  /// <summary>
  /// Вызов метода как процедуры с использованием идентификатора
  /// </summary>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <param name="pParams">Параметры</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT CallAsProc([in]long lMethodNum,[in] SAFEARRAY (VARIANT) *paParams);
  /// </prototype>
  /// </remarks>
  void CallAsProc(
    Int32 methodNum,
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)]
    ref object[] pParams);

  /// <summary>
  /// Вызов метода как функции с использованием идентификатора
  /// </summary>
  /// <param name="methodNum">Идентификатор метода</param>
  /// <param name="retValue">Возвращаемое значение</param>
  /// <param name="pParams">Параметры</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT CallAsFunc([in]long lMethodNum,[in,out] VARIANT *pvarRetValue,
///         [in] SAFEARRAY (VARIANT) *paParams);
  /// </prototype>
  /// </remarks>
  void CallAsFunc(
    Int32 methodNum,
    [MarshalAs(UnmanagedType.Struct)]
    ref object retValue,
    [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)]
    ref object[] pParams);
}

1С вызывает функции внешнего компонента в следующей последовательности: FindMethod, GetNParams, HasRetVal, CallAsFunc. Ниже приведена реализация этих методов в классе компонента:

/// <summary>
/// Возвращает идентификатор метода
/// </summary>
/// <param name="methodName">Имя метода</param>
/// <param name="methodNum">Идентификатор метода</param>
/// <remarks>
/// <prototype>
/// [helpstring("method FindMethod")]
/// HRESULT FindMethod(BSTR bstrMethodName,[in,out]long *plMethodNum);
/// </prototype>
/// </remarks>
public void FindMethod(
  [MarshalAs(UnmanagedType.BStr)]
  String methodName,
  ref Int32 methodNum)
{
  methodNum = (Int32)nameToNumber[methodName];
}
    
/// <summary>
/// Возвращает число параметров метода по его идентификатору
/// </summary>
/// <param name="methodNum">Идентификатор метода</param>
/// <param name="pParams">Число параметров</param>
/// <remarks>
/// <prototype>
/// HRESULT GetNParams([in]long lMethodNum,[in,out]long *plParams);
/// </prototype>
/// </remarks>
public void GetNParams(Int32 methodNum, ref Int32 pParams)
{
  pParams = (Int32)numberToParams[methodNum];
}

/// <summary>
/// Указывает, что у метода есть возвращаемое значение
/// </summary>
/// <param name="methodNum">Идентификатор метода</param>
/// <param name="retValue">Наличие возвращаемого значения</param>
/// <remarks>
/// <prototype>
/// HRESULT HasRetVal([in]long lMethodNum,[in,out]BOOL *pboolRetValue);
/// </prototype>
/// </remarks>
public void HasRetVal(Int32 methodNum, ref Boolean retValue)
{
  retValue = (Boolean)numberToRetVal[methodNum];
}

/// <summary>
/// Вызов метода как функции с использованием идентификатора
/// </summary>
/// <param name="methodNum">Идентификатор метода</param>
/// <param name="retValue">Возвращаемое значение</param>
/// <param name="pParams">Параметры</param>
/// <remarks>
/// <prototype>
/// HRESULT CallAsFunc( [in]long lMethodNum,[in,out] VARIANT *pvarRetValue,
///       [in] SAFEARRAY (VARIANT)*paParams);
/// </prototype>
/// </remarks>
public void CallAsFunc(
Int32 methodNum,
  [MarshalAs(UnmanagedType.Struct)]
  ref object retValue,
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_VARIANT)]
  ref object[] pParams)
{
  retValue = allMethodInfo[(int)numberToMethodInfoIdx[methodNum]].Invoke(
    this, pParams);
}

IAsyncEvent - {AB634004-F13D-11D0-A459-004095E1DAEA}

Данный интерфейс реализован 1С для получения событий от внешнего компонента. Ссылка на него получена путем приведения параметра функции Init интерфейса IInitDone к типу IAsyncEvent при инициализации компонента. Внешнее событие обрабатывается 1С в функции ОбработкаВнешнегоСобытия.

[Guid("ab634004-f13d-11d0-a459-004095e1daea")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAsyncEvent // : IUnknown
{
  /// <summary>
  /// Установка глубины буфера событий
  /// </summary>
  /// <param name="depth">Buffer depth</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT SetEventBufferDepth(long lDepth);
  /// </prototype>
  /// </remarks>
  void SetEventBufferDepth(Int32 depth);

  /// <summary>
  /// Получение глубины буфера событий
  /// </summary>
  /// <param name="depth">Buffer depth</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetEventBufferDepth(long *plDepth);
  /// </prototype>
  /// </remarks>
  void GetEventBufferDepth(ref long depth);

  /// <summary>
  /// Посылка события
  /// </summary>
  /// <param name="source">Event source</param>
  /// <param name="message">Event message</param>
  /// <param name="data">Event data</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT GetEventBufferDepth(long *plDepth);
  /// </prototype>
  /// </remarks>
  void ExternalEvent(
    [MarshalAs(UnmanagedType.BStr)] String source,
    [MarshalAs(UnmanagedType.BStr)] String message,
    [MarshalAs(UnmanagedType.BStr)] String data
    );

  /// <summary>
  /// Очистка буфера событий
  /// </summary>
  /// <remarks>
  /// <prototype>
  /// </prototype>
  /// </remarks>
  void CleanBuffer();
}

IStatusLine - {AB634005-F13D-11D0-A459-004095E1DAEA}

С помощью этого интерфейса внешний компонент получает доступ к строке состояния 1С. Ссылка на интерфейс получена методом, описанным в IAsyncEvent.

[Guid("AB634005-F13D-11D0-A459-004095E1DAEA")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IStatusLine
{
  /// <summary>
  /// Задает текст статусной строки
  /// </summary>
  /// <param name="bstrStatusLine">Текст статусной строки</param>
  /// <remarks>
  /// <prototype>
  /// HRESULT SetStatusLine(BSTR bstrStatusLine);
  /// </prototype>
  /// </remarks>
  void SetStarusLine(
    [MarshalAs(UnmanagedType.BStr)]String bstrStatusLine
    );

  /// <summary>
  /// Сброс статусной строки
  /// </summary>
  /// <remarks>
  /// <propotype>
  /// HRESULT ResetStatusLine();
  /// </propotype>
  /// </remarks>
  void ResetStatusLine();
}

Подключение компонента к 1С

Подключить внешний компонент к 1С можно двумя методами:

  1. С помощью функции ЗагрузитьВнешнююКомпоненту("<имя dll файла>"). Файл DLL должен находиться в папке БД. В этом случае имя компонента (ProgID) задается в файле ресурсов (*.RC) компонента под идентификатором 100. При этом 1С сама регистрирует компонент как COM-сервер. Так как структура ресурсов приложения .NET отличается от unmanaged-приложения, данный способ не может быть использован в нашем случае [7].
  2. Можно также зарегистрировать компонент с помощью regsvr32 и подключать ее при помощи ПодключитьВнешнююКомпоненту("<ProgID>"), где <ProgID> может иметь вид "<ProgID1>[|<ProgID2>]...[|<ProgIDn>]" [8]. Во этом случае необязательно нахождение компонента в папке БД, так как путь к файлу указан в реестре. Использование данного метода показано ниже:
Если ПодключитьВнешнююКомпоненту("AddIn.SimpleExternalComponent") = 0 Тогда
  Предупреждение("Не удалось загрузить компонент" + симв(13) + симв(10) 
    + "AddIn.SimpleExternalComponent!", 60);
  ЗавершитьРаботуСистемы(0);
  Возврат;
КонецЕсли;
Компонента = СоздатьОбъект("AddIn.SimpleExternalComponent");

Конфигурирование COM+

Конфигурирование COM+-приложения осуществляется с помощью атрибутов пространства имен System.EnterpriseServices. Например, приведенный ниже атрибут устанавливает активацию в процессе вызывающего приложения.

[assembly: ApplicationActivation(ActivationOption.Library)]

Аналогичного эффекта можно добиться, используя Component Services (COM+) management tool


Для обеспечения безопасности можно осуществлять контроль доступа на уровне интерфейсов и методов. Данная опция также задается двумя альтернативными способами: с помощью атрибута ApplicationAccessControl или через Component Services (COM+) management tool.

Использование

Представленный исходный код может быть использован для написания пользовательских компонентов. Для добавления новых методов разработчик должен выполнить следующие действия:

  1. Определить пользовательские интерфейсы.
  2. Реализовать методы этих интерфейсов в классе компонента.
  3. Зарегистрировать компонент и подключить его к 1С.

Заключение

У COM+-компонентов есть ряд достоинств, которые я не буду детально описывать в этой статье. Достаточно упомянуть поддержку транзакций, Web-сервисов, Just in time активацию, ролевую безопасность и т. д. [9].

Благодаря использованию классов пространства System.Reflection время разработки 1C-компонента существенно сокращается. Разработчик может сосредоточиться на реализации пользовательских методов, не вникая в логику интерфейсов 1С.

Литература

  1. MSDN, .NET Framework Developer’s Guide. Serviced Component Overview.
  2. MSDN, .NET Framework Developer’s Guide. Strong-Named Assemblies.
  3. MSDN, Visual Studio Technical Articles. Troubleshooting .NET Interoperability.
  4. MSDN, .NET Framework Developer’s Guide. Default Marshaling for Arrays.
  5. Объектный поставщик данных для «1С:Предприятия» (COM и технология внешних компонент).
  6. Основы Visual C++. Девид Дж. Круглински. Русская Редакция. Microsoft Press.
  7. MSDN, Visual Studio Technical Articles. Building Managed Resources from Win32 Resources.
  8. Сублимированный опыт работы с внешними компонентами в 1С:Предприятии.
  9. Enterprise Services Technical Articles. .NET Enterprise Services and COM+ 1.5 Architecture.
  10. MSDN, .NET Development (General) Technical Articles. Understanding Enterprise Services (COM+) in .NET


Эта статья опубликована в журнале RSDN Magazine #3-2004. Информацию о журнале можно найти здесь