Технология внешних компонентов (). Технология внешних компонентов () Написание native компоненты 1с на c




Эта статья посвящена работе с внешними компонентами, а именно: их подключению. На данный момент для расширения возможностей 1С Предприятия используются две технологии внешних компонент:

  • 1 С использованием Native API
  • 2 С использованием технологии COM
В этой статье я решил осветить работу с компонентами Native API.
Итак, приступим, от простого к сложному:
Выдержка из ИТС

1. Допустим ВК у нас расположена в определенном каталоге на диске:

Возможно использование в "Толстом Клиенте (обычное приложение)";

Это самый простой пример работы с компонентой Native. Следует обратить внимание на то, что компонент этого типа не требует регистрацию в системе, что значительно упрощает администрирование.

2. Рассмотренный выше пример совсем не жизненный. Чаще всего компонент располагают в макете. Макет должен содержать zip архив c файлами компонента и файлом MANIFEST.xml
Пример файла манифеста:

3. При работе в тонком и web клиенте обязательно использования метода .
Цитата с ИТС:

пояснение:
%APPDATA%\1C\1Cv82\ExtCompT - каталог установки компонентов для Толстого, Тонкого клиентов.
%APPDATA%\Roaming\Mozilla\Extensions - каталог (в моем случае) расширений для Mozilla FF/
При использовании метода УстановитьВнешнююКомпоненту() , в зависимости от используемого клиента, в соответствующий каталог будут распакованы расширения.

Пример процедуры установки внешнего компонента:

УстановитьВнешнююКомпоненту - метод должен вызываться лишь при первичной установке компонента и в случае, когда необходимо обновить установленную версию компонента.

В случае тонкого и толстого клиента:
Достаточно повторно выполнить операцию установки внешней компоненты с помощью метода УстановитьВнешнююКомпоненту ().

В случае web клиента для обновления компонента:

  • Необходимо удалить плагин через механизм работы с дополнениями веб-браузера (Mozilla FF)
  • Воспользоваться методом УстановитьВнешнююКомпоненту
Для подключения ВК можно использовать следующую процедуру:

Если компонент не был установлен, то будет вызвано исключение.

2. бывают случаи, когда компонент необходимо установить из временного хранилища (файл получен со стороннего источника, внешняя обработка), в этом случае необходимо первым параметров в методы ПодключитьВнешнююКомпоненту и УстановитьВнешнююКомпоненту адрес архива во временном хранилище. Ниже приведу возможный пример работы:

&НаКлиенте Перем АдресАрхиваКомпонента; &НаКлиенте Перем Компонент; &НаКлиенте Процедура ПриОткрытии(Отказ) // адрес, содержит строку (навигационную ссылку на двоичные данные zip архива во // временном хранилище) АдресАрхиваКомпонента = ПолучитьАдресАрхиваВоВременномХранилище(); КонецПроцедуры // ПриОткрытии() &НаСервере // методы ПодключитьВнешнююКомпоненту,УстановитьВнешнююКомпоненту, могут принимать в // качестве первого параметра строку в формате "навигационная ссылка" // (URL к внешней компоненте, упакованной в ZIP-архив, в формате, аналогичном // ПолучитьНавигационнуюСсылку). Функция ПолучитьАдресАрхиваВоВременномХранилище() ОбъектОбработки = РеквизитФормыВЗначение("ОбработкаОбъект"); СсылкаНаАрхив = ПоместитьВоВременноеХранилище(ОбъектОбработки.ПолучитьМакет("MIKO_phone_IP"), Новый УникальныйИдентификатор); Возврат СсылкаНаАрхив; КонецФункции // ПолучитьАдресАрхиваВоВременномХранилище() &НаКлиенте // Процедура должна вызываться лишь один раз, в случае, если компонент еще не установлен // или нуждается в обновлении Процедура УстановитьКомпонент(Команда) Попытка УстановитьВнешнююКомпоненту(АдресАрхиваКомпонента); Исключение Сообщить("Не удалось устанвить внешнюю компоненту."); КонецПопытки; КонецПроцедуры // УстановитьКомпонент() &НаКлиенте // основная процедура инициализации комопнента Процедура Инициализаровать(Команда) Попытка ПодключитьВнешнююКомпоненту(АдресАрхиваКомпонента,"Comp" ,ТипВнешнейКомпоненты.Native); Компонент = Новый ("AddIn.Comp.MIKO_phone_IP"); Исключение Сообщить("Исключение при инициализации. Возможно компонент еще не был установлен."); КонецПопытки; КонецПроцедуры
  • Tutorial

Введение

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

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

В заголовок статьи вынесена фраза "для чайников". Под чайником я имел в виду в первую очередь себя. Все мои знания С++ остались на уровне 3-4 курса ВУЗа, когда я встал на кривую дорожку 1С. И все бы хорошо, но недавно встала задача, требующая написания внешней компоненты. Пришлось поворошить воспоминания и стряхнуть пыль со знаний C++. Оказывается, все не так страшно. Краткий ликбез написания внешних компонент я и хочу вам предложить.

Шаблон компоненты на ИТС

На диске ИТС имеется полная документация по механизму внешних компонент, дополненная примером проекта и шаблоном для собственной разработки. Материал так и называется "Технология внешних компонент". Документация - это прекрасно, но в ней еще надо разобраться, а времени, как обычно, мало. На самом деле, существует всего несколько ключевых моментов, на которые стоит обратить внимание, остальное - тлен и суета:)

Необходимые материалы

Для создания внешней компоненты нам понадобятся:

  1. Материал «Технология создания внешних компонент», расположенный на ИТС
  2. Шаблон пустой внешней компоненты, прилагающийся к материалу
  3. MS Visual Studio. Версия Express бесплатна и более чем достаточна для наших нужд.
  4. Наличие базовых знаний синтаксиса C++, а именно:
  • Умение отличить объявление переменной от цикла или условия
  • Понимание того, что строк в чистом виде в C++ не существует, есть массивы, под которые явно требуется заморачиваться с памятью
  • Ну и само собой, требуется умение реализовать поставленную задачу на указанном языке. Как минимум, умение вызвать из C++ какую-то стороннюю библиотечку, которая сама все сделает.

Начинаем копать

Документация на Native API достаточно подробна. Если подвести резюме, то она говорит о следующем:

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

Мы не будем залезать в особенности реализации, у нас сроки горят, да и компетенции маловато. Нам нужно быстро понять - в какое место нужно вписать свои строчки, чтобы компонента заработала. Для этого, берем шаблон компоненты с ИТС и открываем его в Visual Studio. Шаблон находится в папке template распакованного архива. Посмотрим, что у нас тут есть.

Нас интересует файл AddInNative.cpp. Вся реализация заложена в нем. Он содержит заготовки всех нужных методов, нужно только их слегка настроить. Однако оказалось, что проще взять за основу не пустой шаблон, а разобраться с рабочим примером. В нем есть несколько полезных примочек, которых нет в пустом шаблоне. Когда придет понимание - нужно будет взять пустой шаблон и уже со знанием дела его доработать. Пример рабочей компоненты расположен в папке example\NativeAPI, а пустой шаблон - в папке template.

Откроем проект из папки example и в нем - файл AddInNative.cpp

В самом начале файла расположены объявления констант и вспомогательных функций. Нас интересуют следующие строчки:

Наш объект, как «настоящий» будет поддерживать методы, написанные, как на русском, так и на английском языке. Для этого объявлены написания имен свойств и методов на двух языках. Синяя рамка - английские термы, красная, соответственно - русские. На картинке видно, что в примере уже реализован ряд методов и свойств. Наша задача - их убрать и вставить свои.

Зеленой рамкой выделена строка, в которой объявлено имя класса. Честно признаюсь, я не вникал, что оно означает. Если его поменять, то ничего не работает. Поскольку изначально оговорились, что я «чайник», то мне простительно. :)

Таким образом, если наш объект будет содержать метод «ВыполнитьРасчет» и свойство «Адресат», то нам нужно описать это имя в массиве g_MethodNamesRu и g_PropNamesRu, соответственно.

Вызовы из языка 1С

Итак, наш объект будет содержать один метод и свойство, доступное для чтения и записи.

Пусть будет следующий сценарий использования:

НашОбъект = Новый(“AddIn. MyComponent. DataSender”); НашОбъект. Адресат = «somemail@server. com»;
НашОбъект. ВыполнитьРасчет(СуммаПлатежа, «За коммунальные услуги»);

Имеется строковое свойство и метод с числовым и строковым параметром. Для того, чтобы все это заработало 1С выполняет примерно следующий протокол общения с компонентой:

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

Вернемся к нашему коду. Во избежание «волшебных чисел» в классе CAddInNative объявлены два перечисления, отвечающие за определение номеров методов и свойств. Откроем файл CAddInNative.h и увидим их в самом начале:

В пустом шаблоне этих перечислений нет, а также нет примера для отделения вызовов на русском от вызова на нерусском. Этот подход необязателен. Важно соблюдать интерфейс, а будут перечисления или нет - решать вам.

Строки Unicode

Многие, наверное, знают, что платформа оперирует двухбайтовыми символами в формате Unicode. В шаблоне для этого объявлен специальный тип WCHAR_T. Этот тип является кросс-платформенной оберткой и обеспечивает одинаковый размер символа на Windows и на Linux. Стандартный тип wchar_t по размеру может отличаться на разных системах. Обратите также внимание, все строковые литералы объявляются с префиксом в виде буквы L. Это означает, что такая строка имеет тип wchar_t.

Есть простое правило: внутри компоненты строки обрабатываются как wchar_t (на Linux может быть 4 байта, в Windows - 2), но как только мы передаем строку в 1С или принимаем ее оттуда, то нужен WCHAR_T (строго 2 байта на всех системах).

Для преобразования одного типа строк в другие в шаблоне предусмотрены вспомогательные функции:

Первая - формирует WCHAR_T из стандартного wchar_t:

uint32_t convToShortWchar(WCHAR_T** Dest, const wchar_t* Source, uint32_t len = 0);

Вторая - наоборот. Формирует wchar_t из WCHAR_T.

uint32_t convFromShortWchar(wchar_t** Dest, const WCHAR_T* Source, uint32_t len = 0);

При взаимодействии с платформой всегда используется только WCHAR_T.

Тип Variant

Еще одна интересная вещь - это универсальный тип данных Variant. Он позволяет нам взаимодействовать с языком 1С, который, как известно, не типизирован и каждая переменная в нем может содержать что угодно. При обмене значениями используется именно этот тип. Мы передаем в метод ВыполнитьРасчет два параметра - число и строку. В компоненту «приедут» два значения Variant. На нас возлагается обязанность проверить их действительный тип. Никто не помешает передать в компоненту не число, а скажем, таблицу значений.

Хотя, я похоже, ошибаюсь. Мне кажется, что ТаблицуЗначений в NativeAPI передать все-таки не получится, т.к. ее нет в списке допустимых типов, но, тем не менее, можно передать Дату вместо Cтроки. Это тоже не есть хорошо. Мы должны проверить реальный тип переменной, приехавшей из 1С.

Тип Variant устроен несложно. Это структура, свойствами которой являются значения разных типов. Там есть свойства типа DATE, wchar_t, int и прочие. Главной частью Variant является свойство «vt» которое хранит настоящий тип переменной, и по которой можно понять, как именно трактовать данный Variant. Кроме того, объявлен ряд вспомогательных макросов, упрощающих работу с типом Variant.

Ближе к делу

Вроде бы, со вступлением всё. Предлагаю рассмотреть пример реализации внешней компоненты. В качестве ТЗ выступит пример компоненты с диска ИТС. Этот пример описывает следующие возможности:

  • Вывод текста в строку состояния главного окна;
  • Посылка внешнего события по таймеру;
  • Передача двоичных данных в 1С:Предприятие;
  • Реализация свойств;
  • Реализация процедур;
  • Реализация функций;

Компонента имеет следующий API:

  • Свойства:
    • Включен/IsEnabled;
    • ЕстьТаймер/IsTimerPresent;
    • Методы:
      • Включить/Enable;
      • Выключить/Disable;
      • ПоказатьВСтрокеСтатуса/ShowInStatusLine;
      • ВключитьТаймер/StartTimer;
      • ВыключитьТаймер/StopTimer;
      • ЗагрузитьКартинку/LoadPicture;

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

Руководствуясь имеющимися у нас знаниями, рассмотрим компоненту с самого начала.

Регистрация компоненты

Наш объект реализуется в виде отдельного класса C++, в данном случае - CAddInNative. Чтобы 1С смогла увидеть наш класс, библиотека dll должна экспортировать 3 функции:

  • GetClassObject
  • DestroyObject
  • GetClassNames

Эти экспорты можно увидеть в файле AddInNative.def в дереве проекта VisualStudio. Посмотрим на код этих функций:

Самая простая - функция GetClassNames - сообщает платформе 1С какие классы есть в нашей компоненте. Пусть гуру C++ меня поправят, мне кажется, что здесь нужно ответить платформе именами классов C++, чтобы она могла их к себе импортировать. Именно для этого служит массив g_kClassNames, тот самый, с зеленой «рамочкой». Специально не проверял, если нужно просто заставить компоненту работать, то следует оставить все, как есть в примере. Он и так рабочий, не стоит его ковырять до поры до времени.

Итак, GetClassNames, возвращает в платформу массив имен классов, реализующих полезные объекты внешней компоненты. В нашем примере компонента вернет в платформу массив из одного элемента с именем класса CAddInNative.

Обратите внимание, в платформу пойдет значение типа WCHAR_T, а имя класса в массиве g_kClassNames имеет тип wchar_t. Поэтому, выполняется приведение с помощью вспомогательной функции, о которой говорилось выше.

Следующая функция - GetClassObject. Вызывается, когда в коде предприятия мы написали «Новый». Платформа требует от нас создать новый экземпляр класса и вернуть ей указатель на новый объект.

Опять же, обратите внимание, первым параметром платформа говорит нам - какой именно класс создать (из тех, что дали ей методом GetClassNames). Поскольку у нас только один класс, то это имя здесь вообще не проверяется, просто создается объект через new и возвращается через выходной параметр pInterface.

И последняя обязательная экспортная функция - DestroyObject. Название говорит само за себя. Когда объект платформе больше не нужен, его требуется удалить. Нам передается указатель на ранее созданный объект. Освобождаем его с помощью delete и обнуляем ненужные указатели.

Описанные реализации достаточно универсальны. Если наша компонента реализует только один класс (как в примере), то эти функции нужно тупо скопировать к себе. Единственное условие - создать правильный класс в функции GetClassObject, если у вас он называется не CAddInObject, а как-то иначе.

Инициализация/завершение существования компоненты

После создания класса, реализующего компоненту, платформа вызывает методы этого класса. Перед началом работы платформа сообщит нам объект «самой себя», с которым мы можем вызывать те или иные методы самой платформы. Происходит это в методе Init. В примере объект платформы сохраняется в переменной m_iConnect.

Еще один важный метод - setMemManager. Позволяет выделять блоки памяти, которые будет освобождать сама платформа. Реализуется следующим образом:

Мы просто сохраняем у себя указатель на менеджера памяти, который передает нам платформа. Потом, этим менеджером мы будем выделять память, освобождаемую самой платформой.

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

Полезная нагрузка. Методы и свойства объекта компоненты

Регистрация

Ну и разумеется, мы создавали компоненту не ради ее инициализации, а ради какого-то полезного функционала. Настало время рассмотреть, как он реализуется.

Во-первых, мы должны зарегистрировать объект, который может быть создан и вызван из языка 1С. Данный объект регистрируется в методе RegisterExtensionAs.

В этом методе мы сообщаем платформе имя нашего класса, как оно будет видно из языка 1С. Именно по этому имени мы будем его создавать через «Новый». В данном случае, создание объекта будет выполняться следующим кодом:

ПодключитьВнешнююКомпоненту(Файл, "МояКомпонента" , ТипВнешнейКомпоненты. Native);
ОбъектКомпоненты = Новый("AddIn.МояКомпонента.AddInNativeExtension" );

Согласно документации, память для строки с именем класса выделяется менеджером памяти, и по этому адресу записывается имя - «AddInNativeExtension». Здесь можно безболезненно написать свое имя. Обратите внимание, опять происходит преобразование из wchar_t в платформенный WCHAR_T.

Использование

Как я писал выше, платформа опрашивает компоненту на предмет различных языковых особенностей. Есть ли указанное свойство, поддерживает ли оно запись, есть ли у параметра функции значение по умолчанию, есть ли возвращаемое значение и т.п. Если взять приведенный ранее пример кода:

НашОбъект = Новый("AddIn.MyComponent.DataSender" ); // DataSender - это имя из ф-ции RegisterExtensionAs (рассмотрена ниже).
НашОбъект. Адресат = "[email protected]" ;
НашОбъект. ВыполнитьРасчет(СуммаПлатежа, "За коммунальные услуги" );

то будет выполнен следующий опрос:

  1. Есть ли свойство «Адресат»
  2. Поддерживает ли оно запись
  3. Есть ли метод ВыполнитьРасчет
  4. Сколько у него параметров
  5. Есть ли у него возвращаемое значение
  6. Какие умолчания у необязательных параметров (если есть)

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

Свойства

Первое, что стоит рассмотреть - это инфраструктура свойств. Платформа запрашивает существование свойства методом FindProp

Платформа передает нам имя искомого свойства в виде WCHAR_T. Оно преобразуется в wchar_t вспомогательным методом, и происходит поиск этого текста сначала в англоязычных термах, а затем в русскоязычных. Вернуть мы должны номер свойства. Обратите внимание, здесь задействована вспомогательная функция findName. Ее реализация есть в примере, но нет в пустом шаблоне компоненты. Кажется целесообразным перетаскивать ее к себе, если в компоненте планируются двуязыячные термы.

Далее, метод GetPropName выполняет обратную задачу, получает имя свойства по его номеру. Строка с именем также выделяется через менеджер памяти предприятия. Подозреваю, что метод GetPropName совместно с GetNProps используется, когда мы разворачиваем свойства объекта «плюсиком» в отладчике. Тогда платформа получит общее число свойств и для каждого из них запросит имя.

Следующая пара методов IsPropReadable/IsPropWritable. Здесь все просто, для указанного номера свойства мы должны сказать можно ли его читать/писать.

Получение и запись значений выполняются методами GetPropVal/SetPropVal. Здесь стоит остановиться подробнее. Мы начинаем работать с типами 1С:Предприятия, а значит, на сцену выходит Variant.

Шаблон компоненты определяет набор вспомогательных макросов для упрощения работы с Variant. Первый из них - это проверка типа значения. Например, макрос TV_VT позволяет проверить/установить тип значения. Определены также именованные константы для каждого из поддерживаемых типов. Эти константы и их соответствия типам 1С:Предприятия перечислены в документации.

Макрос TV_BOOL получает из варианта булево значение, с которым можно работать. По аналогии получаются целые значения (TV_INT), строки (TV_WSTR) и другие. Точные значения есть в коде, их всегда можно посмотреть.

Важный момент - мало присвоить варианту какое-то значение, нужно также присваивать и действительный тип. Обратите внимание на GetPropVal. Помимо присваивания TV_BOOL = true идет присваивание типа: TV_VT = VTYPE_BOOL. Если тип не присвоить, платформа не будет знать - какой тип значения ей вернули. Разумеется, можно накосячить и задать неверный тип. Часто это сопровождается падением платформы.

Подведем итог вышесказанного:

Получаем значение из варианта:

bool someVariable = TV_BOOL(pVariant);

Записываем значение в вариант:

TV_VT(pVariant) = VTYPE_BOOL; // действующий тип данных

TV_BOOL(pVariant) = someBooleanVariable; // устанавливаем само значение

А теперь - горбатый методы!

С методами немного сложнее, но в целом, похоже на свойства. Во-первых, есть точно такая же функция поиска метода по имени, получения общего количества методов, получения имени по номеру. Однако помимо перечисленного, добавляются следующие особенности:

  • Если метод может возвращать значение, значит, его можно использовать в «Вычислить» и записывать справа от операции присваивания в языке 1С. Если нет, то это процедура и подобные вещи будут приводить к исключению «Использование процедуры как функции»
  • У метода есть параметры. Платформа должна знать их количество. Если при вызове указано аргументов больше чем в сигнатуре метода, то возникает ошибка «Слишком много параметров»
  • Если методу передано недостаточное количество аргументов, значит, некоторые из них могут быть необязательными, а если необязательных параметров нет, то возникает ошибка «Недостаточно параметров».
  • При вызове, если это процедура, то возвращаемого значения быть не может. Если это функция, то есть возвращаемое значение. Его тоже нужно обработать.

Есть ряд простых методов, назначение которых понятно из их имен и из документации. Сюда относятся HasRetVal, GetNParams, GetParamDefValue. Их предлагаю не рассматривать, примера более чем достаточно. Наш интерес будет направлен в сторону непосредственной реализации полезной нагрузки. Она реализуется в методах CallAsProc и CallAsFunc. Первый отвечает за вызов процедур, второй - за вызов функций. Отличаются они тем, что CallAsFunc имеет дополнительный выходной параметр, в котором мы передадим платформе возвращаемое значение функции.

Вызов осуществляется следующим образом: платформа передает нам номер вызываемого метода, массив фактических параметров и их количество. Мы должны проанализировать номер метода и скормить ему переданные параметры. В случае функции мы должны также записать что-то в возвращаемое значение.

В примере номер метода анализируется в switch/case и в зависимости от номера выполняется логика метода. Для методов Включить/Выключить просто устанавливается флажок. Интересен метод ПоказатьВСтрокеСтатуса. Он показывает то, что ему передали в строке состояния окна 1С:Предприятия. Для этого используется объект подключения к платформе m_iConnect, тот, что был «выдан» нам при регистрации компоненты. Полный перечень его возможностей описан в документации.

Интересный момент. Здесь, в примере, не проверяется тип приехавшего из 1С значения, а просто вызывается SetStatusLine со строковой частью Variant. Я подозреваю, что если вызвать метод компоненты из языка 1С, передав туда число или дату (вместо строки), то работать ничего не будет… Опять же, пусть гуру поправят, но кажется, что указатель pwstrVal будет указывать неизвестно куда, если из предприятия приехало скажем, число, а не честная строка. При вызове SetStatusLine, платформа попытается прочитать с неизвестного адреса строку и, скорее всего, упадет. Лучше всегда проверять ожидаемый тип. Мало ли чего.

Функция ЗагрузитьКартинку в примере реализована более интересно, там рассмотрена возможность обмена с платформой строками и двоичными данными.

Во-первых, здесь проверяется количество переданных параметров. Если их нет, то вызов признается неудачным. Возвращается false, что трактуется платформой, как ошибка вызова.

Далее, здесь проверяется тип переданного параметра. Если это узкая строка (VTYPE_PSTR), то используется char-овая часть варианта. В примере написано paParam->pstrVal, но можно воспользоваться макросом TV_STR, будет то же самое, но еще и соблюдено единообразие работы с вариантом.

Если это широкая строка (VTYPE_PWSTR), то выполняется преобразование сначала к wchar_t, а затем к char. Дело в том, что нам из языка 1С в данный метод передается путь к файлу, который затем используется в функции fopen(char*). Эта функция на вход требует тип char*, а из платформы к нам придет WCHAR_T. Для корректной работы и выполняются преобразования строк.

Ну и последнее, если это вообще не строка, то вызов признается неудачным, возвращается false.

Мы выделяем память под двоичные данные менеджером памяти. Это логично, двоичные данные станут полноценным объектом внутри платформы, и распоряжаться ими должна именно она. Память выделяется для варианта pvarRetValue, представляющего собой возвращаемое значение функции внешней компоненты.

В выделенный буфер считывается файл целиком, кроме того, обязательно указывается байтовый размер в свойстве варианта strLen и тип данных варианта VTYPE_BLOB. Если память выделится удачно, то возвращаем true, как признак удачного вызова всей функции.

Таким образом, когда в языке 1С будет написано:

ДвоичныеДанные = Компонента . ЗагрузитьКартинку ("C:\pic.jpg");

будет вызван метод CallAsFunc объекта компоненты с передачей пути и возвратом двоичных данных, как описано выше.

В случае успеха переменная ДвоичныеДанные будет содержать полноценный объект языка 1С. Когда он выйдет из области видимости вся занятая им память будет освобождена платформой. Именно поэтому выделялась она через менеджер памяти.

Заключение

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

Спасибо за внимание.

Технология внешних компонентов позволяет создавать программы (внешние компоненты), которые будут динамически подключаться и тесно взаимодействовать с системой 1С:Предприятие 8, расширяя ее возможности. Данная технология позволяет подключать к системе 1С:Предприятие 8 различное торговое оборудование: сканеры штрих-кодов, принтеры этикеток и т.д.

Native API

Для создания внешних компонентов используется технология Native API – собственный интерфейс системного программирования 1С:Предприятия 8. Она поддерживает операционные системы Windows и Linux, и дает возможность создавать внешние компоненты, работающие как под одной, так и под другой операционной системой. Компоненты, созданные по технологии Native API, могут быть подключены в толстом клиенте, в тонком клиенте, в веб-клиенте, внешнем соединении и в сервере приложений.

Расширение встроенного языка

Внешние компоненты позволяют расширять встроенный язык новыми объектами. Структуры механизмов внешних компонент максимально приближены к внутренним структурам системы 1С:Предприятие 8, что повышает эффективность работы.

Вызов процедуры обработки событий, контролируемых внешней компонентой

Внешняя компонента может порождать события, обрабатываемые в предопределенной процедуре языка ОбработкаВнешнегоСобытия . Это позволяет подключать к системе 1С:Предприятие 8 сканеры и другие устройства, требующие асинхронного обмена данными.

Добавление страницы свойств в параметры «1С:Предприятие 8»

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

Сохранение параметров компоненты через механизм сохранения параметров «1С:Предприятие 8»

При сохранении своих параметров внешняя компонента может использовать механизмы системы 1С:Предприятие 8.

Доступ к строке состояния

При работе состояние внешней компоненты может отражаться в панели состояния системы 1С:Предприятие 8.

ОЛЕГ ФИЛИППОВ , АНТ-Информ, заместитель начальника отдела разработки, [email protected]

Расширяем функциональность 1С:Предприятия
Часть 1. Внешние компоненты COM

В жизни каждого разработчика 1С наступает момент, когда задачи, перед ним поставленные, превосходят возможности платформы 1С. Рассмотрим способы, как «преодолеть границу возможного»

Опытные разработчики 1С уже, наверное, по первым трем словам названия статьи догадались, что речь пойдет о внешних компонентах для 1С:Предприятия. Эта технология существует еще со времен платформы 1С:Предприятие 7.7 (или более ранней). Исторически появилась в основном для решения задач взаимодействия с торговым оборудованием (сканерами ШК, электронными весами, ККМ), в ходе которых возникает необходимость обработки платформой 1С определенных событий, инициируемых оборудованием. Такие события являются не чем иным, как поступающими на COM/LPT-порты последовательностями байт. Конечно, адаптировать платформу 1С для работы с такими низкоуровневыми механизмами было бы не очень правильно, поэтому придумали технологию внешних компонент для 1С:Предприятия.

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

Что такое внешние компоненты

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

На практике существует ряд тонкостей: если внешняя компонента активно взаимодействует с самой платформой, то ее функции будут специализированными для конкретной версии.

С версии 8.2 в 1С:Предприятии появилась новая технология разработки внешних компонент, так называемая NativeAPI. Компоненты, написанные по этой технологии, уже не являются COM-объектами. С одной стороны, это плюс – данные компоненты не требуют регистрации, с другой, использование их еще где-либо, кроме платформы 1С:Предприятие, невозможно. Несколько сложнее стала разработка внешних компонент, чуть большее количество обязательных интерфейсов они должны поддерживать. Но о ней речь пойдет в следующей статье.

Внешние компоненты и технология COM

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

Если же хочется все-таки сделать полноценную COM внешнюю компоненту для 1С:Предприятия (из вариантов, зачем это может понадобиться, могу придумать только один – компонента должна активно взаимодействовать с системой на платформе 1С, оповещать пользователей, изменять строку статуса, отображать диалоговые окна и т.п.), то надо воспользоваться непосредственно технологией внешних компонент. Итак, приступим.