Технологія зовнішніх компонентів (). Технологія зовнішніх компонентів () Шаблон компоненти на ІТС

ansmirnov 22 серпня 2013 о 14:12

Зовнішні компоненти 1С 8.2

  • Програмування,
  • C++
  • 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; ((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:)
Внесемо зміни до функції :
bool CAddInNative::GetParamDefValue(const long lMethodNum, const long lParamNum, tVariant *pvarParamDefValue) ( ​​TV_VT(pvarParamDefValue)= VTYPE_EMPTY; switch(lMethodNum) howInStatusLine: 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(LoMethodNum) ( case eMeMe if (!lSizeArray || !paParams) return false; s1 = (paParams) -> pwstrVal; s2 = (paParams+1) -> pwstrVal; 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; w "ComponentNative", *what = L"Timer"; wchar_t *wstime = new wchar_t; if (wstime) ( wmemset(wstime, 0, TIME_LEN); , 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. Перерахуємо найчастіше використовувані:
Генерація повідомлення про помилку
віртуальний 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; uint8_t ui8Val; 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; strVal;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 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С, оповіщати користувачів, змінювати рядок статусу, відображати діалогові вікна та т.п.), то треба скористатися безпосередньо технологією зовнішніх компонентів. Отже, почнемо.

  • 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; ((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:)
Внесемо зміни до функції :
bool CAddInNative::GetParamDefValue(const long lMethodNum, const long lParamNum, tVariant *pvarParamDefValue) ( ​​TV_VT(pvarParamDefValue)= VTYPE_EMPTY; switch(lMethodNum) howInStatusLine: 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(LoMethodNum) ( case eMeMe if (!lSizeArray || !paParams) return false; s1 = (paParams) -> pwstrVal; s2 = (paParams+1) -> pwstrVal; 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; w "ComponentNative", *what = L"Timer"; wchar_t *wstime = new wchar_t; if (wstime) ( wmemset(wstime, 0, TIME_LEN); , 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. Перерахуємо найчастіше використовувані:
Генерація повідомлення про помилку
віртуальний 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; uint8_t ui8Val; 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; strVal;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.

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

  • 1 За допомогою Native API
  • 2 З використанням технології COM
У цій статті я вирішив висвітлити роботу з компонентами Native API.
Отже, приступимо, від простого до складного:
Витяг з ІТС

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

Можливе використання в "Товстому Клієнті (звичайний додаток)";

Це найпростіший приклад роботи з компонентом Native. Слід звернути увагу, що компонент цього не вимагає реєстрацію у системі, що значно спрощує адміністрування.

2. Розглянутий вище приклад не життєвий. Найчастіше компонент розташовують у макеті. Макет повинен містити zip архів з файлами компонента та файлом 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" ,ТипЗовнішньоїКомпоненти. Компонент = Новий (AddIn.Comp.MIKO_phone_IP); Виняток Повідомити("Виняток при ініціалізації. Можливо, компонент ще не був встановлений."); КінецьСпроби; КінецьПроцедури