Разработка внешних компонент для 1С по технологии Native API на языке C++ часто сопровождается рядом трудностей, особенно для начинающих разработчиков. Основные подводные камни обычно кроются в работе с памятью, типами данных, а также в кодировках при передаче кириллицы между различными системами, сетью и платформой 1С. Разберем типичные проблемы, возникающие при создании компоненты, которая обменивается данными по сети через сокеты, и детально проанализируем способы их решения.
Представим ситуацию: у нас есть консольный клиент на C++, который отправляет текстовое сообщение на русском языке через TCP-сокет, а внешняя компонента его принимает. Однако вместо русского текста в компоненту приходят непонятные символы. Выясним причину такого поведения.
Если мы используем стандартную консоль Windows для ввода текста, она может работать в устаревшей OEM-кодировке (например, CP866). Для быстрого исправления ситуации на стороне консольного клиента можно принудительно установить кодировку Windows-1251 перед вводом данных:
system("chcp 1251");
Это позволит консоли правильно считывать вводимые русские символы. Однако в современной разработке передавать данные по сети принято в универсальной кодировке UTF-8. Платформа 1С:Предприятие внутри себя оперирует строками в формате Unicode (UTF-16), для которых в C++ используется тип wchar_t.
Чтобы корректно преобразовать полученные по сети байты в формат, понятный платформе 1С, использование стандартной функции C mbstowcs не всегда оправдано, так как ее результат зависит от текущей локали операционной системы. В Windows гораздо надежнее использовать функцию WinAPI MultiByteToWideChar, явно указав кодировку (например, CP_UTF8 или CP_ACP). Это гарантирует, что кириллица будет преобразована в Unicode без искажений, независимо от региональных настроек компьютера.
Рассмотрим следующую ситуацию: мы пытаемся передать строку из кода 1С в свойство компоненты. В коде 1С мы пишем:
ОбъектКомпоненты.Ip = "127.0.0.1";
А в коде компоненты на C++ проверяем тип переданного значения:
if (TV_VT(varPropVal) != VTYPE_PSTR)
return false;
При отладке мы видим, что свойство равно пустой строке, а условие не выполняется. В чем же ошибка? Проблема заключается в том, что платформа 1С в современных версиях передает строки в формате Unicode. Тип переменной в структуре tVariant будет не VTYPE_PSTR (указатель на ANSI-строку), а VTYPE_PWSTR (указатель на Wide-строку), и само значение будет находиться в поле pwstrVal.
Рассмотрим правильный подход к извлечению строкового свойства. Нам необходимо проверять оба варианта типов строк и корректно выделять под них память:
case 2: // Индекс нашего свойства
if(TV_VT(var) == VTYPE_PSTR) {
delete[] Prop2;
size_t len = std::strlen(var->pstrVal);
Prop2 = new char[len + 1];
std::strncpy(Prop2, var->pstrVal, len + 1);
break;
} else if(TV_VT(var) == VTYPE_PWSTR) {
delete[] Prop2;
// Здесь необходимо реализовать функцию конвертации
// WCHAR_to_char(Prop2, var->pwstrVal);
// или использовать MultiByteToWideChar для обратного преобразования
break;
} else {
return false;
}
Важное примечание по работе с памятью: Если внешняя компонента должна вернуть строку обратно в 1С (через возвращаемое значение метода или чтение свойства), память под эту строку должна выделяться только через внутренний менеджер памяти 1С (используя метод AllocMemory интерфейса IMemoryManager). Использование new или malloc для возвращаемых в 1С строк приведет к аварийному завершению работы платформы, так как 1С попытается очистить эту память своими внутренними механизмами.
Иногда при написании логики обработки команд (например, команды закрытия порта) разработчики допускают синтаксические ошибки, специфичные для C/C++. Посмотрим на пример кода:
char msg[256];
// ... получение данных в msg ...
if (msg == "ClosePort") {
break;
}
Этот код никогда не прервет цикл. В C++ переменная msg представляет собой указатель на массив символов. Оператор == в данном случае сравнивает не содержимое строк, а адреса памяти. Естественно, адрес буфера msg и адрес строкового литерала "ClosePort" никогда не совпадут.
Для корректного сравнения Си-строк необходимо использовать специальные функции:
char*) используйте: strcmp(msg, "ClosePort") == 0wchar_t*) используйте: wcscmp(Data, L"ClosePort") == 0Также, в современном C++ рекомендуется использовать классы std::string или std::wstring. Для них оператор == перегружен и корректно сравнивает именно текстовое содержимое, что делает код более безопасным и читаемым.
При сборке компоненты под разные архитектуры (32/64 bit) можно столкнуться с тем, что свойства и методы на русском языке перестают работать в 1С. В отладчике имена свойств могут выглядеть как иероглифы вида Статус. При этом английские идентификаторы продолжают работать исправно.
Это классическая проблема кодировки исходных файлов. Внешние компоненты Native API требуют, чтобы русские имена методов задавались широкими строками (литералы с префиксом L, например, L"Статус"). Если исходный файл сохранен в кодировке UTF-8, но компилятор (например, MSVC в Visual Studio) интерпретирует его как Windows-1251 (ANSI), русские символы будут скомпилированы неправильно.
Рассмотрим шаги для решения этой проблемы в Visual Studio:
/utf-8. Этот флаг принудительно указывает компилятору MSVC читать исходные файлы и формировать строковые литералы в исполняемом файле в кодировке UTF-8.Применяя эти практики работы с кодировками, типами данных Native API и менеджером памяти 1С, вы сможете избежать большинства типовых ошибок и создать стабильно работающую внешнюю компоненту.