gifts2017

Создание внешних компонент 1С по технологии NativeAPI на языке Eiffel

Опубликовал Игорь Кисиль (IgorKissil) в раздел Программирование - Теория программирования

Статья знакомит с новым средством создания NativeAPI внешних компонент для 1С - языком Eiffel. Она будет интересна высококвалифицированным разработчикам 1С, которые имеют также опыт программирования на других языках. Eiffel - это высокоуровневый кроссплатформенный полностью объектно-ориентированный язык, обладающий красотой и элегантностью. Как инструмент создания внешних компонент он способен составить конкуренцию C++.

Вступление

За годы своего развития платформа 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С ее разворачивает и устанавливает сама.
  • А недостатком является сложность их создания. Фактически, сейчас в качестве примера компонент использует только 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[X1…Xn] в 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[G]:

Инварианты класса очевидны и всегда подразумеваются программистом, пишущим класс, но только в 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 2016 DEVELOPER.

Скачать файлы

Наименование Файл Версия Размер
Шаблон компоненты 12
.zip 33,45Kb
15.07.16
12
.zip 33,45Kb Скачать

См. также

Подписаться Добавить вознаграждение
Комментарии
1. Игорь <...> (I_G_O_R) 22.08.16 22:51
Ситуация резко изменилась после выхода версии 8.2 и создания технологии NativeAPI. Теперь программисты вынуждены довольствоваться ее демонстрацией только на C++.

Обычный com-объект, написанный на любом языке никто не отменял, эта технология всё еще работает и на клиенте и на сервере.

А кому надо NativeAPI писать на C#, тот использует технологию CLR Hosting API (между прочим, эту технологию юзает ms sql server, ms office и т.д.):
http://infostart.ru/public/300091/
если сильно хочется, c# (CoreCLR) можно и на линукс юзать:
http://infostart.ru/public/534901/

java можно по такому-же принципу использовать, используя JNI