Технология Native API от компании 1С является современным стандартом создания внешних компонент. В отличие от устаревшей технологии COM, Native API позволяет создавать кроссплатформенные решения, которые работают не только в Windows, но и в Linux, а также в веб-клиенте. Основная сложность при использовании C# заключается в том, что 1С ожидает от библиотеки экспорта функций в стиле C (машинный код), тогда как стандартный .NET генерирует управляемый байт-код. В качестве альтернативы для разработки кроссплатформенных компонент можно использовать руководство по созданию внешней компоненты на Python.
В данной статье мы разберем, как преодолеть эти ограничения, настроить экспорт функций и создать полноценный шаблон внешней компоненты на языке C#. Для ознакомления с другим практическим решением можно использовать пример разработки внешней компоненты Native API для мониторинга файловой системы.
Разберем основы взаимодействия. 1С загружает динамическую библиотеку (.dll или .so) и ищет в ней три обязательные функции:
GetClassObject — создание экземпляра объекта компоненты.DestroyObject — удаление объекта и освобождение памяти.GetClassNames — получение списка имен классов, реализованных в компоненте.Для реализации этого на C# нам необходимо сделать управляемый код "видимым" для неуправляемой среды 1С. Рассмотрим два основных способа решения этой задачи.
С выходом .NET 7 и .NET 8 появилась технология Native Ahead-Of-Time (NativeAOT). Она позволяет скомпилировать проект C# непосредственно в машинный код. Это идеальный вариант для Native API 1С, так как на выходе получается библиотека, не требующая установленного .NET Runtime на целевом компьютере.
Проанализируем процесс настройки проекта:
Class Library..csproj) добавим настройку <PublishAot>true</PublishAot>.[UnmanagedCallersOnly] для функций, которые должна видеть 1С.Пример экспорта функции инициализации:
[UnmanagedCallersOnly(EntryPoint = "GetClassObject")]
public static unsafe int GetClassObject(byte* name, IntPtr* componentObj)
{
// Логика создания объекта компоненты
return 1; // Успех
}
Если вы работаете на .NET Framework или .NET Core старых версий, используйте NuGet-пакет DllExport. Он модифицирует таблицу экспорта DLL, добавляя туда ваши методы.
Рассмотрим пример реализации метода CallAsFunc, который вызывает функцию компоненты из 1С. В обсуждении на форуме упоминалось, что важно правильно передавать массив параметров. Посмотрим, как это выглядит в коде:
public bool CallAsFunc(int lMethodNum, ref object pvarRetValue, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] ref object[] pParams)
{
try
{
// Проанализируем номер метода, приходящий из 1С
if (lMethodNum == 0) // Например, наш первый метод
{
pvarRetValue = "Результат из C#";
return true;
}
return false;
}
catch (Exception ex)
{
// Обработка ошибок
return false;
}
}
Важно помнить, что в Native API 1С использует структуру tVariant для передачи данных. При работе на низком уровне нам придется описывать эту структуру вручную, используя LayoutKind.Explicit.
Выясним причину, по которой многие разработчики сталкиваются с трудностями при передаче строк и чисел. 1С передает данные через указатели на структуру tVariant. На стороне C# мы должны объявить её следующим образом:
[StructLayout(LayoutKind.Explicit, Size = 24)]
public struct tVariant
{
[FieldOffset(0)] public short vt; // Тип значения (VTYPE)
[FieldOffset(8)] public long iVal;
[FieldOffset(8)] public double dVal;
[FieldOffset(8)] public bool bVal;
[FieldOffset(8)] public IntPtr pstrVal; // Для строк
[FieldOffset(16)] public uint strLen; // Длина строки
}
При передаче строк из C# в 1С помните, что 1С ожидает кодировку UTF-16. Также необходимо использовать менеджер памяти 1С (IMemoryManager), чтобы выделить буфер под строку, который 1С сама сможет потом очистить. Если выделить память через стандартный Marshal.AllocHGlobal, возникнет утечка памяти, так как 1С не сможет её освободить.
Основная логика компоненты должна быть сосредоточена в классе, реализующем методы интерфейса IComponentBase. Разберем основные шаги реализации:
IAddInDefBase). Сохраните его, чтобы иметь возможность вызывать события или обращаться к свойствам платформы."MyComponent").Посмотрим на пример, как правильно извлечь данные из массива параметров paParams, который приходит в метод CallAsFunc. Допустим, нам передали строку и число:
// paParams - это указатель на массив структур tVariant
// lSizeArray - количество параметров
public bool CallAsFunc(int lMethodNum, IntPtr pvarRetValue, IntPtr paParams, int lSizeArray)
{
if (lMethodNum == 1) // Метод "Сложить"
{
// Извлекаем первый параметр (число)
tVariant var1 = Marshal.PtrToStructure<tVariant>(paParams);
int a = (int)var1.iVal;
// Извлекаем второй параметр
IntPtr nextParamPtr = IntPtr.Add(paParams, Marshal.SizeOf<tVariant>());
tVariant var2 = Marshal.PtrToStructure<tVariant>(nextParamPtr);
int b = (int)var2.iVal;
// Записываем результат обратно в pvarRetValue
tVariant result = new tVariant();
result.vt = 3; // VT_I4 (целое число)
result.iVal = a + b;
Marshal.StructureToPtr(result, pvarRetValue, false);
return true;
}
return false;
}
Проанализируем ситуацию с регистрацией. COM-компоненты требуют регистрации в реестре Windows через regasm или regsvr32, что часто требует прав администратора. Компонента Native API, созданная на C#, подключается простой командой в коде 1С:
// Пример на языке 1С
Результат = ПодключитьВнешнююКомпоненту("ОбщийМакет.МояКомпонентаCSh", "MyComp", ТипВнешнейКомпоненты.Native);
Если Результат Тогда
ОбъектВК = Новый ("AddIn.MyComp.MainClass");
ОбъектВК.МетодИзСиШарпа();
КонецЕсли;
Это делает развертывание решения максимально простым: достаточно включить dll (и so для Linux) в состав макета конфигурации. При использовании .NET 8 NativeAOT ваша компонента будет работать быстро и стабильно, не уступая по производительности решениям на C++.
Важный момент: Если вы планируете поддержку Linux, убедитесь, что вы не используете Windows-специфичные библиотеки (например, System.Drawing или прямое обращение к WinAPI). В остальном код на C# будет полностью переносимым.