Разработка внешних компонент (ВК) для 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;
}
Одной из самых коварных проблем является работа со строками. Как было замечено в обсуждении, строка может «стать в два раза короче» при передаче в 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 байтам, независимо от операционной системы.
Разберем еще один критический аспект — возвращение строковых значений в 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С:
IMemoryManager, который передается компоненте в методе Init.AllocMemory.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 в 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
}
Если же вы принципиально не хотите использовать внешние библиотеки, как автор темы, вам придется самостоятельно экранировать спецсимволы в строках и следить за форматом чисел, что значительно увеличивает вероятность ошибок в протоколе обмена.
Подводя итог, выделим основные правила создания надежной внешней компоненты:
SetPropVal проверяйте тип входящего tVariant через TV_VT, а не сравнивайте значение с константами типов.IMemoryManager::AllocMemory. Никогда не передавайте указатели на локальные или статические буферы.char16_t вместо wchar_t для работы с UTF-16.Соблюдение этих принципов позволит вам создавать стабильные и быстрые расширения для 1С, которые будут корректно работать в любых сценариях эксплуатации.