Стояла сложная задача: настроить возможность совместной работы нескольких пользователей в одном заказе, чтобы не было блокировок и изменения других пользователей подсвечивались.
Поначалу пытался решить эту задачу через использование обработчиков ожидания. Но это решение не понравилось, операторы хотели сразу видеть изменения других пользователей, были также другие причины.
Затем была изучена и протестирована типовая система взаимодействия. Эта система вполне подходила, но был жирный минус: она почему-то отказывалась работать на обычных формах. Так как работа шла в конфигурации УТ 10.3, то эта система также была отброшена.
Начался мучительный поиск внешней компоненты для передачи оповещений по UDP или TCP. Большинство найденных компонент работали только на платформе 1С win32, а у нас стояла платформа 64 бит. Подходящая компонента все же была найдена и протестирована. Но так как она не поддерживала передачу кириллицы, то также была отброшена.
В итоге решил написать свою компоненту, которую назвал MyLib, так как появилась идея использовать ее не только для udp или tcp оповещения, но по мере необходимости добавлять туда и другой функционал. Компонента написана при помощи Visual Studio 2013 на C++ по технологии Native. За основу был взят пример с диска ИТС, поэтому некоторые методы компоненты остались по наследству. Все свойства и методы можно писать как по русски, так и по английски.
Свойства:
- PortTypeIsTCP (ПотоковыйТипПорта) - логический тип. Задает тип оповещения: по tcp или udp.
- LocalPort (Локальный порт) - целочисленный тип. Задает номер порта, который будет открыт для прослушивания.
- Status (Статус) - строковый тип. Служебное свойство, которое можно использовать для отладки.
- LogFile (ЛогФайл) - строковый тип. Здесь задается полный путь к текстовому файлу, в который компонента может писать логи. Это использовалось на этапе отладки, сейчас не применяется.
Методы:
- ShowInStatusLine(<Текст>) (ПоказатьВСтрокеСтатуса) - этот метод достался по наследству. На 5 секунд выводит в строку статуса полученный Текст.
- ExternalEvent(<Источник>, <Событие>, <Данные>) (ВнешнееСобытие) - достался по наследству. Возвращает true - событие помещено в очередь, или false - очередь переполнена, обработка событий недоступна или неизвестная ошибка. Помещает событие в очередь, записывая источник события (Источник - тип Строка), наименование (Событие - тип Строка) и параметры события (Данные - тип Строка). При обработке события эти данные передаются процедуре ВнешнееСобытие(<Источник>, <Событие>, <Данные>). ExternalEvent доступен только на клиенте.
- LoadPicture(<ИмяФайла>) (ЗагрузитьКартинку) - достался по наследству. Загружает изображение из указанного файла и передает его в "1С:Предприятие".
- ShowMessageBox() (ПоказатьСообщение) - достался по наследству. Выводит сообщение о версии платформы.
- OpenPort() (ОткрытьПорт) - запускает прослушивание порта, который задан в свойстве LocalPort.
- ClosePort() (ЗакрытьПорт) - останавливает прослушивание порта LocalPort.
- NotifyPort(<Порт>, <Источник>, <Событие>, <Данные>) (ОповеститьПорт) - посылает оповещение клиенту, у которого открыт Порт. У клиента при этом срабатывает процедура ВнешнееСобытие(<Источник>, <Событие>, <Данные>). Источник, Событие, Данные - это параметры строкового типа, не более 1 Кб длины, т.е. максимум 1024 символа каждый.
- Pause(<Миллисекунд>) (Пауза) - останавливает выполнение программы на некоторое время без нагрузки на процессор.
- Loopback(<ДвоичныеДанные>) (Петля) - достался по наследству. Метод принимает один аргумент типа ДвоичныеДанные и возвращает его копию.
В моем случае пользователи 1С работают на терминальном сервере, т.е. IP всегда равен 127.0.0.1, поэтому не выводил IP как параметр в метод NotifyPort(), чтобы не усложнять.
Работа компоненты тестировалась на платформах 1С: 8.3.12.1616 32бит, 8.3.13.1809 64бит, 8.3.15.1489 64бит. Операционные системы: Windows server 2008, Windows 8. В настоящее время проект по совместной работе пользователей в одном заказе полностью реализован и используется.
Простой пример подключения и использования MyLib
- Добавляем в конфигурацию общий макет:
В архив AddInNativeWin.zip включены 3 файла: AddInNativeWin32.dll, AddInNativeWin64.dll, MANIFEST.XML.
- В модуле обычного приложения:
Объявляем глобальную переменную:
Перем MyLib Экспорт;
Находим процедуру ПриНачалеРаботыСистемы и вставляем туда код:
//Стартуем сервер взаимодействия
Если ПодключитьВнешнююКомпоненту("ОбщийМакет.MyLib","MyLib",AddInType.Native) Тогда
MyLib = Новый("AddIn.MyLib.CppNativeExtension");
MyLib.LocalPort = МодульВзаимодействия.ПолучитьЛокальныйПорт();
MyLib.PortTypeIsTCP = Истина;
MyLib.OpenPort();
Иначе
Предупреждение("Компонента MyLib не подключена!");
КонецЕсли;
В процедуре ПриЗавершенииРаботыСистемы вставляем код:
//Останавливаем сервер взаимодействия
Если MyLib <> Неопределено Тогда
MyLib.ClosePort();
MyLib = Неопределено;
КонецЕсли;
- Добавляем в конфигурацию общий модуль МодульВзаимодействия (в свойствах ставим всего одну галочку "Сервер"):
//Данный алгоритм присвоения номера порта пользователю корректен только в том случае, если предположить,
//что пользователи не будут открывать по несколько сеансов одной базы 1С:Предприятие под одним именем.
//В противном случае, придется разрабатывать более сложную систему назначения портов.
Функция ПолучитьЛокальныйПорт() Экспорт
ЛокальныйПорт = 1024;
ТекущийПользовательИБ = ПользователиИнформационнойБазы.ТекущийПользователь();
Для Каждого Соединение Из ПолучитьСоединенияИнформационнойБазы() Цикл
Если Соединение.ИмяПриложения <> "Designer" И Соединение.ИмяПриложения <> "BackgroundJob" Тогда
Если Соединение.Пользователь.УникальныйИдентификатор = ТекущийПользовательИБ.УникальныйИдентификатор Тогда
ЛокальныйПорт = ЛокальныйПорт + Соединение.НомерСеанса;
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат ЛокальныйПорт;
КонецФункции
Функция ПолучитьСписокОткрытыхПортов() Экспорт
ОткрытыеПорты = Новый Массив;
ТекущийПользовательИБ = ПользователиИнформационнойБазы.ТекущийПользователь();
Для Каждого Соединение Из ПолучитьСоединенияИнформационнойБазы() Цикл
Если Соединение.ИмяПриложения <> "Designer" И Соединение.ИмяПриложения <> "BackgroundJob" Тогда
Если Соединение.Пользователь.УникальныйИдентификатор <> ТекущийПользовательИБ.УникальныйИдентификатор Тогда
ОткрытыеПорты.Добавить(1024 + Соединение.НомерСеанса);
КонецЕсли;
КонецЕсли;
КонецЦикла;
Возврат ОткрытыеПорты;
КонецФункции
Процедура ОповеститьВсех(Событие,Данные,Источник) Экспорт
Для Каждого Порт Из ПолучитьСписокОткрытыхПортов() Цикл
MyLib.NotifyPort(Порт,"MyLib.Port: " + MyLib.LocalPort,Событие,Данные);
КонецЦикла;
Оповестить(Событие,Данные,Источник); //На случай, если требуется оповещение самому себе
КонецПроцедуры
- В форме документа ЗаказПокупателя:
Ищем обработчик ПослеЗаписи и прописываем там код:
МодульВзаимодействия.ОповеститьВсех("OrderRecord",Строка(Ссылка.УникальныйИдентификатор()),ЭтаФорма);
Добавляем новую процедуру:
Процедура ОбработкаОповещенияВзаимодействия(Источник, Событие, Данные)
Если Событие = "OrderRecord" Тогда
ЭтотОбъект.Прочитать();
ЭтаФорма.Обновить();
ОбновлениеОтображения();
Предупреждение("Форма обновлена!");
КонецЕсли;
КонецПроцедуры
В обработчик ВнешнееСобытие вставляем код:
Если Лев(Источник,5) = "MyLib" Тогда
ОбработкаОповещенияВзаимодействия(Источник, Событие, Данные);
Возврат;
КонецЕсли;
В обработчик ОбработкаОповещения прописываем:
ОбработкаОповещенияВзаимодействия(Источник, ИмяСобытия, Параметр);
Вот такой простенький пример, на базе которого можно построить свою систему оповещения о каких-то событиях. В будущем планирую, если получится, на базе MyLib разработать систему асинхронных вызовов. Возможно, найдутся и другие сферы применения.
Исходный код MyLib также выкладываю. Возможно, кто-то захочет его использовать как базу для своей внешней компоненты.
На этом пока все.