1С технология внешних компонент

1С технология внешних компонент

Содержание

Вступление

За годы своего развития платформа 1С:Предприятия становилась все более открытой. Возможность работы с текстовыми файлами и табличными данными в dbf-формате в 90-е годы в 2000-е дополнилась доступностью многих современных средств интеграции. Здесь и веб-сервисы, и поддержка схем xml при обменах со сторонними системами, и возможность подключения к внешним источникам данных, и встроенный почтовый клиент. К сожалению, в развитии технологии внешних компонент наблюдается противоположный процесс. Это можно видеть, последовательно просмотрев диски ИТС с демонстрационными примерами от самой фирмы 1С. С выходом технологии разработчикам были доступны образцы создания компонент на C++ и Delphi, затем в список языков включили Visual basic 6, еще позже C#. Тогда 1С декларировала, что компоненту можно создать на любом компилируемом языке, поддерживающем COM. Ситуация резко изменилась после выхода версии 8.2 и создания технологии NativeAPI. Теперь программисты вынуждены довольствоваться ее демонстрацией только на C++. Причины такого сужения доступных средств лежат на поверхности, прежде всего это необходимость поддержки 64-битной архитектуры и кроссплатформенность.

Не будем уподобляться многим программистам (в том числе очень авторитетным), которые не переносят C++. Автор этой статьи придерживается более философского подхода, который можно сформулировать, как «каждому инструменту — свое место», но он не уверен, что технология внешних компонент является монопольным правом C++. Другое дело, что этот язык трудно вытеснить из-за жестких ограничений, накладываемых на формат компоненты 1С: необходимость создания машинных исполняемых файлов для каждой платформы (а не промежуточных образов как в Java или C#), возможность статической сборки компоненты в один, независящий от внешних библиотек и сред, файл. Кроме того, возросшие вкусы современных программистов выдвигают повышенные требования к инструменту замены: полноценная поддержка ООП, включая автоматическую сборку мусора, красота и элегантность; иными словами, необходимо оставить всю мощь, гибкость и эффективность C++, но убрать его недостатки и сложность. Казалось бы, требования невыполнимые и невозможно найти такой язык и платформу. Но автор смело заявляет: Есть такой язык!

История развития технологии внешних компонент

Давайте посмотрим, как менялся подход 1С к созданию внешних компонент с конца 90-х по настоящее время.

Кроме перечисленных плюсов и минусов COM-компонент, следует отметить, что для их создания программисту 1С достаточно незначительно расширить багаж своих знаний. Недостаток, указанный в третьем пункте думаю знаком даже программистам, не писавшим компоненты: стоит написать вместо ПодключитьВнешнююКомпоненту — ЗагрузитьВнешнююКомпоненту в надежде что 1С сама установит файл, и (привет системным администраторам!) потребуется регистрация компоненты в реестре Windows, которая невозможна без прав локального администратора. А если мы создаем компоненту на .NET, то нужно писать свой инсталлятор или пользоваться инструментами платформы .NET ( утилита regasm).

Технология внешних компонент на основе NativeAPI

Теперь посмотрим на внешние компоненты, разработанные на основе NativeAPI.

  • Эта технология удобна для 1С-разработчиков тем, что установка компоненты не требуется, 1С ее разворачивает и устанавливает сама.
  • А недостатком является сложность их создания. Фактически, сейчас 1С в качестве примера компонент использует только C++.
    Конечно, можно создавать внешние компоненты и на Delphi. Но нюанс в том, что Kylix (компилятор приложений, написанных на Delphi, для работы под Linux) начиная с 2002 года уже не поддерживается, поэтому, у таких компонент могут возникнуть проблемы с кроссплатформенностью.

Язык программирования Eiffel

Язык Eiffel был разработан Бертраном Мейером, одним из ведущих специалистов по объектно-ориентированному программированию (ООП) во второй половине 80-х годов, промышленный компилятор и среда разработки выпущены в середине 90-х, им же основанной фирмой Eiffel Software (тогда ISE). Уже в те годы Eiffel удовлетворял всем критериям объектной-ориентированного языка, которые указаны на слайде:

Сила языка Eiffel заключается в его бескомпромиссном рационализме. Никаких лишних понятий и концепций, не имеющих выражения в языке или математической модели, которой является теория абстрактных типов данных. По сути, абстрактный тип данных – это класс без его конкретной реализации. Абстрактный тип данных определяется набором аксиом, характеризующих его поведение. Аксиомы при реализации типа как класса преобразуются в сущности (features), которые делятся на запросы, команды и конструкторы. Запрос возвращает данные о состоянии экземпляра класса (объекта), команда – изменяет это состояние, конструктор создает объект класса.

Уже первые версии реализации языка и среды разработки (Eiffel Studio) значительно опережали «конкурентов» (Java и позже появившейся C#) в плане следования стандартам ООП. Eiffel служил и служит лабораторией, где впервые реализуются новые (и как правило успешные) возможности, которые со временем появляются в более распространенных языках и средах. Например, множественное наследование до сих пор недоступно программистам Java и C#, ковариантность шаблонов появилась в C# только в net framework 3.5 и в это же время в Java. Ковариантное согласование параметров (аргументов) невозможно в других языках. Например, для команды копирования в базовом классе в Eiffel можно написать:

copy (other: like Current)

В классе-потомке аргумент other будет согласованно изменять свой тип. Неотъемлемой чертой Eiffel является идея контрактного программирования, берущая начало в теории абстрактных типов данных. Контракты позволяют на порядок повысить качество программного продукта и облегчают отладку. Контракты – прямое выражение алгебраического подхода моделирования внешнего мира, присущего программным объектам. Аксиоматические сущности предельно четко выражаются через классы и контракты. (Контракты были введены в C# начиная с версии 4.5.)

Синтаксис класса в Eiffel

На скриншоте показано, как может выглядеть реализация класса натуральных чисел в аксиоматике Пеано.

  • В разделе invariant в конце класса описываются инварианты (аксиомы) класса.
  • Члены класса (features) не делятся как в традиционных языках на поля, свойства и методы. По их функциональности они могут быть разделены на запросы и команды. Запросы предоставляют информацию и состоянии класса, команды — модифицируют это состояние.

Примеры технологий, реализованных в Eiffel раньше других языков программирования

В Eiffel были реализованы многие революционные подходы, которые потом «перетекли» в другие промышленные языки. Например:

  • Ковариантность, которая в .NET появилась в 2000-х годах, а в Eiffel была изначально.
  • Контрактное программирование, для которого только после выхода .NET версии 4.5 появились аддон для VisualStudio и соответствующий namespace.
  • Многопоточное программирование на основе async/await, которые совсем недавно появились в .NET. Аналогичный по простоте механизм был введен в Eifel в серенине 2000-х.
  • Проблема обращение к нулевой ссылке, когда вызывается метод или свойство переменной, которая еще не инициализирована. В Eiffel изначально предлагается очень простое решение этой проблемы. (см. операторы detachable/attached языка).

Дополнительные возможности Eiffel, которые позволяют создавать полноценные внешние компоненты

Рассмотрим, как Eiffel удовлетворяет требованиям по сборке внешних компонент:

Первое требование реализуется средой разработки от Eiffel Software. Реализация же второго пункта во многих языках представляет сложную задачу, но не в Eiffel, который имеет несколько средств интеграции с кодом на C++. Они нам потребуются для создания этих интерфейсов, поэтому вкратце рассмотрим их.

Средства интеграции Eiffel c С++

В первом примере читатели статьи, пасавшие NativeAPI компоненты лего узнают выделение памяти для объектов, создаваемых в компоненте и передаваемых в 1С. Имена параметров из Eiffel предваряются в C++ знаком $. Пример демонстрирует интеграцию кода C++ в Eiffel. Второй пункт слайда — вызов процедур Eiffel из C++ реализуется с помощью библиотеки CECIL, которая включает функции и макросы преобразования типов и API для управления сборщиком мусора. Для использования CECIL скомпилированный проект на Eiffel studio собирается утилитой make (nmake) в статическую библиотеку (*.lib), которая затем подключается к проекту на C++. Именно этим способом собирается внешняя компонента, реализуемая на языке Eiffel.

Архитектура шаблона внешней компоненты для 1С, написанного на Eiffel

Рассмотрим строение компоненты на Eiffel с точки зрения модульной архитектуры:

EiffelStudio для диаграмм классов употребляет BON (business object notation) нотацию, которую будем использовать и мы. В ней класс отображается в виде овала, связи между классами в виде сплошной (родитель – потомок) или пунктирной стрелки (поставщик – клиент). В виде прямоугольника мы обозначили набор экспортируемых для dll функций. Как известно, их назначение – дать декларативное описание объекта (или объектов) компоненты и создать по запросу указанный объект. Чтобы не заставлять программиста, использующего этот шаблон, каждый раз модифицировать код на C++, требуется Eiffel класс COMPONENTNAMES, который возвращает имена объектов компоненты. Как и в EiffelStudio класс выделен более светлым фоном, потому что он заморожен, т.е. не допускает наследования.

Запуск движка Eiffel средствами CECIL рассмотрим на примере экспортной функции GetClassNames:

Для старта необходимо подготовить ряд параметров среды (командную стоку, глобальные переменный ОС) и вызвать функцию eif_rtinit(), что делается в процедуре EifEnvInit. Окончание работы должно завершаться reclaim() для освобождения памяти и принудительного вызова сборщика мусора либо завершение работы произойдет автоматически при завершении процесса или выгрузке DLL, что актуально для внешней компоненты. Код функции представляет собой пример вызова сущности класса Eiffel из C++. eif_create выделяет память для объекта, но не вызывает конструктор класса, поэтому за ее вызовом требуется вызов конструктора для инициализации свойств и соблюдения инварианта класса. В данном примере инициализация не требуется, поэтому вызов отсутствует. Объекты Eiffel в C++ могут иметь тип EIF_OBJECT или EIF_REFERENCE, а также для некоторых типов EIF_INTEGER, EIF_POINTER и др. Оба типа представляют собой указатели, различие между ними в том, что EIF_OBJECT является «зафиксированным» в памяти объектом, который защищен от манипуляций сборщика мусора (перемещение в памяти с целью оптимизации), а EIF_REFERENCE – нет. Понятно, что вызовы сущностей из C++ должны производится только от EIF_OBJECT, но по окончании работы с объектом необходимо обязательно перевести его в нефиксированный статус вызовом eif_wean. Аналогичная инициализация среды Eiffel выполняется функцией GetClassObject, возвращающей EiffelAddIn, который служит по сути классом-оберткой над объектом компоненты в Eiffel. Его методы вызывают соответствующие сущности класса ECOMPONENTBASE. Их структура однотипна, приведем для примера код метода CallAsProc:

Массив параметров tVariant преобразуется тип TUPLE. Кортеж TUPLE в Eiffel определяется как последовательность n элементов, которая может быть больше чем n. Они широко используются в агентах (указателях на функции), inline агентах (анонимных функциях) и лямбда выражениях. Класс EiffelAddIn содержит также служебные процедуры для преобразования строк и параметров, передаваемых из 1С в Eiffel и обратно. Типу tVariant соответствует тип V8_ARG, который будет рассмотрен позже.

Базовый класс ECOMPONENTBASE, реализация свойств и методов компоненты

Класс ECOMPONENTBASE является передаточным звеном между декларативными вызовами свойств и методов компоненты из 1С и реальным ее объектом в Eiffel. Таким образом он должен выполнять две функции: предоставлять сущности для вызовов из промежуточного слоя C++ (класс EiffelAddIn) и отражать вызовы на объект компоненты, который от него наследуется. Поскольку среда и компилятор языка Eiffel не использует промежуточные метаданные, вроде байт-кода или msil, он не имеет развернутых средств рефлексии. В какой-то степени класс ECOMPONENTBASE их заменяет. Посмотрим на его инициализацию и некоторые свойства:

Напомним, что свойства (запросы) класса, которые в других языках называются полями в Eiffel’е всегда доступны только для чтения. Запрос addinname должен быть обязательно переопределен в классе-потомке (deferred), это имя класса-компоненты в 1С, которое создается инструкцией Новый(«AddIn.name»). Команда make – вспомогательная и служит для вызова из конструктора наследника, например:

В секции объявления наследования (inherit) указывается, что сущность make базового класса переопределяется. Кроме инструкции redefine в этой секции могут встречаться ряд других (undefined, select, rename, export), служащих для управления видимостью и разрешения конфликтов при множественном и дублируемом наследовании. Eiffel позволяет сделать любую сущность – конструктором, в свою очередь конструкторы базового класса не являются таковыми у потомка. В секции create происходит объявление make конструктором, а внутри можно видеть вызов предшественника Precursor. Сущность make служит для сохранения контекста 1С и указателя на менеджер памяти в компоненте. Также они инициализируют описатели свойств и методов V8_PROP и V8_METHOD, код которых рассмотрим позднее. Описатели хранятся списком LINKED_LIST:

Инварианты класса очевидны и всегда подразумеваются программистом, пишущим класс, но только в Eiffel’е они имеют явное выражение.

Класс V8_ARG является представление типа Variant, а это означает, что он может содержать значение переменной языка 1С:Предприятия, которая передается в компоненту. На его примере рассмотрим неявную конвертацию типов в Eiffel:

Конструктор класса может быть одновременно конвертером, если он дополнительно объявлен в секции convert и в этой же секции объявлена функция обратной конверсии.

И в заключение, рассмотрим устройство механизма рефлексии для параметров и свойств компоненты.

Класс V8_FEATURE определяет общие свойства свойств и методов и алгоритм их поиска для методов FindMethods и FindProps:

Переопределив сущность is_equal базового класса ANY, мы получаем возможность поиска путем пообъектного сравнения в списках LINKED_LIST.

В Eiffel в отличие от платформы NET отсутствует рефлексия типов, поэтому описатели свойств и методов компоненты приходится писать в подобном стиле:

Пожалуй, это единственный минус в архитектуре компоненты.

В заключение раздела на примере сущности callasfunc познакомим читателей с синтаксисом попытки приведения типов в Eiffel:

В кортеже параметров (TUPLE) могут быть члены любого типа, поэтому такая проверка гарантирует правильную обработку типов и исключает вызов к нулевой ссылке (Void reference exception). Ее использование в данном контексте – требование языка, оно необходимо для правильной работы динамического связывания

Как известно, описание любого паттерна должно состоять из двух частей: указания по применению («как использовать») и описание самого шаблона. В предыдущих разделах мы постарались обратить внимание на ключевые места внутреннего строения компоненты, сейчас же нам осталось дать пошаговый алгоритм использования. Он очень простой:

Шаблон компоненты приложен к даной статье.

**************

Данная статья написана по итогам доклада, прочитанного на конференции INFOSTART EVENT 2015 CONNECTION 15-17 октября 2015 года.

Приглашаем вас на новую конференцию INFOSTART EVENT 2019 INCEPTION.

1. Что такое внешняя компонента. Подключение компоненты 1С

2. Установка компонент 1С. Подключение внешней компоненты.

Перед тем как подключить внешнюю компоненту 1С, давайте рассмотрим, что же она собой представляет в плане теории.

1. Что такое внешняя компонента. Подключение компоненты 1С

Ни для кого не секрет, что 1С является очень динамичной платформой для работы с основными технологиями, к которым мы можем получить доступ из конфигурации. Явным примером этого является Ole, COM, web сервисы, обмен данными через xml и т.д. Имея навыки программирования можно легко настроить подключение и/или обмен данными с любой cms системой в сети интернет либо той, которая имеет api функционал. Но иногда появляется необходимость в расширении функционала, которого мы не можем добиться, используя язык 1С. В этом плане на помощь нам приходят внешние компоненты. Говоря простым языком, внешние компоненты — это в 1С стандартные библиотеки с наборами функций, скомпилированные (собранные) для работы с внешней программой. Для работы с внешними компонентами нам в первую очередь нужно их подключить, это можно делать либо динамически (подключить в конкретном куске кода для выполнения некоторого функционала, нужно лишь в этот момент), либо зарегистрировать ее в системе.

Наиболее оптимально хранить компоненты как макеты в конфигурации. Это дает возможность не зависеть от конкретного «железа», а также программного обеспечения. Для такого хранения нам подойдет макет с типом «Двоичные данные»:

2. Установка компонент 1С. Подключение внешней компоненты.

Хранение компоненты мы организовали. Далее нам надо рассмотреть, как подключить компоненту. Если внешняя компонента используется впервые, перед подключением ее нужно установить. Пример установки и подключения компоненты приведен ниже:

Для вызова функций нашей компоненты нам нужно ее использовать как переменную. Пример вызова функции:

В данном примере мы рассмотрели, как подключить компоненту, и что собой представляет внешняя компонента 1С.

Разнообразие внешних компонент весьма большое. С их помощью мы можем работать с графикой, подключать разнообразное оборудование, производить потоковое сканирование. Исходя из этого заметно то, что возможности внешних компонент ограничиваются лишь их целесообразностью и фантазией специалиста, который их создает.

Стоит также отметить, что при использовании компоненты нужно учесть, в какой операционной системе она будет работать, а также какую разрядность ОС поддерживает.

Специалист компании ООО «Кодерлайн»

Сергей Кулаженко

Введение

Эта статья дает представление о работе внешних компонент в системе «1С: Предприятие».
Будет показан процесс разработки внешней компоненты для системы «1С: Предприятие» версии 8.2, работающей под управлением ОС семейства Windows с файловым вариантом работы. Такой вариант работы используется в большинстве решений, предназначенных для предприятий малого бизнеса. ВК будет реализована на языке программирования C++.

Внешние компоненты «1C: Предприятие»

«1С: Предприятие» является расширяемой системой. Для расширения функциональных возможностей системы используются внешние компоненты (ВК). С точки зрения разработчика ВК представляет собой некоторый внешний объект, который имеет свойства и методы, а также может генерировать события для обработки системой «1С: Предприятие».
Внешние компоненты можно использовать для решения класса задач, которые сложно или даже невозможно реализовать на встроенном в «1C: Предприятие» языке программирования. В частности, к такому классу можно отнести задачи, требующие низкоуровневого взаимодействия с операционной системой, например, для работы с специфичным оборудованием.
В системе «1С: Предприятие» используются две технологии создания внешних компонент:

  • с использованием Native API
  • с использованием технологии COM

При заданных ограничениях между двумя вышеозначенными технологиями разница незначительна, поэтому будем рассматривать разработку ВК с использованием Native API. При необходимости, реализованные наработки могут быть применены для разработки ВК с использованием технологии COM, а также, с незначительными доработками, применены для использования в системе «1С: Предприятие» с другими вариантами работы, отличными от файлового режима работы.

Структура ВК

Внешняя компонента системы «1С: Предприятие» представлена в виде DLL-библиотеки. В коде библиотеки описывается класс-наследник IComponentBase. В создаваемом классе должны быть определены методы, отвечающие за реализацию функций внешней компоненты. Более подробно переопределяемые методы будут описаны ниже по ходу изложения материала.

Запуск демонстрационной ВК

Задача:

  1. Выполнить сборку внешней компоненты, поставляемой с подпиской ИТС и предназначенной для демонстрации основных возможностей механизма внешних компонент в 1С
  2. Подключить демонстрационную компоненту к конфигурации 1С
  3. Убедиться в корректной работоспособности заявленных функций

Компиляция

Демонстрационная ВК расположена на диске подписки ИТС в каталоге «/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С не было сообщено об ошибке, то ВК была успешно подключена.
В результате выполнения приведенного кода в глобальной видимости конфигурации появляется объект ДемоКомп, имеющий свойства и методы, которые определены в коде внешней компоненты.

Демонстрация заложенного функционала

Проверим работоспособность демонстрационной ВК. Для этого попробуем установить и прочитать некоторые свойства, вызвать некоторые методы ВК, а также получить и обработать сообщение ВК.
В документации, поставляемой на диске ИТС заявлен следующий функционал демонстрационной ВК:

  1. Управление состоянием объекта компоненты
    Методы: Включить, Выключить
    Свойства: Включен
  2. Управлением таймером
    Каждую секунду компонента посылает сообщение системе «1C: Предприятие» с параметрами Component, Timer и строкой счетчика системных часов.
    Методы: СтартТаймер, СтопТаймер
    Свойства: ЕстьТаймер
  3. Метод ПоказатьВСтрокеСтатуса, который отображает в строке статуса текст, переданный методу в качестве параметров
  4. Метод ЗагрузитьКартинку. Загружает изображение из указанного файла и передает его в систему «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»);

Расширение списка свойств ВК

Задача:

  1. Изучить реализацию свойств ВК
  2. Добавить свойство строкового типа, доступное для чтения и записи
  3. Добавить свойство строкового типа, доступное для чтения и записи, которое хранит тип данных последнего установленного свойства. При установке значения свойства никаких действий не производится
  4. Убедиться в работоспособности произведенных изменений

Для определения свойств создаваемой компоненты разработчику необходимо реализовать следующие методы в коде библиотеки 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, в зависимости от переданного порядкового номера свойства в соответствии с логикой приложения.
Для того, чтобы добавить произвольное свойство необходимо:

  1. Добавить имя добавляемого свойства в массивы g_PropNames и g_PropNamesRu (файл AddInNative.cpp)
  2. В перечисление Props (файл AddInNative.h) перед ePropLast добавить имя, однозначно идентифицирующее добавляемое свойство
  3. Организовать память под хранение значений свойств (завести поля модуля компоненты, хранящие соответствующие значения)
  4. Внести изменения в методы GetPropVal и SetPropVal для взаимодействия с выделенной на предыдущем шаге памятью
  5. В соответствии с логикой приложения внести изменения в методы IsPropReadable и IsPropWritable

Пункты 1, 2, 5 не нуждаются в пояснении. С деталями реализации этих шагов можно ознакомиться, изучив приложение к статье.
Дадим названия тестовым свойствам Тест и ПроверкаТипа соответственно. Тогда в результате выполнения пункта 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. Расширить функционал внешней компоненты следующим функционалом:
  2. Изучить способы реализации методов внешней компоненты
  3. Добавить метод-функцию Функц1, которая в качестве параметра принимает две строки («Параметр1» и «Параметр2»). В качестве результата возвращается строка вида: «Проверка. Параметр1, Параметр2»
  4. Убедиться в работоспособности произведенных изменений

Для определения методов создаваемой компоненты разработчику необходимо реализовать следующие методы в коде библиотеки 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 содержат непосредственно исполняемый код метода.
Для добавления метода, который может вызываться только как функция необходимо произвести следующие изменения в исходном коде внешней компоненты:

  1. Добавить имя метода в массивы g_MethodNames и g_MethodNamesRu (файл AddInNative.cpp)
  2. Добавить осмысленный идентефикатор метода в перечисление Methods (файл AddInNative.h)
  3. Внести изменения в код функции GetNParams в соответствии с логикой программы
  4. При необходимости внести изменения в код метода GetParamDefValue, если требуется использовать значения по умолчанию параметров метода.
  5. Внести изменения в функцию HasRetVal
  6. Внести изменения в логику работы функций CallAsProc или CallAsFunc, поместив туда непосредственно исполняемый код метода

Приведем массивы g_MethodNames и g_MethodNamesRu, а также перечисление Methods к виду:
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; }
Внесем изменения в функцию CAddInNative::GetParamDefValue:
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»); пер = ДемоКомп.Тест(«Привет, «, «Мир!»); Сообщить(пер); КонецПроцедуры
После запуска конфигурации получим сообщение: «Привет, Мир!», что говорит о том, что метод отработал успешно.

Таймер

Задача:

  1. Изучить реализацию таймера в демонстрационной ВК
  2. Модифицировать метод «СтартТаймер», добавив возможность передавать в параметрах интервал срабатывания таймера (в миллисекундах)
  3. Убедиться в работоспособности произведенных изменений

В 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), предназначенную непосредственно для хранения данных
  • идентификатор типа данных

В общем случае работа с переменными типа tVariant происходит по следующему алгоритму:

  1. Определение типа данных, которые в данный момент хранятся в переменной
  2. Обращение к соответствующему полю смеси, для непосредственного доступа к данным

Использование типа tVariant значительно упрощает взаимодействие системы «1С: Предприятие» и внешней компоненты

Приложение

Каталог «examples» содержит примеры к статье
examples/1 — запуск демонстрационной компоненты
examples/2 — демонстрация расширения списка свойств
examples/3 — демонстрация расширения списка методов
Каждый каталог содержит проект VS 2008 и готовую конфигурацию 1C.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *