Как правильно подключить внешнюю C# компоненту к 1С и настроить передачу внешних событий?

Программист 1С v8.3 (Управляемые формы)
← На главную

При разработке и подключении внешних компонент на C# для платформы 1С:Предприятие часто возникают сложности. В этой статье мы подробно разберем типичные проблемы, с которыми сталкиваются разработчики, включая ошибку "The system cannot find the file specified" при создании COM-объекта, а также отсутствие реакции на внешние события 1С. Мы рассмотрим пошаговое решение этих проблем, а также предоставим необходимые примеры кода.

Начальные сложности: Ошибка "The system cannot find the file specified" (0x80070002)

Первая проблема, с которой вы можете столкнуться, это ошибка "-2147024894(0x80070002): The system cannot find the file specified" при попытке создать COM-объект в 1С. Эта ошибка означает, что система не может найти или загрузить вашу зарегистрированную компоненту.

Рассмотрим пример кода компоненты на C#, предложенный автором темы:


using System;
using System.Runtime.InteropServices;

namespace AddIn
{
    [Guid("8C034F6A-1D3F-4DB8-BC99-B73873D8C297")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)]
    [ProgId("AddIn.TestCalc")]
    public class Calculator
    {
        public int Sum(int a, int b)
        {
            return a + b;
        }
    }
}

И код 1С для её подключения:


// ПодключитьВнешнююКомпоненту("AddIn.TestCalc"); // Эта строка была избыточна для первоначальной проблемы
ExtComponent = Новый COMОбъект("AddIn.TestCalc");
Сообщить(ExtComponent.FName()); // Здесь была ошибка

Мы видим, что в коде 1С происходит обращение к методу FName(), тогда как в C# классе Calculator существует только метод Sum(). Это одна из возможных причин ошибки, но часто сама ошибка "файл не найден" указывает на более глубокие проблемы с регистрацией или разрядностью.

Причины ошибки "The system cannot find the file specified" (0x80070002) и их решение

Давайте проанализируем основные причины возникновения этой ошибки и способы их устранения:

  1. Опечатка в имени метода или ProgID: Как было упомянуто выше, убедитесь, что имя метода, который вы вызываете из 1С, точно соответствует имени метода в вашей C# компоненте. Также внимательно проверьте ProgID (в нашем случае "AddIn.TestCalc"), который используется для создания COMОбъект.
  2. Несоответствие разрядности (битности) компонентов: Это одна из наиболее распространенных причин. Если вы используете 32-разрядный (x86) клиент 1С, то ваша C# компонента также должна быть скомпилирована под платформу x86, и для её регистрации необходимо использовать RegAsm.exe из 32-разрядной версии .NET Framework (например, из C:\Windows\Microsoft.NET\Framework\v4.0.30319\). Аналогично, для 64-разрядного (x64) клиента 1С, компонента и RegAsm.exe (из папки Framework64) должны быть 64-разрядными. Несоответствие разрядностей приведет к тому, что 1С просто не сможет найти зарегистрированную компоненту, даже если регистрация прошла успешно.
  3. Регистрация без параметра /codebase: При регистрации сборки .NET для COM-взаимодействия крайне рекомендуется использовать ключ /codebase. Этот ключ прописывает в реестре полный путь к вашей DLL. Без него сборка должна находиться либо в одной папке с исполняемым файлом 1С, либо в глобальном кэше сборок (GAC), что усложняет развертывание.
    Команда регистрации должна выглядеть так:
    
    c:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe "Путь\К\Вашей\TestCompCalc.dll" /tlb /codebase
    
    Не забудьте использовать правильный путь к RegAsm.exe в зависимости от разрядности вашей компоненты.
  4. Отсутствие прав администратора при регистрации: Утилита RegAsm.exe вносит изменения в системный реестр, поэтому командную строку для регистрации необходимо запускать от имени администратора. В противном случае регистрация может быть выполнена некорректно или не полностью.
  5. Зависимости компоненты: Если ваша C# библиотека использует другие сторонние сборки (DLL-файлы), они должны быть доступны в той же папке, где находится основная DLL компоненты, либо также зарегистрированы в GAC. Ошибка "файл не найден" может относиться не к самой компоненте, а к одной из её зависимостей.

После устранения этих проблем, обращение к существующему методу, например, Sum(), должно работать:


ExtComponent = Новый COMОбъект("AddIn.TestCalc");
Сообщить(ExtComponent.Sum(10, 20)); // Вызовет метод Sum с параметрами 10 и 20

Внешние события и "Object reference not set to an instance of an object"

После успешного подключения компоненты и вызова методов, следующей задачей, как правило, является организация обратной связи — передача событий из C# компоненты в 1С. Здесь возникает новая проблема: метод ВнешнееСобытие в 1С не срабатывает, а при попытке вызова ExternalEvent из C# возникает ошибка "Object reference not set to an instance of an object".

Давайте выясним, почему так происходит, и как правильно реализовать внешние события.

Различие между Новый COMОбъект и ПодключитьВнешнююКомпоненту

Мы должны четко понимать, что Новый COMОбъект() и ПодключитьВнешнююКомпоненту() — это две принципиально разные технологии взаимодействия 1С с внешними компонентами:

  1. Новый COMОбъект("ProgID"): Это стандартный механизм Windows для работы с любыми COM-серверами. 1С в данном случае выступает как обычный COM-клиент, который может создавать экземпляры COM-объектов и вызывать их методы/свойства. При использовании этого подхода, 1С не знает о специфике интеграции с "Технологией создания внешних компонент" платформы 1С:Предприятие. Как следствие, такие важные для внешних компонент механизмы, как генерация внешних событий (через метод ExternalEvent, который должен быть передан компоненте), не работают. Метод Init вашей C# компоненты, который предназначен для инициализации связи с 1С, никогда не вызывается.
  2. ПодключитьВнешнююКомпоненту("ProgID", "ИмяКомпоненты", ТипВнешнейКомпоненты.COM): Это специальный механизм платформы 1С:Предприятие, предназначенный для работы с компонентами, разработанными по "Технологии создания внешних компонент". Компонента, созданная по этой технологии (будь то COM или Native API), должна реализовывать определенные интерфейсы (например, IInitDone, ILanguageExtender), которые 1С использует для более тесной и специфичной интеграции. Только при подключении компоненты этим способом, 1С вызывает метод Init вашей компоненты и передает ей необходимые для работы объекты, включая интерфейс для отправки внешних событий.

Почему ВнешнееСобытие не работает и возникает ошибка "Object reference not set..."

Проблема с ВнешнимСобытием напрямую вытекает из различий, описанных выше. Давайте разберем по шагам:

  1. Интерфейс IAsyncEvent: Для того чтобы компонента могла отправлять события в 1С, платформа должна передать ей ссылку на специальный объект, реализующий интерфейс IAsyncEvent. Этот интерфейс содержит метод ExternalEvent, который инициирует событие в 1С.
  2. Метод Init: Передача этого объекта происходит в момент инициализации компоненты, когда 1С вызывает в ней специальный метод Init. Этот метод является частью стандарта "Технологии создания внешних компонент 1С". Внутри этого метода вы должны получить ссылку на объект 1С, который реализует IAsyncEvent, и сохранить её для последующего использования.
  3. Причина ошибки: Когда вы создаете объект через Новый COMОбъект("AddIn.TestCalc"), платформа 1С не рассматривает его как "свою" внешнюю компоненту и, соответственно, никогда не вызывает метод Init. В результате этого, ваше поле AsyncEvent (или AsyncEventConnection) в C# коде остается неинициализированным (равным null). Любая попытка вызвать AsyncEvent.ExternalEvent(...) приведет к ошибке Object reference not set to an instance of an object (Ссылка на объект не указывает на экземпляр объекта), что мы и наблюдали в коде автора.

Реализация внешней компоненты на C# с поддержкой событий 1С

Чтобы ваша C# компонента могла отправлять внешние события в 1С, мы должны создать её как полноценную внешнюю компоненту 1С и подключать её через ПодключитьВнешнююКомпоненту().

Рассмотрим структуру C# компоненты, которая поддерживает внешние события:

  1. Определяем интерфейс IAsyncEvent: Этот интерфейс должен быть доступен вашей компоненте. Чаще всего он копируется из стандартных шаблонов или документации 1С.
  2. 
    using System;
    using System.Runtime.InteropServices;
    
    namespace AddIn
    {
        // Интерфейс для вызова событий в 1С
        // GUID может быть любым уникальным, но обычно используется стандартный из шаблонов
        [Guid("C5091D5F-106A-4FE4-8973-05B30DEE2C33")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        [ComVisible(true)]
        public interface IAsyncEvent
        {
            void ExternalEvent([MarshalAs(UnmanagedType.BStr)] string Source,
                               [MarshalAs(UnmanagedType.BStr)] string Event,
                               [MarshalAs(UnmanagedType.BStr)] string Data);
        }
    
        // ... другие необходимые интерфейсы, такие как IInitDone, ILanguageExtender, IProperties
        // Для упрощения примера и использования ClassInterfaceType.AutoDual, мы можем
        // не реализовывать их явно, но для промышленной разработки это рекомендуется.
    }
    
  3. Изменяем класс компоненты для поддержки инициализации и событий: Наша компонента должна иметь метод Init и переменную для хранения ссылки на IAsyncEvent. Для простоты, мы используем ClassInterfaceType.AutoDual, чтобы 1С могла "видеть" методы класса без реализации всех интерфейсов ILanguageExtender.
  4. 
    using System;
    using System.Runtime.InteropServices;
    // using System.Windows.Forms; // Можно использовать для отладочных сообщений
    
    namespace AddIn
    {
        // Определение IAsyncEvent (как показано выше)
    
        [Guid("8C034F6A-1D3F-4DB8-BC99-B73873D8C297")] // GUID вашей компоненты
        [ClassInterface(ClassInterfaceType.AutoDual)] // Автоматически генерировать COM-интерфейс
        [ComVisible(true)]
        [ProgId("AddIn.TestCalc")]
        public class Calculator // Класс вашей компоненты
        {
            // Переменная для хранения ссылки на объект 1С, через который вызываются события
            protected IAsyncEvent AsyncEventConnection;
    
            // Метод Init, который вызывается 1С при подключении компоненты
            // Здесь мы получаем ссылки на необходимые объекты 1С
            public void Init([MarshalAs(UnmanagedType.IDispatch)] object connection)
            {
                // connection - это объект 1С, который реализует различные интерфейсы
                // для взаимодействия с компонентой (IAsyncEvent, IStatusLine, IErrorLog и др.)
                AsyncEventConnection = connection as IAsyncEvent;
    
                if (AsyncEventConnection == null)
                {
                    // Если IAsyncEvent не удалось получить, то внешние события не будут работать
                    // Можно записать в лог или вывести сообщение об ошибке
                    // MessageBox.Show("Не удалось инициализировать IAsyncEvent. Внешние события не будут работать.");
                }
                // Можно инициализировать другие интерфейсы, если они нужны, например:
                // IStatusLine statusLine = connection as IStatusLine;
                // if (statusLine != null) statusLine.SetStatusLine("Компонента успешно инициализирована.");
            }
    
            // Метод, который 1С может вызвать для генерации событий
            public void TestEventGenerator()
            {
                if (AsyncEventConnection != null)
                {
                    for (int i = 0; i < 3; i++)
                    {
                        // Вызов ExternalEvent передает событие в 1С
                        AsyncEventConnection.ExternalEvent("МойКомпонент", "СобытиеИзКалькулятора", "Данные " + (i + 1).ToString());
                    }
                    // MessageBox.Show("События отправлены в 1С.");
                }
                else
                {
                    // MessageBox.Show("IAsyncEvent не инициализирован. Не могу отправить событие.");
                }
            }
    
            // Оригинальный метод из примера автора
            public int Sum(int a, int b)
            {
                return a + b;
            }
    
            // Метод Done, вызывается 1С при отключении компоненты (очистка ресурсов)
            public void Done()
            {
                AsyncEventConnection = null; // Освобождаем ссылку
            }
        }
    }
    

Важное примечание: Использование ClassInterfaceType.AutoDual упрощает создание компоненты для начинающих, поскольку 1С автоматически "видит" все публичные методы и свойства. Однако, для более стабильных и версионируемых компонент, особенно в промышленной разработке, рекомендуется использовать ClassInterfaceType.None и явно реализовывать интерфейсы ILanguageExtender, IInitDone и другие. Для этого существуют готовые шаблоны проектов (например, от Serginio), которые значительно упрощают эту задачу.


Перем ОбъектВнешнейКомпоненты;

Процедура ПриОткрытии() // Или ПриНачалеРаботыСистемы() для глобального модуля
    Попытка
        // Подключаем внешнюю компоненту по её ProgID
        // "AddIn.TestCalc" - ProgID вашей C# компоненты
        // "МояТестоваяКомпонента" - произвольное имя для компоненты в 1С
        ОбъектВнешнейКомпоненты = ПодключитьВнешнююКомпоненту("AddIn.TestCalc", "МояТестоваяКомпонента", ТипВнешнейКомпоненты.COM);
            
        Если ОбъектВнешнейКомпоненты = Неопределено Тогда
            Сообщить("Не удалось подключить внешнюю компоненту.", СтатусСообщения.Важное);
        Иначе
            Сообщить("Внешняя компонента 'МояТестоваяКомпонента' успешно подключена.", СтатусСообщения.Информация);
                
            // Теперь можно вызывать методы компоненты
            Сообщить("Результат Sum(5, 7): " + ОбъектВнешнейКомпоненты.Sum(5, 7));
                
            // Вызываем метод компоненты, который генерирует внешние события
            ОбъектВнешнейКомпоненты.TestEventGenerator();
        КонецЕсли;
    Исключение
        Сообщить("Ошибка при подключении или работе с внешней компонентой: " + ОписаниеОшибки(), СтатусСообщения.Ошибка);
    КонецПопытуры;
КонецПроцедуры

// Обработчик внешних событий
Процедура ВнешнееСобытие(Источник, Событие, Данные)
    Сообщить("--- Получено внешнее событие ---", СтатусСообщения.Внимание);
    Сообщить("Источник: " + Источник);
    Сообщить("Событие: " + Событие);
    Сообщить("Данные: " + Данные);
    Сообщить("------------------------------", СтатусСообщения.Внимание);
КонецПроцедуры

В этом коде 1С мы сначала подключаем компоненту, затем вызываем её метод TestEventGenerator(). Этот метод в C# компоненте, в свою очередь, использует сохраненную ссылку AsyncEventConnection для вызова ExternalEvent, отправляя событие обратно в 1С. 1С перехватывает это событие в процедуре ВнешнееСобытие.

Альтернативы и дополнительные рекомендации

Мы рассмотрели традиционный подход с использованием COM-компонент, но существует и более современная альтернатива.

Технология Native API

В обсуждении упоминалась технология Native API как более современная альтернатива COM для внешних компонент 1С. Давайте разберем её ключевые особенности:

  1. Преимущества Native API:
    • Не требует регистрации в реестре: Это главное и очень весомое преимущество. Для распространения компоненты достаточно просто скопировать файл DLL в нужную папку. Это значительно упрощает установку, обновление и устранение проблем на клиентских машинах.
    • Кроссплатформенность: Компоненты, написанные с использованием Native API, могут работать не только в Windows, но и в Linux, что расширяет возможности использования 1С-компонент в разных операционных средах.
    • Производительность: В некоторых случаях может быть достигнута лучшая производительность, так как отсутствует дополнительный уровень COM-маршалинга.
  2. Недостатки Native API:
    • Сложность разработки: Стандартным языком для Native API компонент является C++, что для C#-разработчика может быть сложнее. Однако существуют готовые шаблоны и обертки, которые позволяют создавать Native API компоненты и на C# (например, с использованием UnmanagedExports или других наработок).
    • Отсутствие автоматического интерфейса: В отличие от ClassInterfaceType.AutoDual в COM, для Native API требуется явное описание всех методов и свойств, которые будут доступны из 1С.

Если вы планируете долгосрочную разработку или столкнулись с необходимостью кроссплатформенности, изучение Native API может быть оправдано, но для большинства простых задач COM-компоненты остаются рабочим и понятным решением, особенно при использовании готовых шаблонов.

Заключение

Мы подробно разобрали, как создать и правильно подключить C# COM-компоненту к 1С, а также как настроить механизм внешних событий. Ключевыми моментами являются:

  1. Правильная регистрация: Убедитесь в соответствии разрядностей, использовании /codebase и административных правах для RegAsm.exe.
  2. Различие в подключении: Используйте ПодключитьВнешнююКомпоненту(), а не Новый COMОбъект(), для работы со специфичными для 1С механизмами, такими как внешние события.
  3. Инициализация IAsyncEvent: В методе Init вашей C# компоненты обязательно получите и сохраните ссылку на интерфейс IAsyncEvent для последующей отправки событий в 1С.
  4. Аккуратность в коде: Внимательно проверяйте соответствие имен методов и ProgID между C# и 1С.

Следуя этим рекомендациям, вы сможете успешно интегрировать ваши C# компоненты с платформой 1С:Предприятие и использовать все преимущества двухстороннего взаимодействия.

← На главную