Всем зрасьте!
В прошлый раз я красил газон, рассказывая, как там языки программирования появились, как до 1С-а мы дожили и как теперь писать диссертации про C++. Меня раскритиковали за туманное описание таинств. Но на горизонте событий черной дыры C++, где приличное высокоуровневое программирование вырождается в постоянное кипение вакуума вокруг выделения и освобождения памяти, скзать что-то достоверно - весьма непростая задача. Но мы попробуем еще раз зайти в эту реку. Ну или это будет уже другая река - не суть.
Итак, давайте рассмотрим практический кейс рисования внешней компоненты, которая будет оповещать нас из фонового задания, что обещает нам 1С аж в 26-м релизе, до которого еще жить и жить.
ЗАМЫСЕЛ БОЖЫЙ
Итак, мысль проста, как три копья. Странно, что 1С-негам это все в голову пришло только сейчас, ибо сделать оное было весьма просто, а уж додуматься - и сам Кришна велел.
Суть в том, что в современном мире микросервисов любой приличный язык программирования позволяет с полпинка запилить какой-нить сервис, основанный на обычном HTTP[S]-протоколе. Для C++ написано огромное количество различных библиотек для организации такого сервера. В одной из статей я писал про библиотеку restbed, в этой статье я раскажу о библиотеке httplib, о которой я узнал из комментов.
Суть же всего дива заключается в том, что внешняя компонента будет создавать локальный веб-сервер, а фоновое задание будет создавать HTTP-соединение и дергать его по любому поводу, в то время как компонента будет генерировать внешнее событие (например, в форме, из которой запущено фоновое задание). Ну и в ходе обработки этого внешнего события мы будем двигать прогресс-бар и писать слова.
И да, я все это на Linux буду делать. Для винды этот код тоже подойдет, но как там его компилить - сами разбирайтесь, ибо мне вломы искать винду - я ей только на работе пользуюсь.
ОСНОВЫ
За основу я взял шаблон от kandr, который не глючит под 64-битным линухом, в отличие от шаблона от infactum. Единственная проблема в том, что шаблон от kandr не умеет внешние события, поэтому их в него воткнуть.
Итак, приступим...
БИБЛИОТЕКА httplib
Установка
Речь о библиотеке для организации сервера. Итак, у меня в убунту это делается так:
sudo apt install libcpp-httplib-dev
Сборка ВК
Для сборки ВК с этой библиотекой мы будем использовать такое заклинание:
g++ AddInNative.cpp TestComponent.cpp -I../include -I/usr/include -fPIC -march=x86-64 -O2 -lcpp-httplib --std=c++17 -o mylib.so
Если использовать ключ -shared, то есть вероятность того, что собранная компонента заработает не только на вашем компьютере.
МОДИФИКАЦИЯ КОДА ВНЕШНЕЙ КОМПОНЕНТЫ
Итак, мы установили библиотеку httplib, скачали шаблон внешней компоненты. Остается теперь внести в этот шаблон правки, чтобы у нас все заработало.
Для начала чуть подправим функционал для того, чтобы нам обрабатывать внешнее событие.
Для этого в файл "AddinTemplate/src/AddInNative.cpp" нужно добавить функцию для организации внешнего события:
bool AddInNative::ExtEvent(const std::u16string& cname, const std::u16string& cevent, const std::u16string& ctext)
{
return m_iConnect && m_iConnect->ExternalEvent((WCHAR_T*)cname.c_str(), (WCHAR_T*)cevent.c_str(), (WCHAR_T*)ctext.c_str());
}
Собственно, мы тут просто вызываем функцию ExternalEvent 1С-ного интерфейса iConnect. Оное действо как раз вызывает процедуру ВнешнееСобытие в форме (ну или ОбработкаВнешнегоСобытия в модуле приложения).
Дальше нам нужно поправить файл "AddinTemplate/src/AddInNative.h". И тут я позволю себе немного поленится и покласть эту функцию в секцию public, чтобы не мучаться в дальнейшем с пробросом этого всего в наш класс.
bool ExtEvent(const std::u16string& cname, const std::u16string& cevent, const std::u16string& ctext);
Так, с внешними событиями покончили.
Дальше приступим к реализации нашего веб-сервера. Для этого поменяем файл "AddinTemplate/src/TestComponent.cpp", в который добавим следующие строки:
#include <thread>
#include <codecvt>
#include "httplib.h"
std::vector<std::thread> t1;
void start_thread (void * _obj)
{
httplib::Server svr;
TestComponent * progress((TestComponent*)_obj);
svr.Get("/hi", [&](const httplib::Request &r, httplib::Response &res) {
std::string s;
for (auto p : r.params) {
s+= p.first + ":" + p.second + "\n";
}
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
const std::u16string s0 = convert.from_bytes(s);
progress->setR(s0);
res.set_content(s, "text/plain; charset=utf-8");
});
svr.listen("0.0.0.0", 8080);
return;
}
void TestComponent::startListen()
{
t1.push_back ( std::thread(start_thread, (void *) this) );
}
Собсно, какую мы тут дичь сотворили?
1. Добавили заголовки для работы с потоками, конвертированием строк и нашу httplib.
2. Добавили функцию start_thread, которая стартует поток, в котором создается веб-сервис, слушающий порт 8080 и реагирующий на HTTP GET /hi.
3. Добавили функцию startListen, которая будет вызываться методом внешней компоненты ЗапуститьСервер().
Для того, чтобы это сработало, нам нужно в функцию "TestComponent::TestComponent()" добавить следующий код:
AddProcedure(u"StartListen", u"ЗапуститьСервер", [&]() { this->startListen(); });
Дальше нам нужно создать функцию "setR", которая вызывается из нашего обработчика GET, которая, в свою очередь, должна вызывать функцию ExternalEvent.
Давайте сделаем это!
void TestComponent::setR(const std::u16string &text)
{
std::u16string event = u"ExternalEvent";
std::u16string name = u"AddIn." + name;
ExtEvent(name, event, text);
}
Ну и добавим в файл "AddinTemplate/src/TestComponent.h" следующий код:
public:
void setR(const std::u16string &text);
ПОДКЛЮЧЕНИЕ К 1С
Итак, давайте подоткнем это к 1С. Для этого я создал новую конфигурацию, сотворил там общую форму, в которой прописал подключение компоненты и запуск веб-сервиса, а также обработку внешнего события:
&НаКлиенте
асинх Процедура ПриОткрытии(Отказ)
МетоположениеКомпоненты = "/home/.../AddinTemplate/src/mylib.so";
Т = Ждать ПодключитьВнешнююКомпонентуАсинх(МетоположениеКомпоненты, "AAA", ТипВнешнейКомпоненты.Native);
ВнешняяКомпонента = Новый("AddIn.AAA.AddInNative");
ВнешняяКомпонента.ЗапуститьСервер();
КонецПроцедуры
&НаКлиенте
Процедура ВнешнееСобытие(Источник, Событие, Данные)
Сообщить(Источник);
Сообщить(Событие);
Сообщить(Данные);
КонецПроцедуры
А теперь давайте дернем "http://localhost:8080/hi?a=Привет, мир!" И вот что у нас получилось:
Т.е. компонента передала пару Параметр:Значение в 1С!
Ну я надеюсь, что дальше эту конструкцию Вы и сами допилите, применив оное колдунство для оповещения с сервера. И да, файл не прикладываю - в статье достаточно информации, чтобы это повторить, ну и вирусов никаких, раз файла нет)))
ЗАКЛЮЧЕНИЕ
Я пришел домой в 18:30, в 20:30 статья была готова. О чем это говорит? О том, что нет ничего сложного в создании внешней компоненты, которая дернет из фонового задания вашу форму и передаст данные о том, что там вообще и как.
Всем всех благ! Подписывайтесь на каналы, ставьте лайки, пишите комменты!