Например, не получится переписать компоненту, если вы не её автор и исходников просто нет. Либо если для ее работы недостаточно поддерживаемых технологией Native API простейших типов (число, строка, булево, дата).
При работе с файловой базой особых проблем нет. Регламентное задание вызывается в фоновом процессе обычного пользователя. Поэтому ему доступны клиенские вызовы. В серверной базе при запуске регламентного задания клиентского контекста нет, соответственно вызов ПодключитьВнешнююКомпоненту() недоступен.
В этом случае можно вызывать компоненту на клиенте. Для этого достаточно выполнить из регламентного задания на сервере запуск еще одного сеанса 1С в котором на клиенте выполнить нужные действия. Ну и не забыть потом завершить запущенный сеанс.
Допустим, у нас в регламентном задании выполняется формирование и сохранение отчета, использующего для склонения ФИО внешнюю COM-компоненту NameDeclension.dll. На файловой базе такое регламентное задание будет работать корректно, на серверной компоненту подключить не получится.
Для устранения проблемы добавим в модуль регламентных заданий процедуру, которая в серверном режиме запустит еще один сеанс и в нем выполнит из внешней обработки вызов формирования отчета на клиенте.
#Если Клиент Тогда Процедура ВыполнитьФормированиеИСохранениеОтчета() Экспорт Если ПодключитьВнешнююКомпоненту("ОбщийМакет.NAMEDECL","Скл",ТипВнешнейКомпоненты.COM) Тогда Компонента = Новый ("AddIn.Скл.NameDeclension"); //Тут код формирования и сохранения отчета Иначе ЗаписьЖурналаРегистрации("РеглЗадания", УровеньЖурналаРегистрации.Ошибка, "Не удалось подключить внешнюю компоненту на клиенте"); КонецЕсли; КонецПроцедуры #Иначе Процедура ВыполнитьФормированиеИСохранениеОтчета() Экспорт ВыполнитьОперациюНаКлиенте("РеглЗадания.ВыполнитьФормированиеИСохранениеОтчета()"); КонецПроцедуры Процедура ВыполнитьОперациюНаКлиенте(ПараметрДляВыполнения) Экспорт ИмяПользователя = ""; ПарольПользователя = ""; ПутьКВнешнейОбработке = "c:/temp/Автозапуск.epf"; Кавычка = """"; КаталогBIN = КаталогПрограммы(); ПутьККонфигурации = СтрокаСоединенияИнформационнойБазы(); ПутьККонфигурации = СтрЗаменить(ПутьККонфигурации, Кавычка, Кавычка + Кавычка); СтрокаЗапуска = Кавычка + КаталогBIN + "1cv8.exe" + Кавычка + " ENTERPRISE" + " /IBConnectionString " + Кавычка + ПутьККонфигурации + Кавычка + " /N " + Кавычка + ИмяПользователя + Кавычка + " /P " + Кавычка + ПарольПользователя + Кавычка + " /Execute " + Кавычка + ПутьКВнешнейОбработке + Кавычка + " /C " + Кавычка + ПараметрДляВыполнения + Кавычка; ЗапуститьПриложение(СтрокаЗапуска); КонецПроцедуры #КонецЕсли
Код внешней обработки, которая просто вызовет в контексте клиента печать нужного отчета из модуля регламентных заданий и завершит сеанс после формирования отчета.
Попытка Выполнить(ПараметрЗапуска); Исключение КонецПопытки; ЗавершитьРаботуСистемы(Ложь);
Удобство решения в том, что при настройке регламентных заданий не важно, в каком режиме будет выполняться запуск задания. Если база файловая, то сразу запустится нужная процедура. Если база серверная и при запуске клиентский контекст отсутствует, то выполнится инициализация нового сеанса и в нем процедура корректно отработает в контексте клиента.
Код для обычного приложения. Теоретически полностью аналогично будет работать и в управляемом.
p.s. Также этот подход можно использовать для выполнения любых клиентских процедур в регламентных заданиях.
Вариант синтаксиса: По имени и местоположению
Синтаксис:
ПодключитьВнешнююКомпоненту(<Местоположение>, <Имя>, <Тип>)
Параметры:
<Местоположение> (обязательный)
Тип: Строка.
Местоположение внешнего компонента.
В качестве местоположения может использоваться:
путь к файлу внешнего компонента в файловой системе (недоступно на веб-клиенте), не ZIP-архив;
полное имя макета, хранящего двоичные данные или ZIP-архив;
URL к внешнему компоненту, в виде двоичных данных или ZIP-архива, в , аналогичном ПолучитьНавигационнуюСсылку.
<Имя> (обязательный)
Тип: Строка.
Символическое имя подключаемой внешнего компонента.
Имя должно удовлетворять правилам именования встроенного языка.
<Тип> (необязательный)
Тип: ТипВнешнейКомпоненты.
Тип подключаемого внешнего компонента.
Не используется, если компонент упакован в ZIP-архив.
Описание варианта метода:
Подключает компоненты, выполненные по технологии Native и COM.
Компонент может храниться в информационной базе или макете конфигурации в виде двоичных данных или в ZIP-архиве.
Для режимов запуска "Тонкий клиент" и "Веб-клиент", компонент должен быть предварительно установлен методом УстановитьВнешнююКомпоненту.
Вариант синтаксиса: По идентификатору
Синтаксис:
ПодключитьВнешнююКомпоненту(<ИдентификаторОбъекта>)
Параметры:
<ИдентификаторОбъекта> (обязательный)
Тип: Строка.
Идентификатор объекта внешнего компонента в виде ProgID (Programmatic Identifier) реестра MS Windows (например: "AddIn.Scanner").
Должно соответствовать информации, находящейся в регистрационной базе данных системы (Registry).
Описание варианта метода:
Компонент должен быть выполнен по технологии COM и зарегистрирован в реестре MS Windows.
Эти компоненты совместимы с компонентами 1С:Предприятия 7.7.
Внимание! Вариант метода не работает на сервере и во внешнем соединении.
Возвращаемое значение:
Тип: Булево.
Истина - подключение прошло успешно.
Описание:
Подключает внешний компонент к 1С:Предприятию.
Внешние компоненты могут храниться в информационной базе или макетах конфигурации в виде ZIP-архива или в виде двоичных данных, а также в файле файловой системы.
При работе на тонком клиенте и веб-клиенте компонент должен быть предварительно установлен.
Доступность:
Тонкий клиент, веб-клиент, сервер, внешнее соединение.
Примечание:
Внешние компоненты могут быть выполнены по технологии Native API или COM. Компоненты, выполненные по технологии COM, совместимы с компонентами 1С:Предприятия 7.7.
Веб-клиент может работать только с компонентами в информационной базе, упакованными в архив.
Тонкий клиент может работать с компонентами в информационной базе, упакованными в архив, и компонентами, расположенными в файловой системе.
Толстый клиент может работать со всеми вариантами хранения компонентов. При этом, если компонент установлен методом УстановитьВнешнююКомпоненту, то используется установленный компонент, а если не установлен, то компонент будет получен в момент подключения.
Сервер может работать со всеми компонентами. Компонент кэшируется на сеанс работы сервера.
Пример:
Если ПодключитьВнешнююКомпоненту("AddinObject.Scanner") Тогда
Сообщить("Компонента для сканера штрихкодов загружена");
Иначе
Сообщить("Компонента для сканера штрихкодов не загружена");
КонецЕсли;
Это связано с некоторыми особенностями работы функции глобального контекста ПодключитьВнешнююКомпоненту()
.
Зачастую у программистов возникают проблемы с подключением внешних компонент (например, драйверов торгового оборудования), когда пользователи работают с 1С, подключаясь к серверу через терминал.
При этом пользователи видят, например, такую картинку:
В то время как при работе с локальных компьютеров никаких проблем с подключением внешних компонент нет.
С чем это связано? Это связано с тем, что, когда пользователи работают через сервер терминалов, они имеют меньше прав, чем при работе на локальном компьютере.
В этом легко убедиться, если зайти на сервер терминалов под учетной записью с административными правами.
Причина такой разницы заключается в том, что 1С не может зарегистрировать внешнюю компоненту в реестре, когда пользователь работает в терминале под обычными правами, т.к. у обычного пользователя нет прав на запись в ветку системного реестра HKEY_CLASSES_ROOT .
В публикациях на тему подключения внешних компонент в терминале предлагаются самые разные методы решения этой проблемы.
Например, такие:
1. Запустить первый раз 1С под административными правами.
Этот вариант далеко не всегда срабатывает. Ниже объясню, почему.
2. Дать обычным пользователям терминала права на запись в ветку системного реестра HKEY_CLASSES_ROOT .
Недостаточно "продвинутым" пользователям лучше этого не делать, иначе могут быть проблемы.
3. С помощью различных "примочек" регистрировать ВК от имени пользователя с полными правами.
Тоже не есть хорошо.
Так как же все таки лучше выйти из этой ситуации?
Я предлагаю свой вариант решения этой проблемы. По моему мнению - простой и красивый.
Исследуя эту проблему, я задался вопросом - а зачем 1С вообще пытается зарегистрировать ВК по новому пути? Ведь она уже зарегистрирована в системе.
Дело оказалось в том, что в типовых конфигурациях 1С (например "Управление Торговлей") используется такой синтаксис метода глобального контекста ПодключитьВнешнююКомпоненту():
ПодключитьВнешнююКомпоненту("Справочник.ПодключаемоеОборудование.Макет.ДрайверАТОЛСканерШтрихкода", "АТОЛСканер");
Как видим, ВК драйвера подключается из макета "ДрайверАТОЛСканерШтрихкода" справочника "ПодключаемоеОборудование".
Что же при этом происходит?
1С сохраняет компоненту во временной папке пользователя, например "C:\Documents and Settings\User\Local Settings\Temp\1032\v8_4_12.tmp"
и пытается зарегистрировать ее в ветке реестра HKEY_CLASSES_ROOT именно по этому пути.
На терминале у обычных пользователей нет прав на изменение этой ветки реестра, поэтому компонента у них не подключается.
Теперь о том, как выйти из этой ситуации.
Метод глобального контекста ПодключитьВнешнююКомпоненту() имеет несколько вариантов синтаксиса. Вот этим мы и воспользуемся.
Итак, по шагам:
1. Регистрируем внешнюю компоненту утилитой regsvr32.exe на сервере терминалов в папке C:\WINDOWS\SYSTEM32 для 32-разрядной ОС или в папке C:\WINDOWS\SYSWOW64 для 64-разрядной ОС.
2. Используем один из двух дополнительных вариантов синтаксиса метода ПодключитьВнешнююКомпоненту():
Вариант 1:
ПодключитьВнешнююКомпоненту("C:\WINDOWS\SysWOW64\Scaner1C.dll", "АТОЛСканер", ТипВнешнейКомпоненты.COM);
ОбъектДрайвера = Новый ("AddIn.АТОЛСканер.Scaner45");
Вариант 2:
ProgID = "AddIn.Scaner45";
ПодключитьВнешнююКомпоненту(ProgID);
ОбъектДрайвера = Новый (ProgID);
На мой взгляд, вариант № 2 предпочтительнее.
При этом 1С не пытается перерегистрировать ВК по новому пути в реестре и таким образом, все проблемы решаются.
Ну вот собственно и все. Успехов в работе!
[необходимо зарегистрироваться для просмотра ссылки]
Зачастую у программистов возникают проблемы с подключением внешних компонент (например, драйверов торгового оборудования), когда пользователи работают с 1С, подключаясь к серверу через терминал.
При этом пользователи видят, например, картинку представленную в анонсе статьи.
В то время как при работе с локальных компьютеров никаких проблем с подключением внешних компонент нет.
С чем это связано? Это связано с тем, что, когда пользователи работают через сервер терминалов, они имеют меньше прав, чем при работе на локальном компьютере.
В этом легко убедиться, если зайти на сервер терминалов под учетной записью с административными правами.
Причина такой разницы заключается в том, что 1С не может зарегистрировать внешнюю компоненту в реестре, когда пользователь работает в терминале под обычными правами, т.к. у обычного пользователя нет прав на запись в ветку системного реестра HKEY_CLASSES_ROOT .
В публикациях на тему подключения внешних компонент в терминале предлагаются самые разные методы решения этой проблемы.
Например, такие:
1. Запустить первый раз 1С под административными правами.
Этот вариант далеко не всегда срабатывает. Ниже объясню, почему.
2. Дать обычным пользователям терминала права на запись в ветку системного реестра HKEY_CLASSES_ROOT .
Недостаточно "продвинутым" пользователям лучше этого не делать, иначе могут быть проблемы.
3. С помощью различных "примочек" регистрировать ВК от имени пользователя с полными правами.
Тоже не есть хорошо.
Так как же все таки лучше выйти из этой ситуации?
Я предлагаю свой вариант решения этой проблемы. По моему мнению - простой и красивый, не предлагавшийся на инфостарте ранее.
Исследуя эту проблему, я задался вопросом - а зачем 1С вообще пытается зарегистрировать ВК по новому пути? Ведь она уже зарегистрирована в системе.
Дело оказалось в том, что в типовых конфигурациях 1С (например "Управление Торговлей") используется такой синтаксис метода глобального контекста ПодключитьВнешнююКомпоненту() :
ПодключитьВнешнююКомпоненту("Справочник.ПодключаемоеОборудование.Макет.ДрайверАТОЛСканерШтрихкода", "АТОЛСканер");
Как видим, ВК драйвера подключается из макета "ДрайверАТОЛСканерШтрихкода" справочника "ПодключаемоеОборудование".
Что же при этом происходит?
1С сохраняет компоненту во временной папке пользователя, например "C:\Documents and Settings\User\Local Settings\Temp\1032\v8_4_12.tmp"
и пытается зарегистрировать ее в ветке реестра HKEY_CLASSES_ROOT именно по этому пути.
На терминале у обычных пользователей нет прав на изменение этой ветки реестра, поэтому компонента у них не подключается.
Теперь о том, как выйти из этой ситуации.
Метод глобального контекста ПодключитьВнешнююКомпоненту() имеет несколько вариантов синтаксиса. Вот этим мы и воспользуемся.
Итак, по шагам:
1. Регистрируем внешнюю компоненту утилитой regsvr32.exe на сервере терминалов в папке C:\WINDOWS\SYSTEM32 для 32-разрядной ОС или в папке C:\WINDOWS\SYSWOW64 для 64-разрядной ОС.
2. Используем один из двух дополнительных вариантов синтаксиса метода ПодключитьВнешнююКомпоненту():
Вариант 1:
ПодключитьВнешнююКомпоненту("C:\WINDOWS\SysWOW64\Scaner1C.dll", "АТОЛСканер", ТипВнешнейКомпоненты.COM);
ОбъектДрайвера = Новый ("AddIn.АТОЛСканер.Scaner45");
Вариант 2:
ProgID = "AddIn.Scaner45";
ПодключитьВнешнююКомпоненту(ProgID);
ОбъектДрайвера = Новый (ProgID);
На мой взгляд, вариант № 2 предпочтительнее.
При этом 1С не пытается перерегистрировать ВК по новому пути в реестре и таким образом, все проблемы решаются.
Ну вот собственно и все. Успехов в работе!
- Tutorial
Введение
Эта статья дает представление о работе внешних компонент в системе «1С: Предприятие».Будет показан процесс разработки внешней компоненты для системы «1С: Предприятие» версии 8.2, работающей под управлением ОС семейства Windows с файловым вариантом работы. Такой вариант работы используется в большинстве решений, предназначенных для предприятий малого бизнеса. ВК будет реализована на языке программирования C++.
Внешние компоненты «1C: Предприятие»
«1С: Предприятие» является расширяемой системой. Для расширения функциональных возможностей системы используются внешние компоненты (ВК). С точки зрения разработчика ВК представляет собой некоторый внешний объект, который имеет свойства и методы, а также может генерировать события для обработки системой «1С: Предприятие».Внешние компоненты можно использовать для решения класса задач, которые сложно или даже невозможно реализовать на встроенном в «1C: Предприятие» языке программирования. В частности, к такому классу можно отнести задачи, требующие низкоуровневого взаимодействия с операционной системой, например, для работы с специфичным оборудованием.
В системе «1С: Предприятие» используются две технологии создания внешних компонент:
- с использованием Native API
- с использованием технологии COM
Структура ВК
Внешняя компонента системы «1С: Предприятие» представлена в виде DLL-библиотеки. В коде библиотеки описывается класс-наследник IComponentBase. В создаваемом классе должны быть определены методы, отвечающие за реализацию функций внешней компоненты. Более подробно переопределяемые методы будут описаны ниже по ходу изложения материала.Запуск демонстрационной ВК
Задача:- Выполнить сборку внешней компоненты, поставляемой с подпиской ИТС и предназначенной для демонстрации основных возможностей механизма внешних компонент в 1С
- Подключить демонстрационную компоненту к конфигурации 1С
- Убедиться в корректной работоспособности заявленных функций
Компиляция
Демонстрационная ВК расположена на диске подписки ИТС в каталоге «/VNCOMP82/example/NativeAPI».Для сборки демонстрационной ВК будем использовать Microsoft Visual Studio 2008. Другие версии данного продукта не поддерживают используемый формат проекта Visual Studio.
Открываем проект AddInNative. В настройках проекта подключаем каталог с заголовочными файлами, необходимыми для сборки проекта. По умолчанию они располагаются на диске ИТС в каталоге /VNCOMP82/include .
Результатом сборки является файл /bind/AddInNative.dll . Это и есть скомпилированная библиотека для подключения к конфигурации 1С.
Подключение ВК к конфигурации 1С
Создадим пустую конфигурацию 1С.Ниже приведен код модуля управляемого приложения.
перем ДемоКомп; Процедура ПриНачалеРаботыСистемы() ПодключитьВнешнююКомпоненту("...\bind\AddInNative.dll", "DemoVK", ТипВнешнейКомпоненты.Native); ДемоКомп = Новый("AddIn.DemoVK.AddInNativeExtension"); КонецПроцедуры
Если при запуске конфигурации 1С не было сообщено об ошибке, то ВК была успешно подключена.
В результате выполнения приведенного кода в глобальной видимости конфигурации появляется объект ДемоКомп , имеющий свойства и методы, которые определены в коде внешней компоненты.
Демонстрация заложенного функционала
Проверим работоспособность демонстрационной ВК. Для этого попробуем установить и прочитать некоторые свойства, вызвать некоторые методы ВК, а также получить и обработать сообщение ВК.В документации, поставляемой на диске ИТС заявлен следующий функционал демонстрационной ВК:
- Управление состоянием объекта компоненты
Методы: Включить , Выключить
Свойства: Включен - Управлением таймером
Каждую секунду компонента посылает сообщение системе «1C: Предприятие» с параметрами Component , Timer и строкой счетчика системных часов.
Методы: СтартТаймер , СтопТаймер
Свойства: ЕстьТаймер - Метод ПоказатьВСтрокеСтатуса , который отображает в строке статуса текст, переданный методу в качестве параметров
- Метод ЗагрузитьКартинку . Загружает изображение из указанного файла и передает его в систему «1C: Предприятие» в виде двоичных данных.
перем ДемоКомп; Процедура ПриНачалеРаботыСистемы() ПодключитьВнешнююКомпоненту(...); ДемоКомп = Новый("AddIn.DemoVK.AddInNativeExtension"); ДемоКомп.Выключить(); Сообщить(ДемоКомп.Включен); ДемоКомп.Включить(); Сообщить(ДемоКомп.Включен); ДемоКомп.СтартТаймер(); КонецПроцедуры Процедура ОбработкаВнешнегоСобытия(Источник, Событие, Данные) Сообщить(Источник + " " + Событие + " " + Данные); КонецПроцедуры
Результат запуска конфигурации приведен на изображении
На панель «Сообщения» выведены результаты вызовов методов ДемоКомп.Выключить() и Демо.Комп.Включить() . Последующие строки на той же панели содержат результаты обработки полученных от ВК сообщений - Источник , Событие и Данные соответственно.
Произвольное имя внешней компоненты
Задача: Изменить имя внешней компоненты на произвольное.В предыдущем разделе использовался идентификатор AddInNativeExtension , смысл которого не был пояснен. В данном случае AddInNativeExtension - это наименование расширения.
В коде ВК определен метод RegisterExtensionAs , возвращающий системе «1С: Предприятие» имя, которое необходимо для последующей регистрации ВК в системе. Рекомендуется указывать идентификатор, который в известной мере раскрывает суть внешней компоненты.
Приведем полный код метода RegisterExtensionAs с измененным наименованием расширения:
bool CAddInNative::RegisterExtensionAs(WCHAR_T** wsExtensionName) { wchar_t *wsExtension = L"SomeName"; int iActualSize = ::wcslen(wsExtension) + 1; WCHAR_T* dest = 0; if (m_iMemory) { if(m_iMemory->AllocMemory((void**)wsExtensionName, iActualSize * sizeof(WCHAR_T))) ::convToShortWchar(wsExtensionName, wsExtension, iActualSize); return true; } return false; }
В приведенном примере имя ВК изменено на SomeName . Тогда при подключении ВК необходимо указывать новое имя:
ДемоКомп = Новый("AddIn.DemoVK.SomeName");
Расширение списка свойств ВК
Задача:- Изучить реализацию свойств ВК
- Добавить свойство строкового типа, доступное для чтения и записи
- Добавить свойство строкового типа, доступное для чтения и записи, которое хранит тип данных последнего установленного свойства. При установке значения свойства никаких действий не производится
Для определения свойств создаваемой компоненты разработчику необходимо реализовать следующие методы в коде библиотеки AddInNative.cpp:
GetNProps
Возвращает количество свойств данного расширения, 0 – при отсутствии свойств
FindProp
Возвращает порядковый номер свойства, имя которого передается в параметрах
GetPropName
Возвращает имя свойства по его порядковому номеру и по переданному идентификатору языка
GetPropVal
Возвращает значение свойства с указанным порядковым номером
SetPropVal
Устанавливает значение свойства с указанным порядковым номером
IsPropReadable
Возвращает флаг флаг возможности чтения свойства с указанным порядковым номером
IsPropWritable
Возвращает флаг флаг возможности записи свойства с указанным порядковым номером
Рассмотрим реализацию приведенных методов класса CAddInNative
.
В демонстрационной ВК определены 2 свойства: Включен
и ЕстьТаймер
(IsEnabled
и IsTimerPresent
).
В глобальной области видимости кода библиотеки определено два массива:
static wchar_t *g_PropNames = {L"IsEnabled", L"IsTimerPresent"};
static wchar_t *g_PropNamesRu = {L"Включен", L"ЕстьТаймер"};
которые хранят русское и английское названия свойств. В заголовочном файле AddInNative.h
определяется перечисление:
enum Props
{
ePropIsEnabled = 0,
ePropIsTimerPresent,
ePropLast // Always last
};
ePropIsEnabled
и ePropIsTimerPresent
, соответственно имеющие значения 0 и 1 используются для замены порядковых номеров свойств на осмысленные идентификаторы. ePropLast, имеющее значение 2, используется для получения количества свойств (методом GetNProps). Эти имена используются только внутри кода компоненты и недоступны извне.
Методы FindProp и GetPropName осужествляют поиск по массивам g_PropNames
и g_PropNamesRu
.
Для хранения значения полей в модуле библиотеки у класса CAddInNative определены свойства, которые хранят значение свойств компоненты. Методы GetPropVal
и SetPropVal
соответственно возвращают и устанавливают значение этих свойств.
Методы IsPropReadable
и IsPropWritable
и возвращают trure
или false
, в зависимости от переданного порядкового номера свойства в соответствии с логикой приложения.
Для того, чтобы добавить произвольное свойство необходимо:
- Добавить имя добавляемого свойства в массивы g_PropNames и g_PropNamesRu (файл AddInNative.cpp )
- В перечисление Props (файл AddInNative.h ) перед ePropLast добавить имя, однозначно идентифицирующее добавляемое свойство
- Организовать память под хранение значений свойств (завести поля модуля компоненты, хранящие соответствующие значения)
- Внести изменения в методы GetPropVal и SetPropVal для взаимодействия с выделенной на предыдущем шаге памятью
- В соответствии с логикой приложения внести изменения в методы IsPropReadable и IsPropWritable
Дадим названия тестовым свойствам Тест и ПроверкаТипа соответственно. Тогда в результате выполнения пункта 1 имеем:
static wchar_t *g_PropNames = {L"IsEnabled", L"IsTimerPresent", L"Test", L"TestType"}; static wchar_t *g_PropNamesRu = {L"Включен", L"ЕстьТаймер", L"Тест", L"ПроверкаТипа"};
Перечисление Props будет иметь вид:
enum Props { ePropIsEnabled = 0, ePropIsTimerPresent, ePropTest1, ePropTest2, ePropLast // Always last };
Для значительного упрощения кода будем использовать STL C++. В частности, для работы со строками WCHAR , подключим библиотеку wstring .
Для сохранения значения метода Тест , определим в классе CAddInNative в области видимости private поле:
string test1;
Для передачи строковых параметров между «1С: Предприятие» и внешней компонентов используется менеджер памяти «1С: Предприятие». Рассмотрим его работу подробнее. Для выделения и освобождения памяти соответственно используются функции AllocMemory и FreeMemory , определенные в файле ImemoryManager.h . При необходимости передать системе «1С: Предприятие» строковый параметр, внешняя компонента должна выделить под нее память вызовом функции AllocMemory . Ее прототип выглядит следующим образом:
virtual bool ADDIN_API AllocMemory (void** pMemory, unsigned long ulCountByte) = 0;
где pMemory - адрес указателя, в который будет помещен адрес выделенного участка памяти,
ulCountByte - размер выделяемого участка памяти.
Пример выделения памяти под строку:
WCHAR_T *t1 = NULL, *test = L"TEST_STRING"; int iActualSize = wcslen(test1)+1; m_iMemory->AllocMemory((void**)&t1, iActualSize * sizeof(WCHAR_T)); ::convToShortWchar(&t1, test1, iActualSize);
Для удобства работы с строковыми типами данными опишем функцию wstring_to_p . Она получает в качестве параметра wstring-строку. Результатом функции является заполненная структура tVariant . Код функции:
bool CAddInNative::wstring_to_p(std::wstring str, tVariant* val) { char* t1; TV_VT(val) = VTYPE_PWSTR; m_iMemory->AllocMemory((void**)&t1, (str.length()+1) * sizeof(WCHAR_T)); memcpy(t1, str.c_str(), (str.length()+1) * sizeof(WCHAR_T)); val -> pstrVal = t1; val -> strLen = str.length(); return true; }
Тогда соответствующая секция case оператора switch метода GetPropVal примет вид:
case ePropTest1: wstring_to_p(test1, pvarPropVal); break;
Метода SetPropVal :
case ePropTest1: if (TV_VT(varPropVal) != VTYPE_PWSTR) return false; test1 = std::wstring((wchar_t*)(varPropVal -> pstrVal)); break;
Для реализации второго свойства определим поле класса CaddInNative
uint8_t last_type;
в котором будем сохранять тип последнего переданного значения. Для этого в метод CaddInNative::SetPropVal добавим команду:
last_type = TV_VT(varPropVal);
Теперь при запросе чтения значения второго свойства будем возвращать значение last_type , чего требует обозначенное задание.
Проверим работоспособность произведенных изменений.
Для этого приведем внешний вид конфигурации 1С к виду:
перем ДемоКомп; Процедура ПриНачалеРаботыСистемы() ПодключитьВнешнююКомпоненту("...", "DemoVK", ТипВнешнейКомпоненты.Native); ДемоКомп = Новый("AddIn.DemoVK.SomeName"); ДемоКомп.ПроверкаТипа = 1; Сообщить(Строка(ДемоКомп.ПроверкаТипа)); ДемоКомп.Тест = "Вася"; Сообщить(Строка(ДемоКомп.Тест)); ДемоКомп.Тест = "Петя"; Сообщить(Строка(ДемоКомп.Тест)); Сообщить(Строка(ДемоКомп.ПроверкаТипа)); КонецПроцедуры
В результате запуска получим последовательность сообщений:
3
Вася
Петя
22
Второе и третье сообщения являются результатом чтения свойства, установленного на предыдущем шаге. Первое и второе сообщения содержат код типа последнего установленного свойства. 3 соответствует целочисленному значению, 22 - строковому. Соответствие типов и их кодов устанавливается в файле types.h , который находится на диске ИТС.
Расширение списка методов
Задача:- Расширить функционал внешней компоненты следующим функционалом:
- Изучить способы реализации методов внешней компоненты
- Добавить метод-функцию Функц1 , которая в качестве параметра принимает две строки («Параметр1» и «Параметр2»). В качестве результата возвращается строка вида: «Проверка. Параметр1, Параметр2»
- Убедиться в работоспособности произведенных изменений
Для определения методов создаваемой компоненты разработчику необходимо реализовать следующие методы в коде библиотеки AddInNative:
GetNMethods , FindMethod , GetMethodName
Предназначены для получения соответственно количества методов, поиска номера и имени метода. Аналогичны соответствующим методам для свойств
GetNParams
Возвращает количество параметров метода с указанным порядковым номером; если метод с таким номером отсутствует или не имеет параметров, возвращает 0
GetParamDefValue
Возвращает значение по умолчанию указанного параметра указанного метода
HasRetVal
Возвращает флаг наличия у метода с указанным порядковым номером возвращаемого значения: true для методов с возвращаемым значением и false в противном случае
CallAsProc
false , возникает ошибка времени выполнения и выполнение модуля 1С: Предприятия прекращается. Память для массива параметров выделяется и освобождается 1С: Предприятием.
CallAsFunc
Выполняет метод с указанным порядковым номером. Если метод возвращает false , возникает ошибка времени выполнения и выполнение модуля 1С: Предприятия прекращается. Память для массива параметров выделяется 1С: Предприятием. Если возвращаемое значение имеет тип строка или двоичные данные, компонента выделяет память функцией AllocMemory менеджера памяти, записывает туда данные и сохраняет этот адрес в соответствующем поле структуры. 1С: Предприятие освободит эту память вызовом FreeMemory .
Полное описание методов, включая список параметров подробно описан в документации, поставляемой на диске ИТС.
Рассмотрим реализацию описанных выше методов.
В в коде компоненты определены два массива:
static wchar_t *g_MethodNames = {L"Enable", L"Disable", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture"}; static wchar_t *g_MethodNamesRu = {L"Включить", L"Выключить", L"ПоказатьВСтрокеСтатуса", L"СтартТаймер", L"СтопТаймер", L"ЗагрузитьКартинку"};
и перечисление:
enum Methods { eMethEnable = 0, eMethDisable, eMethShowInStatusLine, eMethStartTimer, eMethStopTimer, eMethLoadPicture, eMethLast // Always last };
Они используются в функциях GetNMethods , FindMethod и GetMethodName , по аналогии с описанием свойств.
Методы GetNParams , GetParamDefValue , HasRetVal реализуют switch, в зависимости от переданных параметров и логики приложения возвращают требуемое значение. Метод HasRetVal в своем коде имеет список только методов, которые могут возвращать результат. Для них он возвращает true . Для всехо стальных методов возвращается false .
Методы CallAsProc и CallAsFunc содержат непосредственно исполняемый код метода.
Для добавления метода, который может вызываться только как функция необходимо произвести следующие изменения в исходном коде внешней компоненты:
- Добавить имя метода в массивы g_MethodNames и g_MethodNamesRu (файл AddInNative.cpp )
- Добавить осмысленный идентефикатор метода в перечисление Methods (файл AddInNative.h )
- Внести изменения в код функции GetNParams в соответствии с логикой программы
- При необходимости внести изменения в код метода GetParamDefValue , если требуется использовать значения по умолчанию параметров метода.
- Внести изменения в функцию HasRetVal
- Внести изменения в логику работы функций CallAsProc или CallAsFunc , поместив туда непосредственно исполняемый код метода
static wchar_t *g_MethodNames = {L"Enable", L"Disable", L"ShowInStatusLine", L"StartTimer", L"StopTimer", L"LoadPicture", L"Test"}; static wchar_t *g_MethodNamesRu = {L"Включить", L"Выключить", L"ПоказатьВСтрокеСтатуса", L"СтартТаймер", L"СтопТаймер", L"ЗагрузитьКартинку", L"Тест"};
Enum Methods
{
eMethEnable = 0,
eMethDisable,
eMethShowInStatusLine,
eMethStartTimer,
eMethStopTimer,
eMethLoadPicture,
eMethTest,
eMethLast // Always last
};
Отредактируем функцию GetNProps
, чтобы она возвращала количество параметров метода «Тест»:
long CAddInNative::GetNParams(const long lMethodNum)
{
switch(lMethodNum)
{
case eMethShowInStatusLine:
return 1;
case eMethLoadPicture:
return 1;
case eMethTest:
return 2;
default:
return 0;
}
return 0;
}
Внесем изменения в функцию :
bool CAddInNative::GetParamDefValue(const long lMethodNum, const long lParamNum, tVariant *pvarParamDefValue)
{
TV_VT(pvarParamDefValue)= VTYPE_EMPTY;
switch(lMethodNum)
{
case eMethEnable:
case eMethDisable:
case eMethShowInStatusLine:
case eMethStartTimer:
case eMethStopTimer:
case eMethTest:
// There are no parameter values by default
break;
default:
return false;
}
return false;
}
Благодаря добавленной строке
case eMethTest:
в случае отсутствия одного или нескольких аргументов соответствующие параметры будут иметь пустое значение (VTYPE_EMPTY
). Если необходимо наличие значения по умолчанию для параметра, следует задать его в секции eMethTest
оператора switch функции CAddInNative::GetParamDefValue
.
Так как метод «Тест» может возвращать значение, необходимо внести изменения в код функции HasRetVal
:
bool CAddInNative::HasRetVal(const long lMethodNum)
{
switch(lMethodNum)
{
case eMethLoadPicture:
case eMethTest:
return true;
default:
return false;
}
return false;
}
И добавим исполняемый код метода в функцию CallAsFunc
:
bool CAddInNative::CallAsFunc(const long lMethodNum,
tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray)
{
...
std::wstring s1, s2;
switch(lMethodNum)
{
case eMethLoadPicture:
...
break;
case eMethTest:
if (!lSizeArray || !paParams)
return false;
s1 = (paParams) -> pwstrVal;
s2 = (paParams+1) -> pwstrVal;
wstring_to_p(std::wstring(s1+s2), pvarRetValue);
ret = true;
break;
}
return ret;
}
Скомпилируем компоненту и приведем код конфигурации к виду:
перем ДемоКомп;
Процедура ПриНачалеРаботыСистемы()
ПодключитьВнешнююКомпоненту("...", "DemoVK", ТипВнешнейКомпоненты.Native);
ДемоКомп = Новый("AddIn.DemoVK.SomeName");
пер = ДемоКомп.Тест("Привет, ", "Мир!");
Сообщить(пер);
КонецПроцедуры
После запуска конфигурации получим сообщение: «Привет, Мир!», что говорит о том, что метод отработал успешно.
Таймер
Задача:- Изучить реализацию таймера в демонстрационной ВК
- Модифицировать метод «СтартТаймер», добавив возможность передавать в параметрах интервал срабатывания таймера (в миллисекундах)
- Убедиться в работоспособности произведенных изменений
В WinAPI для работы со временем можно воспользоваться сообщением WM_TIMER . Данное сообщение будет посылаться вашей программе через интервал времени, который вы зададите при создании таймера.
Для создания таймера используется функция SetTimer :
UINT SetTimer(HWND hWnd, // описатель окна UINT nIDevent, // идентификатор (номер) таймера UINT nElapse, // задержка TIMERPROC lpTimerFunc); // указатель на функцию
Операционная система будет посылать сообщение WM_TIMER в программу с интервалом указанным в аргументе nElapse (в миллисекундах). В последнем параметре можно указать функцию, которая будет выполняться при каждом срабатывании таймера. Заголовок этой функции должен выглядеть так (имя может быть любым):
void __stdcall TimerProc (HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
Рассмотрим реализацию таймера в демонстрационной ВК.
Так как мы рассматриваем процесс разработки внешней компоненты для ОС семейства Windows, не будем рассматривать реализацию таймера в других операционных системах. Для ОС GNU/Linux, в частности, реализация будет отличаться синтаксисом функции SetTimer и TimerProc .
В исполняемом коде вызывается метод SetTimer , в который передается функция MyTimerProc :
m_uiTimer = ::SetTimer(NULL,0,100,(TIMERPROC)MyTimerProc);
Идентефикатор созданного таймера помещается в переменную m_uiTimer , чтобы в последствии его можно было отключить.
Функция MyTimerProc выглядит следующим образом:
VOID CALLBACK MyTimerProc(HWND hwnd, // handle of window for timer messages UINT uMsg, // WM_TIMER message UINT idEvent, // timer identifier DWORD dwTime // current system time) { if (!pAsyncEvent) return; wchar_t *who = L"ComponentNative", *what = L"Timer"; wchar_t *wstime = new wchar_t; if (wstime) { wmemset(wstime, 0, TIME_LEN); ::_ultow(dwTime, wstime, 10); pAsyncEvent->ExternalEvent(who, what, wstime); delete wstime; } }
Суть функции сводится к тому, что вызывается метод ExternalEvent , который посылает сообщение системе «1С: Предприятие».
Для расширения функционала метода СтартТаймер произведем следующие действия:
Модифицируем код метода GetNParams так, чтобы он для метода eMethStartTimer возвращал значение 1:
case eMethStartTimer: return 1;
Приведем код метода CallAsProc к виду:
case eMethStartTimer: if (!lSizeArray || TV_VT(paParams) != VTYPE_I4 || TV_I4(paParams) <= 0) return false; pAsyncEvent = m_iConnect; #ifndef __linux__ m_uiTimer = ::SetTimer(NULL,0,TV_I4(paParams),(TIMERPROC)MyTimerProc); #else // код для GNU/Linux #endif break;
Теперь проверим работоспособность. Для этого в модуле управляемого приложения конфигурации напишем код:
перем ДемоКомп; Процедура ПриНачалеРаботыСистемы() ПодключитьВнешнююКомпоненту("...", "DemoVK", ТипВнешнейКомпоненты.Native); ДемоКомп = Новый("AddIn.DemoVK.SomeName"); ДемоКомп.СтартТаймер(2000); КонецПроцедуры
После запуска конфигурации в программу будут поступать сообщения с интервалом в 2 секунды, что говорит о корректной работе таймера.
Взаимодействие с системой «1С: Предприятие»
Для взаимодействия между внешней компонентой и системой «1С: Предприятие» используются методы класса IAddInDefBase, описанного в файле AddInDefBase.h . Перечислим наиболее часто используемые:Генерация сообщения об ошибке
virtual bool ADDIN_API AddError(unsigned short wcode, const WCHAR_T* source, const WCHAR_T* descr, long scode)
wcode , scode - коды ошибки (список кодов ошибок с описанием можно найти на диске ИТС)
source - источник ошибки
descr - описание ошибки
Отправка сообщения системе «1С: Предприятие»
virtual bool ADDIN_API ExternalEvent(WCHAR_T* wszSource, WCHAR_T* wszMessage, WCHAR_T* wszData) = 0;
wszSource - источник сообщения
wszMessage - текст сообщения
wszData - передаваемые данные
Перехват сообщения осуществляется процедурой ОбработкаВнешнегоСобытия
Регистрация внешней компоненты в системе «1С: Предприятие»
virtual bool ADDIN_API RegisterProfileAs(WCHAR_T* wszProfileName)
wszProfileName - имя компоненты.
Этих методов достаточно для полноценного взаимодействия ВК и 1С. Для получения данных внешней компонентой от системы «1С: Предприятие» и наоборот внешняя компонента отправляет специальное сообщение, которое в свою очередь перехватывается системой «1С» и, при необходимости вызывает методы внешней компоненты для обратной передачи данных.
Тип данных tVariant
При обмене данными между внешней компонентой и системой «1С: Предприятие» используется тип данных tVariant. Он описан в файле types.h, который можно найти на диске с ИТС:struct _tVariant { _ANONYMOUS_UNION union { int8_t i8Val; int16_t shortVal; int32_t lVal; int intVal; unsigned int uintVal; int64_t llVal; uint8_t ui8Val; uint16_t ushortVal; uint32_t ulVal; uint64_t ullVal; int32_t errCode; long hRes; float fltVal; double dblVal; bool bVal; char chVal; wchar_t wchVal; DATE date; IID IDVal; struct _tVariant *pvarVal; struct tm tmVal; _ANONYMOUS_STRUCT struct { void* pInterfaceVal; IID InterfaceID; } __VARIANT_NAME_2/*iface*/; _ANONYMOUS_STRUCT struct { char* pstrVal; uint32_t strLen; //count of bytes } __VARIANT_NAME_3/*str*/; _ANONYMOUS_STRUCT struct { WCHAR_T* pwstrVal; uint32_t wstrLen; //count of symbol } __VARIANT_NAME_4/*wstr*/; } __VARIANT_NAME_1; uint32_t cbElements; //Dimension for an one-dimensional array in pvarVal TYPEVAR vt; };
Тип tVariant представляет из себя структру, которая включает себя:
- смесь (union), предназначенную непосредственно для хранения данных
- идентификатор типа данных
- Определение типа данных, которые в данный момент хранятся в переменной
- Обращение к соответствующему полю смеси, для непосредственного доступа к данным
Приложение
Каталог «examples» содержит примеры к статьеexamples/1 - запуск демонстрационной компоненты
examples/2 - демонстрация расширения списка свойств
examples/3 - демонстрация расширения списка методов
Каждый каталог содержит проект VS 2008 и готовую конфигурацию 1C.