Как правильно реализовать свойства и работу со строками во внешней компоненте 1С на C++

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

Анализ проблемы инициализации и передачи числовых свойств

Рассмотрим ситуацию, описанную в первом сообщении. Разработчик объявляет свойства TraceLevel, Port и IP, но при обращении к ним из 1С получает «мусорные» значения (например, 128 или 375 вместо установленных 10 или 11000). Проанализируем исходный код реализации методов GetPropVal и SetPropVal, чтобы понять причины такого поведения.

В методе SetPropVal автор использует следующую проверку:


if (TV_UI1(varPropVal) != VTYPE_UI1) return false;
m_uiTraceLavel = TV_UI1(varPropVal);

Здесь кроется фундаментальная ошибка понимания структуры tVariant. Макрос TV_UI1 извлекает само значение (данные), в то время как тип значения хранится в поле, доступном через макрос TV_VT. Сравнивать значение с перечислением типов (VTYPE_UI1) некорректно. Кроме того, важно помнить, что платформа 1С чаще всего передает целые числа как VTYPE_I4 (32-битное целое) или VTYPE_R8 (число с плавающей точкой), даже если в коде 1С вы написали просто «10».

Рассмотрим, как правильно реализовать установку свойства с проверкой типа и приведением:


bool CAddInNative::SetPropVal(const long lPropNum, tVariant *varPropVal) 
{
    switch(lPropNum) 
    {
        case ePropTraceLavel:
            // 1С может передать значение как I4 (long)
            if (TV_VT(varPropVal) != VTYPE_I4 && TV_VT(varPropVal) != VTYPE_UI1) 
                return false;
            m_uiTraceLavel = (uint8_t)TV_I4(varPropVal);
            break;
        case ePropPort:
            if (TV_VT(varPropVal) != VTYPE_I4) 
                return false;
            m_uiPort = (uint16_t)TV_I4(varPropVal);
            break;
        // ... другие свойства
        default:
            return false;
    }
    return true;
}

Проблема «обрезанных» строк и особенности wchar_t

Одной из самых коварных проблем является работа со строками. Как было замечено в обсуждении, строка может «стать в два раза короче» при передаче в 1С. Выясним причину этого явления. Платформа 1С внутри себя использует кодировку UTF-16, где каждый символ занимает 2 байта. В Windows (компилятор MSVC) тип wchar_t также занимает 2 байта, что обеспечивает совместимость.

Однако в Linux-системах (компилятор GCC) тип wchar_t обычно занимает 4 байта (UTF-32). Если ваша компонента скомпилирована под Linux или вы используете специфические настройки компилятора, передача указателя wchar_t* приведет к тому, что 1С, ожидая 2 байта на символ, увидит нулевой байт (часть 4-байтового символа) и воспримет его как конец строки. Именно поэтому строка визуально «обрезается».

Рекомендация: Для кроссплатформенной разработки всегда используйте тип char16_t и строки std::u16string. Это гарантирует, что размер символа всегда будет равен 2 байтам, независимо от операционной системы.

Управление памятью через IMemoryManager

Разберем еще один критический аспект — возвращение строковых значений в 1С через GetPropVal. Посмотрим на типичную ошибку в реализации wstring_to_p (или аналогичных функций):


// ОПАСНО! Нельзя просто отдавать указатель на внутреннюю память C++
case ePropIP:
    TV_VT(pvarPropVal) = VTYPE_PWSTR;
    pvarPropVal->pwstrVal = (WCHAR_T*)m_sIP.c_str(); 
    pvarPropVal->wstrLen = m_sIP.length();
    break;

Почему это приведет к падению 1С? Платформа, получив значение типа VTYPE_PWSTR, считает себя владельцем этой памяти и попытается удалить её своим менеджером памяти после использования. Если память была выделена стандартными средствами C++ (внутри std::wstring), возникнет конфликт менеджеров памяти (Access Violation).

Рассмотрим правильный алгоритм выделения памяти для 1С:

  1. Используйте объект IMemoryManager, который передается компоненте в методе Init.
  2. Выделите буфер нужного размера через AllocMemory.
  3. Скопируйте данные в этот буфер.
  4. Передайте указатель на этот буфер в tVariant.

Пример корректной функции для передачи строки:


void CAddInNative::SetWStringToVariant(tVariant* dest, const std::wstring& source)
{
    unsigned int len = source.length();
    // Выделяем память через менеджер 1С
    if (m_iMemory->AllocMemory((void**)&dest->pwstrVal, (len + 1) * sizeof(WCHAR_T))) 
    {
        memcpy(dest->pwstrVal, source.c_str(), len * sizeof(WCHAR_T));
        dest->pwstrVal[len] = 0; // Терминирующий ноль
        dest->wstrLen = len;
        dest->vt = VTYPE_PWSTR;
    }
}

Сериализация данных в JSON на С++

В сообщениях форума активно обсуждается сложность работы с JSON в C++ по сравнению с C#. Действительно, в стандартной библиотеке C++ нет встроенного сериализатора, подобного System.Text.Json. Однако писать сборку строки JSON вручную — трудоемкий и небезопасный процесс.

Проанализируем современные подходы. Для работы с JSON в Native API лучше всего использовать проверенные библиотеки, такие как nlohmann/json. Это библиотека типа «header-only», которую достаточно просто подключить к проекту.

Рассмотрим пример того, как легко можно сформировать JSON объект:


#include "nlohmann/json.hpp"

using json = nlohmann::json;

std::string CreateJsonResponse() 
{
    json j;
    j["TraceLevel"] = m_uiTraceLavel;
    j["Port"] = m_uiPort;
    j["IP"] = "192.168.0.255";
    
    return j.dump(); // Возвращает std::string с готовым JSON
}

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

Итоги и рекомендации

Подводя итог, выделим основные правила создания надежной внешней компоненты:

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

← На главную