Интеграция приложений на платформе .NET (C#) с системой «1С:Предприятие 8» — одна из наиболее востребованных и одновременно сложных задач для разработчика. Традиционно перед программистом встает выбор между использованием технологии COM и кроссплатформенным стандартом Native API. В этой статье мы подробно разберем, как обойти технологические ограничения, создать полноценную внешнюю компоненту на C#, правильно упаковать ее в макет 1С и избежать классических ошибок инициализации.
Прежде чем перейти к практике, разберем теоретические основы взаимодействия сред выполнения. Исторически среда 1С общается с внешним миром двумя путями:
regasm или regsvr32), что накладывает ограничения на права пользователей и полностью исключает работу под Linux.Сложность для C# заключается в том, что стандартный компилятор .NET собирает код в промежуточный язык MSIL (Intermediate Language). Для его работы требуется виртуальная машина (CLR), поэтому обычную сборку .NET нельзя просто так загрузить в 1С как Native API компоненту. Тем не менее, существуют эффективные обходные пути: использование специализированных мостов-оберток (например, технологии Elisy или классов-конвертеров) и современная компиляция Native AOT в .NET.
Рассмотрим пример кода внешней компоненты, написанной на C# с использованием обертки Native API, которая часто вызывает проблемы у разработчиков:
using System.Runtime.InteropServices;
namespace V8.AddIn
{
public class SomeName
{
public SomeName()
{
}
[Alias("Выполнить")]
public string ProcessString(string text)
{
return "Hello world from c#";
}
}
[Guid("6a81d0a9-6441-463f-a0c9-ec7b1f2cbd56")]
[ProgId("AddIn.SomeComponent")]
[ComVisible(true)]
public class Some : LanguageExtenderAddIn
{
public Some() : base(typeof(SomeName), 1000)
{
}
}
}
При попытке вызвать эту компоненту в коде 1С стандартным образом:
ПодключитьВнешнююКомпоненту("AddIn.SomeComponent");
ОбъектКомпоненты = Новый("AddIn.SomeName");
ОбъектКомпоненты.Выполнить("Быстро");
Платформа выдает критическую ошибку: «Тип не определен (AddIn.SomeName)». Давайте выясним причины этого сбоя и исправим их.
Эта ошибка является следствием неверного понимания адресации объектов во внешних компонентах Native API. Проанализируем три главные причины проблемы:
1. Ошибка в синтаксисе обращения к объекту в 1С
При использовании Native API структура инициализации жестко регламентирована. Метод ПодключитьВнешнююКомпоненту принимает путь к макету или файлу и регистрирует символическое имя компоненты (алиас подключения). В коде выше в качестве первого параметра передан ProgId "AddIn.SomeComponent". В этом случае имя подключения регистрируется как SomeComponent.
Чтобы создать экземпляр класса компоненты, необходимо обращаться по схеме: Новый("AddIn.ИмяПодключения.ИмяКласса"). Автор кода ошибочно попытался создать объект напрямую по имени класса: Новый("AddIn.SomeName"), пропустив имя подключения. Правильный вызов должен выглядеть так:
// Подключаем компоненту по имени из макета
ПодключитьВнешнююКомпоненту("ОбщийМакет.eSignPushTest", "MyComponent", ТипВнешнейКомпоненты.Native);
// Создаем объект, указывая ИмяПодключения и ИмяКласса через точку
ОбъектКомпоненты = Новый("AddIn.MyComponent.Some");
2. Несоответствие разрядности платформы (32/64 бит)
Платформа 1С:Предприятие крайне чувствительна к разрядности загружаемых динамических библиотек. Если вы используете 64-битную операционную систему Windows, это еще не означает, что ваш клиент 1С работает в 64-битном режиме. Тонкие и толстые клиенты очень часто устанавливаются как 32-битные приложения (папка x86).
Если dll скомпилирована строго под платформу x64, а клиент 1С запущен в режиме x86 (или наоборот), платформа не сможет загрузить библиотеку на низком уровне и выдаст ошибку отсутствия типа. Рекомендуется компилировать компоненту в двух вариантах и правильно описывать их в манифесте сборки.
3. Ошибки в манифесте внешней компоненты
Для корректного подключения компоненты из общего макета 1С требуется файл манифеста MANIFEST.XML, который упаковывается вместе с DLL-файлами в один ZIP-архив. Посмотрим на структуру корректного манифеста, поддерживающего обе архитектуры под Windows:
<?xml version="1.0" encoding="UTF-8" ?>
<bundle xmlns="http://v8.1c.ru/8.2/addin/bundle">
<component os="Windows" path="eSignPushTest_x86.dll" type="native" arch="i386" />
<component os="Windows" path="eSignPushTest_x64.dll" type="native" arch="x86_64" />
</bundle>
Обратите внимание на атрибут arch: значение i386 соответствует 32-битному клиенту, а x86_64 — 64-битному. Файлы библиотек должны физически находиться в корне ZIP-архива рядом с файлом манифеста.
Если вы хотите создать действительно производительную, кроссплатформенную и автономную внешнюю компоненту Native API на C# без использования тяжелых COM-оберток и сторонних зависимостей, следует обратить внимание на технологию Native AOT (Ahead-of-Time compilation), появившуюся в современном .NET.
Native AOT компилирует код C# напрямую в машинный код (машинные инструкции целевой ОС). На выходе мы получаем стандартную библиотеку (DLL или SO), которая больше не требует установленного .NET Runtime на целевом компьютере и может напрямую экспортировать функции для 1С.
Разберем, как выглядит экспорт основных функций Native API в проекте C#:
using System.Runtime.InteropServices;
namespace MyNativeAddIn
{
public static class EntryPoints
{
[UnmanagedCallersOnly(EntryPoint = "GetClassNames")]
public static unsafe int GetClassNames(IntPtr names)
{
// Возвращаем имена классов компоненты для 1С
return 1;
}
[UnmanagedCallersOnly(EntryPoint = "GetClassObject")]
public static unsafe int GetClassObject(IntPtr name, IntPtr obj)
{
// Инициализируем объект компоненты
return 1;
}
[UnmanagedCallersOnly(EntryPoint = "DestroyObject")]
public static unsafe int DestroyObject(IntPtr obj)
{
// Освобождаем память объекта
return 1;
}
}
}
Использование атрибута UnmanagedCallersOnly позволяет среде 1С видеть методы .NET библиотеки так же, как если бы они были написаны на C++. Это полностью решает проблему кроссплатформенности: скомпилированная под Linux (.so) компонента будет без проблем работать на сервере 1С под управлением Linux.
Разработчикам внешних компонент важно помнить о стабильности работы всей системы. Если в коде C# компоненты произойдет непредвиденная ошибка (например, переполнение буфера при работе с сокетами или утечка памяти при обработке изображений), это может привести к аварийному завершению работы всего сеанса 1С или рабочего процесса сервера rphost.
Чтобы обезопасить систему, рекомендуется использовать встроенный в платформу 1С механизм изолированного подключения. Для этого при вызове метода подключения передавайте соответствующий параметр:
&НаКлиенте
Процедура ПодключитьИзолированно()
ИмяФайлаКомпоненты = "ОбщийМакет.eSignPushTest";
Попытка
// Подключаем компоненту в изолированном процессе ОС
ПодключитьВнешнююКомпоненту(ИмяФайлаКомпоненты, "eSign", ТипВнешнейКомпоненты.Native, , СпособПодключенияВнешнейКомпоненты.Изолированно);
КомпонентаПодключена = Истина;
Исключение
Предупреждение("Не удалось подключить компоненту: " + ОписаниеОшибки());
КонецПопытки;
Если КомпонентаПодключена Тогда
Попытка
ОбъектСДК = Новый("AddIn.eSign.Some");
Результат = ОбъектСДК.Выполнить("Параметр");
Сообщить(Результат);
Исключение
Сообщить("Ошибка вызова метода компоненты: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецПроцедуры
При изолированном подключении операционная система запускает компоненту в выделенном процессе. Любой критический сбой внутри C# кода изолируется и не приводит к падению клиентского приложения 1С, гарантируя бесперебойную работу пользователя.