Как передать ASCII-строку и произвольные байты в COM-объект из 1С без искажения символов?

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

При интеграции 1С:Предприятия с различным торговым и промышленным оборудованием (весы, сканеры, фискальные регистраторы, RFID-считыватели) разработчики часто используют классический элемент управления MSCOMM32.ocx для прямой работы с последовательными COM-портами. Однако при отправке управляющих команд или пакетов данных очень часто возникает неприятная проблема: символы с кодами от 0 до 127 передаются корректно, а все байты со значениями выше 127 (от 128 до 255, то есть диапазон 0x80 – 0xFF) искажаются, превращаясь в знаки вопроса (байт 0x3F) или другие непредвиденные символы.

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

Выясняем причину: почему «ломаются» символы с кодами больше 127?

Для того чтобы понять природу проблемы, проанализируем, как 1С работает со строками и передает их во внешние COM-объекты. Внутреннее представление строковых данных в платформе 1С:Предприятие основано на стандарте Unicode (кодировка UTF-16), где каждый символ занимает 2 байта. В то же время старые COM-объекты, такие как MSCOMM32, изначально проектировались в эпоху доминирования однобайтовых кодировок (ANSI/ASCII).

Когда мы пытаемся передать строку во внешнее свойство КомПорт.Output, операционная система Windows и платформа 1С выполняют неявную конвертацию строки Unicode в однобайтовую кодировку ANSI (формат BSTR). Процесс конвертации происходит следующим образом:

  1. Символы первой половины таблицы ASCII (коды 0–127) в кодировках Unicode и ANSI совпадают. Они проходят через преобразование без изменений.
  2. Символы второй половины (коды 128–255) преобразуются на основе текущей системной локали (кодовой страницы Windows, установленной в операционной системе по умолчанию, например, Windows-1251 для русскоязычной Windows).
  3. Если конкретный символ Юникода не имеет прямого аналога в текущей кодовой странице ANSI, операционная система заменяет его дефолтным символом «неизвестного символа» — знаком вопроса (ASCII-код 63, или 0x3F в шестнадцатеричной системе). Именно это мы и видим на дампе принимающей стороны.

Попытка сформировать строку через конструкцию вида Символ(248) + Символ(85)... приводит к тому, что 1С создает строку Юникода с соответствующими кодовыми точками. При передаче этой строки в свойство Output компонента MSCOMM32 происходит трансляция в системную кодировку ANSI, ломая исходную последовательность байтов.

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

Часто у разработчиков возникает логичная идея: если строка искажается из-за конвертации кодировок, нужно передать данные в виде массива чисел (байтов). Однако простая передача стандартного объекта типа Массив в 1С вызывает ошибку времени выполнения.

Дело в том, что COM-объекты не умеют напрямую работать с внутренними типами данных 1С. Платформа передает стандартный массив как объект типа VT_DISPATCH (ссылка на интерфейс автоматизации). Элемент управления MSCOMM32 ожидает на входе либо строковый буфер, либо специализированный безопасный массив байтов — SAFEARRAY. Получив несовместимый тип данных, компонент аварийно завершает операцию с ошибкой несоответствия типов (Type Mismatch).

Решение №1: Использование скриптового интерпретатора MSScriptControl (VBScript)

Один из обходных путей, к которому часто прибегают разработчики, заключается в делегировании формирования строки интерпретатору VBScript. Поскольку VBScript работает напрямую со структурами ActiveX и корректно формирует строковые типы данных без посредничества механизмов конвертации 1С, этот способ позволяет получить требуемый результат.

Рассмотрим пример функции, реализующей этот подход:


Функция ВернутьСтрVBScript(Стр)
    // Инициализируем скриптовый движок
    sc = Новый COMОбъект("MSScriptControl.ScriptControl");
    sc.language = "VBscript";
    Команда = "";
    
    // Преобразуем каждый символ строки через системную функцию Chr на стороне VBScript
    Для н = 1 По СтрДлина(Стр) Цикл
        Команда = Команда + sc.eval("Chr(""" + КодСимвола(Стр, н) + """)");
    КонецЦикла;
    
    Возврат Команда;
КонецФункции

Этот метод действительно работает и позволяет обойти проблему искажения старших байтов. Однако мы настоятельно не рекомендуем использовать его в реальных коммерческих решениях по ряду критических причин:

  1. Проблема разрядности (32/64-бит): Объект MSScriptControl.ScriptControl является строго 32-битным компонентом. Если ваша база данных будет запущена под 64-битным клиентом 1С или этот код выполнится на 64-битном сервере приложений, система выдаст неустранимую ошибку «Класс не зарегистрирован».
  2. Низкая производительность: Вызов метода sc.eval() в цикле для каждого байта порождает огромные накладные расходы на инициализацию парсера и выполнение микроскриптов. Это недопустимо при интенсивном обмене данными.
  3. Устаревание технологии: Компонент MSScriptControl официально признан Microsoft устаревшим и во многих современных сборках Windows 10/11 заблокирован политиками безопасности по умолчанию.

Решение №2 (Оптимальное): Правильное использование COMSafeArray

Наиболее надежным, быстрым и штатным способом передачи бинарных данных в COM-объекты из платформы 1С является использование специализированного объекта COMSafeArray. Этот объект позволяет создать типизированный массив, который на системном уровне преобразуется в стандартный SAFEARRAY, понятный для любого внешнего COM-компонента.

Для передачи произвольных байтов без искажения нам необходимо инициализировать массив с типом VT_UI1 (Unsigned 1-byte integer — беззнаковое целое число размером 1 байт, соответствующее типу Byte в C++ и VB).

Разберем этот процесс по шагам на практическом примере:


// Шаг 1. Переводим COM-порт в режим работы с бинарными данными
// По умолчанию MSCOMM32 может ожидать текстовый режим (comInputModeText = 0)
// Нам необходимо принудительно установить бинарный режим (comInputModeBinary = 1)
КомПорт.InputMode = 1; 

// Шаг 2. Создаем обычный массив 1С и заполняем его необходимыми числовыми кодами байтов
МассивБайтов = Новый Массив;
МассивБайтов.Добавить(248); // f8 в hex-представлении
МассивБайтов.Добавить(85);  // 55
МассивБайтов.Добавить(206); // ce
МассивБайтов.Добавить(1);   // 01
МассивБайтов.Добавить(0);   // 00
МассивБайтов.Добавить(144); // 90
МассивБайтов.Добавить(144); // 90
МассивБайтов.Добавить(0);   // 00

// Шаг 3. Оборачиваем подготовленный массив в системный объект COMSafeArray.
// Вторым параметром передаем тип данных "VT_UI1", третьим — размерность массива.
РазмерМассива = МассивБайтов.Количество();
МассивCOM = Новый COMSafeArray(МассивБайтов, "VT_UI1", РазмерМассива);

// Шаг 4. Отправляем полученный массив напрямую в порт
// Данные уйдут в устройство байт-в-байт, минуя трансляции кодировок Windows
КомПорт.Output = МассивCOM;

Этот способ обладает абсолютной надежностью, работает на порядок быстрее любых внешних скриптов, не зависит от разрядности операционной системы (если сам COM-объект поддерживает работу в соответствующей среде) и гарантирует передачу байтов в исходном виде.

Решение №3: Преобразование через ADODB.Stream

Еще один профессиональный способ подготовки «чистых» бинарных данных из строки Юникода заключается в использовании системного объекта ADODB.Stream. Этот подход полезен, когда у вас уже есть готовая текстовая строка (например, сформированная xml-структура или JSON) и вам нужно быстро получить ее бинарный эквивалент в определенной кодовой странице (например, Windows-1251 или UTF-8) для отправки в COM-порт.

Проанализируем пример кода с использованием этого механизма:


Функция ПреобразоватьСтрокуВБинарныйДанные(ИсходнаяСтрока, НужнаяКодировка = "windows-1251")
    // Создаем поток ADODB
    Поток = Новый COMОбъект("ADODB.Stream");
    Поток.Type = 2; // Указываем тип потока: текстовый
    Поток.Charset = НужнаяКодировка;
    Поток.Open();
    
    // Записываем нашу строку в поток
    Поток.WriteText(ИсходнаяСтрока);
    
    // Переводим поток в бинарный режим для чтения сырых байтов
    Поток.Position = 0;
    Поток.Type = 1; // Переключаем тип на бинарный (Binary)
    
    // Считываем двоичные данные, которые возвращаются в формате SAFEARRAY (VT_UI1)
    ДвоичныеДанные = Поток.Read();
    Поток.Close();
    
    Возврат ДвоичныеДанные;
КонецФункции

Полученный из функции объект типа VT_UI1 (бинарные данные) можно напрямую присваивать свойству Output вашего COM-порта. Этот метод объединяет удобство работы со строками в 1С и надежность бинарной передачи информации.

Современные альтернативы устаревшему MSCOMM32

В заключение отметим, что использование компонента MSCOMM32.ocx в современных реалиях накладывает жесткие инфраструктурные ограничения. Данный компонент является наследием Visual Basic 6.0, работает исключительно в 32-битных процессах и требует обязательной регистрации в системном реестре Windows с правами администратора (через утилиту regsvr32), что часто вызывает проблемы при развертывании решений на новых рабочих местах.

Если вы проектируете систему «с нуля», рекомендуем рассмотреть более современные альтернативы:

  1. Использование классов .NET через специализированные обертки: Класс System.IO.Ports.SerialPort из платформы .NET Framework предоставляет мощный, кроссплатформенный и стабильный интерфейс для работы с портами, который легко интегрируется в 1С без регистрации внешних ActiveX-компонент.
  2. Использование внешних компонент Native API: Многие современные драйверы торгового оборудования и открытые библиотеки для 1С пишутся по технологии Native API. Они поддерживают прямую работу с объектами типа ДвоичныеДанные и БуферДвоичныхДанных, не зависят от разрядности клиента и могут беспрепятственно работать как на Windows, так и на Linux-системах.
← На главную