При разработке внешних компонент (ВК) для 1С на C++ часто возникает задача не только вызывать функции компоненты из 1С, но и инициировать выполнение кода на стороне 1С из самой компоненты. Нередко это приводит к путанице, особенно у программистов, не имеющих глубокого опыта в C++. Разберем типичную ошибку и правильные подходы к решению этой задачи.
Проанализируем ситуацию: разработчик создал внешнюю компоненту по технологии Native API и пытается из нее выполнить код 1С, используя методы, характерные для COM-объектов, такие как GetPropertyByName и Invoke1. Это приводит к ошибкам, так как интерфейс, передаваемый в Native-компоненту, имеет совершенно другую природу.
// Пример ошибочного кода в Native-компоненте
#include "Component.h"
#include
// IDispatch *m_iConnect; // Неверный тип для Native API
// CComPtr m_iConnect; // Также неверно
bool Component::Init(void *connection_)
{
// connection_ здесь - это указатель на IAddInDefBase, а не на IDispatch
// Попытка привести его к IDispatch и использовать COM-методы обречена на провал
// m_iConnect = ?????? как инициализировать?
return connection_ != nullptr;
}
bool Component::CallAsFunc(...)
{
// Этот код предназначен для COM-компоненты и не будет работать в Native ВК
hr = m_iConnect.GetPropertyByName(L"AppDispatch", ...);
// ...
hr = pApp.Invoke1(L"New", &CComVariant(L"ТаблицаЗначений"), ...);
}
Выясним причину этой ошибки и рассмотрим корректные способы взаимодействия.
Ключевая проблема заключается в смешении двух совершенно разных технологий создания внешних компонент для 1С. Понимание их различий — основа для правильного проектирования взаимодействия.
1. Технология Native API
Это современный и рекомендуемый способ создания ВК. Компонента представляет собой нативную динамическую библиотеку (.dll в Windows, .so в Linux), которая загружается непосредственно в процесс 1С. Взаимодействие с платформой происходит через стандартизированный интерфейс IAddInDefBase.
Init передается указатель типа void*, который на самом деле является указателем на объект, реализующий интерфейс IAddInDefBase.Новый()), вызова процедур или получения свойств глобального контекста. Его основная задача — позволить компоненте генерировать внешние события и управлять своими свойствами и методами.2. Технология COM (Component Object Model)
Это классическая технология Microsoft, работающая только в среде Windows. Компонента регистрируется в системе как COM-сервер.
Init платформа 1С передает указатель на интерфейс IDispatch. Этот указатель и есть тот самый AppDispatch — точка входа в объектную модель 1С.IDispatch можно вызывать COM-методы GetPropertyByName, Invoke, Invoke1 и т.д., чтобы полноценно управлять 1С: создавать объекты, вызывать методы, читать и записывать свойства.Посмотрим на сигнатуру метода Init для COM-компоненты:
STDMETHODIMP CAddInCOM::Init(IDispatch *pConnection)
{
if (!pConnection) return E_FAIL;
m_iConnect = pConnection; // m_iConnect имеет тип IDispatch* или CComPtr
m_iConnect->AddRef();
return S_OK;
}
Таким образом, попытка использовать код для COM-взаимодействия внутри Native-компоненты неверна в своей основе. Не существует простого способа "превратить" указатель IAddInDefBase* в IDispatch*. Это разные сущности. Теперь рассмотрим, как же правильно решать задачу для Native-компонент.
Это основной и штатный способ для Native-компоненты инициировать выполнение кода на стороне 1С. Механизм работает асинхронно и идеально подходит для уведомления 1С о каких-либо событиях, произошедших в компоненте (например, получены данные от оборудования, завершился фоновый расчет).
Разберем по шагам:
Внутри метода компоненты, где нужно инициировать действие в 1С, вызовите метод ExternalEvent у интерфейса IAddInDefBase. Этот интерфейс вы получили и сохранили на этапе вызова Init.
// В классе компоненты
IAddInDefBase *connection;
// ... в методе Init
bool Component::Init(void *connection_) {
connection = static_cast(connection_);
return connection != nullptr;
}
// ... в методе, который должен вызвать код 1С
void Component::DoSomethingAndNotify1C() {
// ... какая-то работа ...
// Генерируем внешнее событие
// 1-й параметр: Имя источника (уникальное имя вашей компоненты)
// 2-й параметр: Имя события (команда, которую нужно выполнить)
// 3-й параметр: Данные (строка с параметрами, можно использовать разделители или JSON)
const WCHAR_T* source = L"MySuperComponent";
const WCHAR_T* event = L"DataReady";
const WCHAR_T* data = L"{\"file_id\": 123, \"status\": \"success\"}";
if (connection) {
connection->ExternalEvent(source, event, data);
}
}
В модуле формы, где используется компонента, необходимо создать процедуру-обработчик ОбработкаВнешнегоСобытия.
&НаКлиенте
Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные)
Если Источник = "MySuperComponent" Тогда
Если Событие = "DataReady" Тогда
// Здесь выполняется нужный код 1С
// Можно разобрать строку 'Данные', если она передана в формате JSON
Попытка
ЧтениеJSON = Новый ЧтениеJSON;
ЧтениеJSON.УстановитьСтроку(Данные);
СтруктураДанных = ПрочитатьJSON(ЧтениеJSON);
Сообщить("ВК сообщила о готовности данных для файла: " + СтруктураДанных.file_id);
// Вызов других процедур и функций 1С
ОбработатьГотовыеДанные(СтруктураДанных);
Исключение
Сообщить("Не удалось разобрать данные от ВК: " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Важный момент: вызов ExternalEvent является асинхронным. Код в C++ не будет ждать, пока выполнится обработчик в 1С, и не сможет напрямую получить от него результат. Этот способ предназначен для уведомлений, а не для вызова функций с возвратом значения.
Если требуется синхронное взаимодействие с получением результата, более современным и гибким решением будет публикация HTTP-сервиса на стороне 1С. Ваша внешняя компонента в этом случае будет выступать в роли HTTP-клиента.
Схема работы:
Преимущества этого подхода:
В заключение, важно четко понимать, какую технологию создания ВК вы используете. Если это Native API, забудьте про AppDispatch и COM-методы. Ваш основной инструмент для инициирования действий в 1С — это ExternalEvent. Если же требуется сложная логика с синхронным обменом данными, смотрите в сторону HTTP-сервисов.