NativeAPI. Внешние компоненты на С++ "для чайников"

В жизни каждого 1С-ника наступает момент, когда для выполнения задачи требуется код на другом языке программирования. На помощь приходят внешние компоненты, но как их писать, если последний раз вы брались за другой язык сто лет назад, сортируя массивы пузырьком на лабораторках в ВУЗе? Можно быстренько узнать только самое нужное, прочитав эту статью.

Предисловие

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

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

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

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

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

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

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

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

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

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

solution

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

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

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

1

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

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

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

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

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

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

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

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

2

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

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

3

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

Строки 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. Посмотрим на код этих функций:

4

Самая простая – функция 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.

idone

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

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

mm

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

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

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

Регистрация

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

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

 ras

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

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

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

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

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

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

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

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

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

Свойства

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

fp

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

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

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

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

dd

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

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

 p

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

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

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

1

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

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

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

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

Далее будет открыт файл, и в случае ошибки будет выдано сообщение (это код для краткости пропущен). Если все хорошо, то в 1С возвращаются двоичные данные картинки.

2

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

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

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

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

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

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

Заключение

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

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

99 Comments

  1. BorisMor

    Ну все… на майские праздники есть в чем разобраться)

    Reply
  2. vasbur

    А можете рассказать, как из С++ в 1С вернуть пустую дату?

    Чтобы и на 7.7 и на 8.2 правильно работало

    Reply
  3. andrewks

    (2) vasbur, для 8-ки 0001.01.01 0:00:00

    Reply
  4. vasbur

    (3) Это в 1С.

    А как это в С++ написать?

    Reply
  5. andrewks
  6. vasbur

    (5) Вобщем, дату «0001.01.01» не вернуть никак

    Reply
  7. andrewks

    (6) vasbur, можно NULL вернуть

    Reply
  8. DoctorRoza

    Хорошо написано, тем более, что 2-3-мя строчками кода тут не обойдешься. Но все-таки, сорцы нужно выкладывать, не жмотся, Автор! 🙂

    Reply
  9. Evil Beaver

    (8) DoctorRoza, ну сорцы как бы на ИТС. Они у всех есть, чего их выкладывать?

    Reply
  10. dyak84

    Очень интересно. Автору огромное спасибо. Есть над чем поработать на выходных

    Reply
  11. AlexanderKai

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

    Reply
  12. le_

    Слегка сумбурно на мой взгляд. Но идея хорошая.

    Я думаю, всем было бы понятнее, если бы автор на конкретных примерах показывал как реализовать необходимые в большинстве случаев механизмы.

    Моё субъективное мнение таково, что у тех, на кого ориентирована эта статья («чайники»), если они возьмутся писать компоненту, в процессе обязательно возникнет ряд вопросов, типа, «как вернуть из компоненты значение» и даже не какое-нибудь хитрое, а просто строку. В шаблоне нет такого примера (именно примера) и в этой статье нет. Тип tVariant упомянут вскользь. 1С-ники зачастую вообще забывают о том, что существуют какие-то типы данных. Передача параметров по ссылке, по указателю, по значению — что это такое? )

    Предполагаю так же, что возникнут сложности с передачей параметров в компоненту. Как получить и обработать несколько строковых параметров в компоненте? Да, это скорее вопросы программирования на С++ и к технологии компонент имеют опосредованное отношение, но ориентация-то на «чайников».

    Как задать параметры по-умолчанию? Как обработать исключительные ситуации? Как сохранить значения для использования при следующем использовании компоненты? Как создать свою форму? Для чего нужен менеджер памяти?

    Всё-таки, много вопросов осталось нераскрытыми )

    Reply
  13. Evil Beaver

    (12) le_, замечания по существу, спасибо. Только вот с этим немного несогласен:

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

    Как раз это есть. Там где возвращаются двоичные данные, вместо BLOB нужно просто задать тип PSTR.

    Но вообще, говоря, вы правы. Нужен краткий справочник howto для типовых действий. Подумаю над продолжением.

    Reply
  14. le_

    (13) Просто имеет смысл акцентировать внимание на том, что и для возвращения строки, обязательно указывать её размер в байтах. Для ASCII один символ — это один байт, а для unicode — 2 байта. Для 1С-ников это непривычно…

    Reply
  15. kng67

    Тема интересная.

    Мой коллега пишет внешние компоненты на С++ для 1С.

    Reply
  16. andrewks

    (14) le_, и + память под строку выделять менеджером памяти 1С, а не сишным

    Reply
  17. Evil Beaver

    (14) le_, там вроде бы в варианте есть два отдельных размера. Для char — strLen, для wchar — wlen не помню, как то так. Указываем все время в символах и не паримся.

    Reply
  18. Evil Beaver

    (16) andrewks, про менеджер вроде много говорил, что им нужно выделять. Непонятно получилось?

    Reply
  19. andrewks

    (18) если честно, статью смотрел строку через пять 😉

    Reply
  20. andrewks

    (17) совершенно верно, но память нужно выделять в байтах 🙂

    Reply
  21. CratosX

    Достойно!

    (12) Le_, на инфостарте уже немного есть пошаговых инструкций. Например, тут, или вот оптом в google MS Visual Studio 1С & внешняя.

    Но, у Evil Beaver получилось неплохо, с неплохим оформлением.

    Reply
  22. Oleg1708

    Спасибо, понравилось. Вспомнил С++… 🙂

    Reply
  23. _LEV_

    Полезно, есть что вспомнить и освежить. Спасибо.

    Reply
  24. ranger

    Автору спасибо за кропотливую работу.

    Но мне без базовых знаний С++ как-то сложно читается к сожалению:(

    Reply
  25. a-novoselov

    (9) Ну «ИТС есть у всех» это не всегда правда, например, на работе есть, а дома нет. И чтобы дома попробовать, придется искать этот ИТС. Тем более за скачанные файлы вам $m будут капать, так что лучше бы выложили… Можно даже со своими комментариями, объясняющими для чайников назначение той или иной функции и ее параметров (особенно если не понятно из названия).

    А в целом статья полезная, однозначно +

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

    Внутри C++ тоже удобнее работать с двубайтовыми строками. Проект скомпилировать с опцией UNICODE и автоматом будут использоваться W-варианты функций WinAPI. Например для работы с файлами гораздо удобнее пользовать класс:

    #include <atlfile.h>

    CAtlFile oFile;

    HRESULT nRes = oFile.Create(cSrc, GENERIC_READ, 0, OPEN_EXISTING);

    nRes = oFile.Read(cBuff, nSize, nGet);

    Reply
  27. Evil Beaver

    (26) oleg_km, тут вся свистопляска из-за линукса. Там нет винапи, а wchar_t, говорят, четырехбайтовый. Вот и сделали обертку WCHAR_T, чтобы всегда и везде была 2 байта. Но ее в wcslen не передашь, поэтому внутри компоненты — компот из разных строк.

    Reply
  28. Borometr

    Недавно начал изучать C#, оказывается не зря, пригодиться и в работе с 1С

    Reply
  29. CratosX

    (0), скачал ваш шаблон, попробовал открыть в

    Microsoft Visual Studio Express 2012 для Windows Desktop

    Версия 11.0.50727.42 VSLRSTAGE

    Microsoft .NET Framework

    Версия 4.5.50709

    Visual C++ 2012 05695-004-0030004-02855

    Microsoft Visual C++ 2012

    На Windows 7 pro x32.

    Дело в версии MS Visual Studio или в Visual C++?

    Но это пол беды. Следуя статье, наткнулся на то, что в файле AddInNative.def нет того кода, что приведён в статье. Предлагается ввести его самостоятельно?

    Естественно, компиляция выдаёт кучу ошибок, но думаю до их разбора пока далеко. Что делаю не так?

    Reply
  30. Evil Beaver

    (29) CratosX, на вашем рисунке файл *.def содержит все, что нужно — три обязательных экспорта. В статье они описаны. Сам вложенный файл — это фрагмент шаблона компоненты с диска ИТС, прикрепил «как есть», вроде бы он нормально собирался, без проблем. Копайте причины ошибок.

    Что делаю не так?

    Как минимум не сообщаете точные тексты ошибок. Телепат был только у г-на Орефкова и то для 7.7 🙂

    Reply
  31. CratosX

    (30) в посте прикрепил один файлик — UpgradeLog.XML, он довольно объёмный и не счёл нужным приводить его в текстовом виде

    Reply
  32. Evil Beaver

    (31) CratosX, да, но это лог конвертации проекта из одной версии «студии» в другую. Поэтому сразу в него не посмотрел, ожидал ошибок именно сборки, а не upgrade проекта. Тем не менее, как я и предполагал, студия жалуется на возможные проблемы с линковкой. Разбирайтесь с настройками проекта, чего-то там не срастается с библиотеками.

    Reply
  33. CratosX

    (32) тогда скажите версию своей MS Visual Studio. Да и Windows наверняка x64?

    Reply
  34. CratosX

    (32) с этим разобрался — файлы из папки include надо было скопировать в папку основного проекта.

    Теперь при построении выдаёт следующее:

    1>—— Перестроение всех файлов начато: проект: AddInNative, Конфигурация: Debug Win32 ——
    1>  stdafx.cpp
    1>  AddInNative.cpp
    1>  dllmain.cpp
    1>AddInNative.obj : warning LNK4075: не учитывается «/EDITANDCONTINUE» из-за спецификации «/SAFESEH»
    1>     Создается библиотека D:ShareExtNativeAPIvncomp_publicNativeAPI__\bindAddInNa­tive.lib и объект D:ShareExtNativeAPIvncomp_publicNativeAPI__\bindAddInNa­tive.exp
    1>  AddInNative.vcxproj -> D:ShareExtNativeAPIvncomp_publicNativeAPI__\bindAddInNa­tive.dll
    ========== Перестроение всех: успешно: 1, с ошибками: 0, пропущено: 0 ==========

    ,

    а при отладке ругается на неправильный путь к dll. Где копать?

    Reply
  35. CratosX

    Думаю, проблема в параметрах компиляции, но как правильно изменить пути, не знаю

    UPD: Решено, в моих глобальных переменных параметр $(SolutionDir) оканчивается на слэш. В итоге требовалось изменить строку параметра на $(SolutionDir)bind

    Reply
  36. Evil Beaver

    (35) CratosX, у вас же все нормально собралось, ошибок сборки нет. В чем беда-то? Что отладка не стартует? Дык это ж DLL. Она ж не умеет стартовать сама по себе. Она же только из какого-то процесса другого может работать. Вы ее втупую из 1С пробовали вызывать?

    Обычно мне помогает гуглить по точному тексту ошибки. Удивительно, но в интернетах почти на все вопросы уже есть ответ.

    Reply
  37. CratosX

    (36) так я и спрашиваю как новичок новичка 🙂

    DLL-ку я увидел, и использовать из 1С — это дело второе.

    Беда в том, что смущало это сообщение, прямое гугление по тексту ошибок навело на неверные параметры настройки компиляции. Ну, не вы, так кто-нибудь другой может подскажет — вдруг это фишка 2012-го MS VS?

    Да, пытаюсь из 1С обратиться к компоненте

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

    Из статьи не совсем понял, как обращаться к методам этой ВК

    Reply
  38. Evil Beaver

    (37) CratosX, к методам обращаться прям как они описаны внутри компоненты. В примере имена методов строками прям в начале файла идут. Из 1С их по этим именам и вызывать:

    ОбъектКомпоненты.МойМетод();

    Reply
  39. CratosX

    (38) слона-то я и не заметил. Спасибо!

    Reply
  40. CratosX

    (38) в статье есть неточность — заявлены методы

    Методы:

    Включить/Enable;

    Выключить/Disable;

    ПоказатьВСтрокеСтатуса/ShowInStatusLine;

    ВключитьТаймер/StartTimer;

    ВыключитьТаймер/StopTimer;

    ЗагрузитьКартинку/LoadPicture;

    Но в самой ВК используются

    static wchar_t *g_MethodNamesRu[] = {L»Включить«, L»Выключить«, L»ПоказатьВСтрокеСтатуса«,

    СтартТаймер«, L»СтопТаймер«, L»ЗагрузитьКартинку«};

    Если кому интересно — вот заготовка для теста из 1С

    Reply
  41. Alex1Cnic

    Интересая публиация, но можно ли сделать подобное для Delphi(RadSudio XE)?

    Reply
  42. Alex1Cnic

    Мне Delphi ближе к телу….а С++ что-то пока не освою…

    Reply
  43. quebracho

    (40) CratosX,

    А вот версия обработки с «живым» таймером 🙂

    + dll

    Reply
  44. Boudybuilder

    Супер статейка!

    Автору спасибо!

    Reply
  45. Abadonna

    Как гляну на сишный код, аж передергивает :)))

    Но плюс все равно.

    Reply
  46. Abadonna

    (41) ProFix1c, вот это спасет отца русской демократии 😉

    http://infostart.ru/public/81644/

    Reply
  47. Evil Beaver

    (46) как вспомню паскальное присваивание через двоеточие, аж передергивает. Но зач0т, все равно)

    Reply
  48. quebracho

    Чтобы ногу свело, вспомните prolog.

    Reply
  49. Alex1Cnic

    (46) Abadonna,

    респект, то, что надо!

    Reply
  50. KroVladS

    Большое спасибо.

    Усиленно ковыряю, пока не очень понятно. Но очень занимательно.

    Reply
  51. androgin

    Я только не понимаю, как скомпилировать компоненту для вебклиента

    нигде не нашел мануалов с описанием/картинками..

    подскажите…

    Reply
  52. MishaHD

    Доброго времени суток, в статье часто упоминается «документация». Но вот где ее найти? Что-то в web итс и на дисках вообще не густо информации. Например вообще не нашел инфы по поводу m_iConnect например. Подскажите где искать плиз.

    Reply
  53. buganov

    А кто знает можно ли как-нибудь отладить ВК в VS 2012?

    Reply
  54. Evil Beaver

    (53) buganov, запускаете Предприятие, потом в Студии делаете меню «DEBUGAttach to process». Выбираете процесс отлаживаемого Предприятия.

    В студии ставите точку останова, а в Предприятии вызываете вашу компоненту. Профит.

    Reply
  55. Evil Beaver

    (52) MishaHD, Материал на ИТС называется «Технология создания внешних компонент»

    Reply
  56. sikuda

    мои пару замечаний по внешним компонентам 1C:

    1. Используем кодировку 1251. В Windows неявно, а в Linux makefile: -finput-charset=WINDOWS-1251

    При сборке на Windows 10(Visual Studio 2008) перестают работать русские методы внешней компоненты.

    2. Форматы динамических библиотек для разных компиляторов не совпадают, поэтому собираем компоненту в Windows только в Visual Studio(никакого MinGW),а Linux gcc.

    Reply
  57. Evil Beaver

    (56) sikuda, что значит «используем кодировку»? А зачем вы именно ее используете, какая с этого выгода? Если вы прицеливаетесь на Линукс, то на кой ляд вам там кодировка windows-1251. И где она у вас там?

    Reply
  58. AlexanderKai

    (56) sikuda,

    Почему никакого mingw? MinGW умеет собирать .dll.

    Reply
  59. AlexO

    (28) Borometr,

    Недавно начал изучать C#, оказывается не зря

    Си и C# — это разные языки.

    (45) Abadonna,

    Как гляну на сишный код, аж передергивает

    А от одноэсового? ))

    (57)

    И где она у вас там?

    Это специально виндовую кодировку подключает в Линкус, чтобы с 1С работать можно было нормально. Иначе — будет опа и никаких ВК.

    (0) автор, а какие-либо компоненты сами по себе будут? Никак не найду задач, чтобы писать ВК ))

    Reply
  60. Гость

    Добавлю пару особенностей.

    Первое: указывать версию 2000 обязательно. Иначе следующий код выведет не «1; 2», как ожидается, а «2; 2» (т. е. Объект1 и Объект2 будут на самом деле ссылками на один и тот же объект):

    ПодключитьВнешнююКомпоненту(«AddInNative.dll», «TestComponent», ТипВнешнейКомпоненты.Native);
    
    Объект1 = Новый(«AddIn.TestComponent.TestExtension»);
    Объект1.ЦелоеЧисло = 1;
    
    Объект2 = Новый(«AddIn.TestComponent.TestExtension»);
    Объект2.ЦелоеЧисло = 2;
    
    Сообщить(Объект1.ЦелоеЧисло);
    Сообщить(Объект2.ЦелоеЧисло);
    

    Показать

    Мне лично это немало крови попортило — свою компоненту писал копипастом с чужой, где версия по неизвестной причине стояла 1000.

    И второе: конструктор класса CAddinNative вызовется первый раз при первом вызове

    ПодключитьВнешнююКомпоненту(«AddInNative.dll», «TestComponent», ТипВнешнейКомпоненты.Native);

    а вот при первом вызове

    Новый(«AddIn.TestComponent.TestExtension»);
    

    не произойдет вообще ничего. Второй вызов «Новый» отработает уже как ожидается — т. е. вызовет конструктор CAddinNative.

    Reply
  61. identificator

    Кто знает, как получить путь к директории, в которой лежит вызываемая DLL?

    В обязательных функциях для стандарта 1С Совместимо указана функция ПолучитьПараметры(GetParameters).

    Ее параметром является XML таблица. Я отдельно вынес эту таблицу в XML файл, который положил рядом с DLL.

    А в функции GetParameters указываю путь к этому файлу и преобразовываю его содержимое в тип std::wstring.

    Не получается указать относительный путь к файлу TableParameters.xml. Потому что рабочая директория вызываемой DLL ссылается на директорию BIN, где лежит 1Сv8.exe.

    Если компонента по технологии Native API не требует регистрации в реестре с помощью regsvr32.exe, то вопрос — а как получить путь до этой DLL?

    Потому что подключать к 1С, все время указывая абсолютный путь — неправильно, ведь пользователь может куда угодно положить эту DLL.

    bool CAddInNative::GetParameters(tVariant* xml_f)
    {
    // 1С ПолучитьПараметры
    // считываем xml файл и записываем в строку
    _setmode(_fileno(stdout), _O_U16TEXT);
    
    const wchar_t filePath[] = L»\TableParameters.xml»; //-это путь к таблице параметров, но это неверно, нужен абсолютный путь
    std::wstring text1 = LoadText1(filePath);
    
    wstring_to_p(text1, xml_f);
    return true;
    }

    Показать

    И в статье приводится код

    ПодключитьВнешнююКомпоненту(ПутьФайла, «МояКомпонента», ТипВнешнейКомпоненты.Native);

    Переменная ПутьФайла — откуда заполняется? Как узнать путь???

    Reply
  62. Evil Beaver

    (61) identificator, если я правильно понял, то вот:

    HMODULE GetCurrentModule()
    { // NB: XP+ solution!
    HMODULE hModule = NULL;
    GetModuleHandleEx(
    GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
    (LPCTSTR)GetCurrentModule,
    &hModule);
    
    return hModule;
    }
    
    bool CAddInNative::GetParameters(tVariant* xml_f)
    {
    LPTSTR buffer; // не забыть выделить памяти
    int bufferLen;
    
    HMODULE hCurrent = GetCurrentModule();
    GetModuleFileName(hCurrent, buffer, bufferLen);
    }
    
    

    Показать

    Естественно, не проверял, но люди пишут, что работает )

    Reply
  63. identificator

    (62) спасибо, получилось!

    А как быть с функцией?

    ПодключитьВнешнююКомпоненту(ПутьФайла, «МояКомпонента», ТипВнешнейКомпоненты.Native);

    Как после установки в 1С своего драйвера оборудования потом обращаться к нему?

    1С сама запишет драйвер в двоичный макет после добавления драйвера?

    Или можно в качестве пути указать имя компоненты?

    Reply
  64. flyer

    кто нибудь использовал внешние компоненты для мобильной платформы 1с? на андроиде например.

    сам пробую что то не могу с ИТС запустить приложение Шагомер. не хочет проходить инициализация

    ВК = Новый («AddIn.com_1c_StepCounter.com_1c_StepCounterExtension»);

    Reply
  65. KroVladS

    Есть такая-же статья про создание компоненты для веб клиента?

    Reply
  66. Evil Beaver

    (65) я не разбирался, но по-моему, там же в шаблоне компонент есть примеры для веб-клиента. А бизнес-логика общая в одной dll для всех браузеров

    Reply
  67. karpik666

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

    Например,

    лКомпонента = Новый («Addin.МояКомпонента»);
    лСумма = 0;
    Если лКомпонента.Рассчитать(лСумма) Тогда
    Сообщить(лСумма);
    КонецЕсли;
    
    Reply
  68. Evil Beaver

    (67) так можно сделать, в чем сложность, что не получилось?

    Reply
  69. karpik666

    (68) я просто не знаю как передать значение по ссылке, т.е. я могу вернуть значение при выполнении функции — return ret, в типовых примерах я не нашел такой возможности. Вот к примеру

    bool CAddInNative::CallAsFunc(const long lMethodNum,
    tVariant* pvarRetValue, tVariant* paParams, const long lSizeArray)
    {   switch(lMethodNum)
    {
    case eMethGetDescription:
    bool ret = true;
    }
    
    TV_VT(pvarRetValue) = VTYPE_BOOL;
    TV_BOOL(pvarRetValue) = true;
    
    return ret;
    }
    
    

    Показать

    Как переделать данную функцию так. чтобы в параметре paParams поменялось одно или несколько входящих в него значений?

    Reply
  70. Evil Beaver

    (69) да точно так же, как в возвращаемом значении. Вам надо в массиве параметров paParams по нужному индексу прописать выходное значение.

    Что-то вроде paParams[1] = myVariantValue;

    Навскидку не помню, возможно, есть еще настроечный метод про выходные параметры (по аналогии с HasRetValue и IsPropReadable) По-моему, нет такого, но лучше уточнить.

    Reply
  71. karpik666

    (70) делал так

     TV_VT(paParams+1) = VTYPE_PSTR;
    TV_STR(paParams+1) -> pstrVal = «ОПИСАНИЕ»;

    Но что-то не заработало.

    Не хватает знаний C++, может знаете где можно увидеть тогда пример передачи такого параметра? в типовых на ИТС, такого нет

    Reply
  72. Evil Beaver

    (71) Первое: кажется, что неправильно писать TV_STR()->pstrVal, т.к. этот макрос уже возвращает свойство pstrVal.

    т.е. нужно писать TV_STR(&paParams[1]) = «ОПИСАНИЕ».

    Второе: строчка выше неверна еще и потому, что текст «ОПИСАНИЕ» должен быть выделен менеджером памяти платформы, а не на стеке.

    Ну и третье: не хватает указания длины строки. Нужно еще присваивание pstrLen (или как там от называется в Variant)

    Reply
  73. karpik666

    (72) спасибо, а как выделить значение в памяти? для меня это темный лес. по остальному вроде понятно.

    Reply
  74. KazanKokos

    Оставлю на заметку

    Reply
  75. Evil Beaver

    (73) смотрите примеры выше и код шаблона. m_iMemory->AllocMemory вот такие вот строчки.

    Reply
  76. karpik666

    (75) хорошо, большое спасибо.

    Reply
  77. Goblin26

    Добрый день.

    Пытаюсь скомпилировать компоненту в VisuaiStudio 2013. Все отрабатывает без ошибок. Компонента на локальной машине запускается.

    Но при попытке подключить на сервере метод

     ПодключитьВнешнююКомпоненту(«ОбщийМакет.Компонента», «AddInNativeExtension», ТипВнешнейКомпоненты.Native)

    возвращает Ложь.

    Как пример брал исходники с сайта ИТС. Компилировал и как 32-битну и 64. Все равно. Есть подозрение что проблема в настойках студии, вот только в каких не понимаю.

    Компилировал на машине где стоит виндовз 10. На сервере серверная винда 2012

    Reply
  78. PerlAmutor

    (58) Потому, что собирать собирает, а работать не работает. Исключение возникает. Тем более, что если .dll тянет какую-нибудь другую .dll с собой, то её уже никак не запакуешь вместе с родительской, 1С не находит.

    Reply
  79. zels

    Как по dll-файлу определить, сделан он как com-компонента или как native?

    Reply
  80. Evil Beaver

    (79) Средствами проводника? Никак. Более того, в одном и том же файле может быть и то, и другое.

    Reply
  81. androgin

    Объясните тупому, как скомпилировать компоненту для Х64 архитектуры?

    Reply
  82. Evil Beaver

    (81) Каким компилятором?

    Reply
  83. androgin

    (82) VS 2015 / VS 2017

    Reply
  84. Evil Beaver

    (83) там в настройках конфигурации Debug и Release по-моему архитектура процессора выставляется. Но это не точно

    Reply
  85. androgin

    (84) Да, это не точно)))

    Reply
  86. Evil Beaver

    (85) Документация говорит, что это все же где-то там: https://msdn.microsoft.com/en-us/library/ms185328.aspx

    Reply
  87. OldthiefXXX

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

    m_iMemory->AllocMemory((void**)&pvarRetValue->pstrVal, 22);

    TV_VT(pvarRetValue) = VTYPE_INT;// VTYPE_PWSTR;

    pvarRetValue->pstrVal = «22»;

    В 1с ловлю результат функции неопределенно. Где мой косяк?

    Reply
  88. Evil Beaver

    (87) строкой указал значение же ведь!

    TV_VT(pvarRetValue) = VTYPE_INT;// VTYPE_PWSTR;
    TV_I4(pvarRetValue) = 22; // тут число а не строка. И лучше применять макросы TV_

    Ах, да. В случае числа — выделять память не надо, выше нужно удалить Alloc

    Reply
  89. OldthiefXXX

    БИНГО!

    pvarRetValue->vt = VTYPE_I4;

    pvarRetValue->lVal = 22;

    Reply
  90. arcadics

    «Если наша компонента реализует только один класс (как в примере), то эти функции нужно тупо скопировать к себе.»

    А как быть когда классов несколько?

    long GetClassObject(const wchar_t* wsName, IComponentBase** pInterface)

    Создает новый экземпляр класса IComponentBase на основании static const wchar_t g_kClassNames[] = { L»Class1|Class2|Class3″ };

    при этом DestroyObject(IComponentBase** pIntf) срабатывает для класса созданного в 1С: объект = Новый (Class1);

    Остальные классы уничтожаются при закрытии платформы.

    При анализе вызова функций в отладчиках 1С и VS я получаю вот такой результат:

    ПодключитьВнешнююКомпоненту(ИмяФайла);

    GetClassObject для каждого класса g_kClassNames[];

    об = Новый («Class1»); В ВК ничего не происходит. Похоже этой строкой я говорю платформе что работаю в данный момент с Class1.

    Если же в этой же обработке вызвать

    об1 = Новый («Class2») т.е. объявить еще одну переменную не уничтожая первую.

    Сработает DestroyObject для Class1 !!!

    Т.е :

    об1 = Новый Class1;

    об2 = Новый Class2; ->> DestroyObject(Class1);

    об3 = Новый Class3; ->> DestroyObject(Class2);

    Вот и получается если после закрытия обработки взывающей процедуру ПодключитьВнешнююКомпоненту в памяти останутся ссылки на объекты созданные GetClassObject они уничтожаться только при закрытии программы.

    Пересмотрел множество примеров ВК ответа не нашел. Во всех используется 1 класс.

    В документации Class1 связывается с фразой «Расширение». Ок. Расширение для одного основного класса. Но как расширять если платформа для каждого расширения требует ссылку на новый IComponentBase?

    Reply
  91. Evil Beaver

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

    Я сейчас точно уже не вспомню, по-моему, порядок такой:

    1. Без создания всяких объектов со стороны кода 1С платформа при подключении компоненты вызовет создание класса (всех классов?)

    2. Если создать из кода объект, то платформа не вызовет CreateObject (похоже, она вернет тот, первый созданный экземпляр, который запросила неявно в п.1)

    3. Все последующие создания классов работают ожидаемо по цепочке Create/Destroy

    4. Экземлпяр класса созданный в п.2 также уничтожается по счетчику ссылок и для него вызывается Destroy, если он вышел за пределы видимости

    5. Если пункт 2 не выполнялся, т.е. мы подключили компоненту, но объектов не создавали, то Destroy для первого неявного объекта (объектов?) вызовется только при закрытии платформы.

    Это наблюдалось на какой-то из 8.2, потом могло измениться.

    И за давностью лет мог что-то напутать. Короче говоря, совершенно точно существует неявно созданный объект, который потом платформой уничтожается. НО все объекты созданные из кода Новый() работают ожидаемо — живут пока на них есть ссылки и уничтожаются при обнулении счетчика ссылок. Неявный объект точно не живет все время работы клиента 1С, но поведение по его созданию/уничтожению выглядит необычно и требует доп.исследований

    Reply
  92. arcadics

    (91)

    но объектов не создавали, то Destroy для первого неявного объекта (объектов?) вызовется только при закрытии платформы.

    Логичнее дать возможность программисту 1С создавать и уничтожать объекты (Новый Класс, Класс = Неопределенно).

    Сейчас создаются все экземпляры IComponentBase на этапе подключения.

    Если например имеется Class1|Class2, а пользователь после после подключения ВК работал с Class2, Class1 после закрытия обработки так и будет висеть в памяти до закрытия платформы. И уничтожить его средствами 1С не возможно т.к. не было создано с помощью «Новый» для него переменной. Я пытался «подчищать» память сохраняя в ВК указатели на объекты в массиве. При закрытии платформы программа рушится так остаются ссылки в самой платформе.

    Может быть я чего то не знаю, или не понимаю в этом функционале.

    Можно конечно на это забить. Но что будет если сотня пользователей будут пользоваться ВК подключенной не в процедуре «ПриНачалеРаботыСистемы» а в внешней обработке?

    (91)

    Неявный объект точно не живет все время работы клиента 1С, но поведение по его созданию/уничтожению выглядит необычно и требует доп.исследований

    Точно! Вопрос нужно сформулировать так: Как удалить все неявно созданные объекты при закрытии внешней обработки?

    Reply
  93. Evil Beaver

    Вопрос не ко мне )

    Reply
  94. markers

    Уважаемый автор, вы не могли бы подсказать как можно можно вернуть массив (Или в крайнем случае ComSafeArray)? Нашел пример только для COM и код в моём случае не работает (там походу ещё и C++ Builder, а у меня Microsoft C++). Я вообще только второй день осваиваю чуждый для меня язык (хоть и знаком с синтаксисом по PHP). Заранее и больше спасибо!

    PS: Если можно вернуть структуру/соответствие, будет ещё лучше!

    Reply
  95. Evil Beaver

    (94) Никак. Массивы ни в компоненту ни из нее возвратить нельзя. Огорчение, но это так.

    Ну а в целом, погружаться в NativeApi на второй день изучения С++ — это прям геройство. Успехов! )

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

    Reply
  96. markers

    (95) Жаль, спасибо за советы. Я тут пытаюсь передать строку типа char, даже вроде уже и память выделяю для строки, но в 1С всё-равно приходит пустая строка, подскажите плиз, что я делаю не так?

    name = «Не удалось подключится к кассе»;
    m_iMemory->AllocMemory((void**)&(paParams + 3)->pstrVal, sizeof(name));
    TV_VT(paParams + 3) = VTYPE_PSTR;
    TV_STR(paParams + 3) = name;
    

    Вместо VTYPE_PSTR пробовал использовать VTYPE_PWSTR (И соответственно вместо TV_STR — TV_WSTR), ничего не изменилось, но даже если бы и изменилось, как поменять тип char на wchar_t? В случае простого текста, то я конечно могу написать name = L»текст», но я так же использую стороннюю библиотеку, а она уже возвращает char[256]

    Большое спасибо!

    Reply
  97. user1055769

    (95) Андрей, здравствуйте! Я занимаюсь разработкой RFID-оборудования, но тут возникла задача разработать Native компоненту для 1С:). На ИТС ознакомился с требованиями к разработке внешних компонент для RFID-оборудования и обнаружил, что в метод «Подключить» необходимо передавать МАССИВ с параметрами подключения. https://its.1c.eu/db/content/metod8dev/src/platform81/to81/requirements/i81­01599.htm?_=1533301588#%D0%BC%D0%B0%D1%81%D1%81%D0%B8%D0%B2%D0%B7%D0%BD%D0%B0%D1%87­%D0%B5%D0%BD%D0%B8%D0%B9 Подобных примеров или каких-либо описаний не нашел.

    Если разработчики 1С это требуют, то значит это как-то возможно?

    Reply
  98. leemuar

    (102) предполагаю, что описанная вами компонента создается по технологии COM, а не NativeAPI:

    * тип этого параметра в документации указан как «IDispatch»,

    * выше в указанной вами статье есть фраза «Также драйвер должен поддерживать множественные подключения (в соответствии с идеологией COM технологии)»

    Reply
  99. markers

    (102) Не знаю вашей задачи, но обратил внимание что вы смотрите старую инструкцию, если вы планируете разработку для современных конфигураций, то вам необходимо читать это: https://its.1c.ru/db/content/metod8dev/src/developers/additional/guides/i81­04829.htm?_=1544610813#chapter236 и там уже нет ни каких массивов и всё через NativeAPI

    Reply

Leave a Comment

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