При интеграции сторонних алгоритмов в среду 1С:Предприятие разработчики часто сталкиваются с необходимостью использования готовых скриптов на языке JavaScript (JScript). Обычно это связано с криптографией, расчетом различных контрольных сумм (например, CRC8, CRC16) или работой со специфическим торговым оборудованием. Классическим подходом для реализации таких задач долгое время являлось использование COM-объекта MSScriptControl.ScriptControl.
Однако этот подход таит в себе множество подводных камней — от неочевидных синтаксических ошибок при передаче строк до критических проблем совместимости с современными 64-разрядными серверами и клиентами 1С. В рамках данной статьи мы подробно разберем, почему возникают ошибки при вызове JScript из 1С, как их исправить, в чем заключается главная опасность использования MSScriptControl, и как полностью переписать алгоритм расчета контрольной суммы CRC8 (дополнение до двух) на чистый и производительный код 1С.
Давайте подробно проанализируем, почему у автора темы возникли сбои на начальных этапах реализации.
1. Ошибка компиляции при вызове метода AddCode
В первом сообщении автор приводит ошибку компиляции Microsoft JScript: «Синтаксическая ошибка» при выполнении метода AddCode. Причин у этой проблемы несколько:
strHex = new String("0123456789ABCDEF")). При передаче этого кода в качестве строкового литерала 1С необходимо строго соблюдать правила экранирования — удваивать кавычки. В исходном примере кода автора кавычки были экранированы некорректно, что нарушило структуру строки еще на этапе компиляции в самом движке 1С.// Calculate 2's complement. Когда многострочный текст из 1С передается через COM-объект, символы перевода строк (ПС) могут некорректно интерпретироваться компонентой. В результате весь последующий исполняемый код «склеивается» в одну строку с комментарием //, превращаясь в неисполняемый текст. Это гарантированно вызывает синтаксическую ошибку компиляции JScript. Рекомендуется полностью избегать однострочных комментариев // в передаваемом коде и заменять их на блочные /* ... */ или удалять.2. Исключительная ситуация (0x80020006) при вызове метода Run
Код ошибки 0x80020006 расшифровывается как «Неизвестное имя» (DISP_E_UNKNOWNNAME). Автор попытался вызвать выполнение функции следующим образом:
Результат = ScrptCtrl.Run("CRC8("""+КодКарты+""");");
Это грубейшая ошибка использования метода Run. Данный метод устроен так: первым параметром передается исключительно имя вызываемой функции в виде строки, а все последующие аргументы функции передаются как отдельные параметры метода Run. Передавая в первый параметр всю конструкцию с аргументом и точкой с запятой, разработчик заставлял систему искать функцию с безумным именем CRC8("AABBCCDD");, которой, естественно, не существовало в контексте скрипта.
Правильный синтаксис вызова метода Run должен выглядеть так:
Результат = ScrptCtrl.Run("CRC8", КодКарты);
Автор темы обошел эту проблему, заменив метод Run на метод Eval:
Результат = ScrptCtrl.Eval("CRC8("""+КодКарты+""");");
Метод Eval действительно работает иначе — он принимает на вход единую строку, компилирует ее как выражение JScript «на лету» и возвращает результат. Несмотря на работоспособность данного костыля, использование Eval снижает производительность (так как выражение парсится заново при каждом вызове) и снижает безопасность кода.
Использование компоненты MSScriptControl.ScriptControl в современных реалиях разработки 1С — это мина замедленного действия. Давайте разберем по шагам, почему это так.
Библиотека msscript.ocx, которая обеспечивает работу этого COM-объекта, является устаревшим 32-разрядным (x86) компонентом Active X. Она поставлялась в составе операционных систем Windows и не имеет официальной 64-разрядной версии от Microsoft.
Как только ваша база данных 1С будет запущена на 64-битном сервере приложений (процессы rphost.exe) или пользователи начнут работать в 64-битных версиях тонкого или толстого клиента, конструктор:
ScrptCtrl = Новый COMОбъект("MSScriptControl.ScriptControl");
вызовет неустранимую ошибку выполнения: «Класс не зарегистрирован» (0x80040154). Это связано с тем, что 64-битный процесс не может напрямую загрузить в свое адресное пространство 32-битные динамические библиотеки (DLL).
Существуют обходные пути для решения этой проблемы, но все они усложняют архитектуру:
MSScriptControl внутри системного 32-битного процесса-суррогата dllhost.exe. Это требует ручной настройки реестра на сервере, прав локального администратора и снижает скорость работы из-за межпроцессного маршалинга данных.tsc64.dll), что требует их дистрибуции и регистрации на всех рабочих станциях или серверах.Из этого напрашивается очевидный вывод: если алгоритм можно переписать на встроенном языке 1С, его необходимо переписать на 1С.
Автор темы заимствовал алгоритм с популярного зарубежного ресурса для расчета контрольных сумм. Самым сложным фрагментом для переноса на язык 1С автору показалась следующая строка:
result = (~(result & 0xff) + 1) & 0xFF;
Давайте подробно проанализируем, что здесь происходит с точки зрения побитовой математики:
result & 0xff — операция побитового «И» (AND) с маской 0xFF (255 в десятичной системе). Она отсекает все старшие биты числа, оставляя только младшие 8 бит (1 байт).~(...) — побитовое отрицание «НЕ» (NOT). Оно инвертирует все биты числа. В компьютерной арифметике для знаковых чисел операция ~x эквивалентна математическому действию -x - 1.+ 1 — прибавление единицы к инвертированному значению. Сочетание инверсии бит и прибавления единицы (~x + 1) — это классический способ получения отрицательного числа в двоичном коде (представление отрицательных чисел методом дополнения до двух). Математически это превращает число x в -x.& 0xFF — повторное наложение маски, чтобы гарантированно удержать результат в рамках одного байта (диапазон от 0 до 255).Вся эта побитовая магия для 8-разрядного беззнакового целого числа сводится к простой арифметической формуле. Дополнение до двух для числа, ограниченного одним байтом, эквивалентно вычитанию этого числа из 256. Таким образом, на языке 1С формула принимает изящный вид:
Результат = (256 - (Сумма % 256)) % 256;
Здесь оператор остатка от деления % 256 заменяет операцию побитового «И» с маской 0xFF. Данная формула работает со 100% точностью и выдает абсолютно идентичные результаты для любых входных данных без использования сложных низкоуровневых функций.
Ниже представлен протестированный программный код на встроенном языке 1С, который полностью заменяет JScript-скрипт автора. Он производит побайтовый разбор HEX-строки (например, кода карты), суммирует значения байтов, рассчитывает дополнение до двух и возвращает результат в виде двухсимвольной шестнадцатеричной строки.
// Вычисляет 8-битное дополнение до двух (2's complement checksum) для HEX-строки.
// Полный аналог алгоритма с сайта planetimming (входной параметр КодКарты - например, "AABBCCDD")
Функция ВычислитьCRC8_Двухдополнение(Знач КодКарты) Экспорт
// Очищаем входную строку от пробелов
ЧистаяСтрока = СтрЗаменить(КодКарты, " ", "");
// Проверяем длину hex-строки на четность
Если СтрДлина(ЧистаяСтрока) % 2 <> 0 Тогда
ВызватьИсключение "Некорректная длина HEX-строки (должно быть четное количество символов)";
КонецЕсли;
СуммаБайт = 0;
Шаг = 2;
// Побайтово разбираем hex-строку и суммируем десятичные значения байтов
Для Позиция = 1 По СтрДлина(ЧистаяСтрока) / Шаг Цикл
БайтСтрокой = Сред(ЧистаяСтрока, (Позиция - 1) * Шаг + 1, Шаг);
// Преобразуем HEX-байт в десятичное число
БайтЧислом = HexВДесятичное(БайтСтрокой);
Если БайтЧислом = -1 Тогда
Возврат "Ошибка: обнаружены некорректные символы в HEX-строке";
КонецЕсли;
СуммаБайт = СуммаБайт + БайтЧислом;
КонецЦикла;
// Вычисляем дополнение до двух (2's complement) для 8 бит с помощью простой арифметики
РезультатЧислом = (256 - (СуммаБайт % 256)) % 256;
// Преобразуем результат обратно в HEX-представление
Возврат ДесятичноеВHex(РезультатЧислом);
КонецФункции
// Вспомогательная функция перевода HEX-строки (длиной до 2 символов) в десятичное число
Функция HexВДесятичное(Знач СтрHEX)
АлфавитHEX = "0123456789ABCDEF";
СтрHEX = ВРег(СтрHEX);
Результат = 0;
Для НомерСимвола = 1 По СтрДлина(СтрHEX) Цикл
Символ = Сред(СтрHEX, НомерСимвола, 1);
ПозицияВАлфавите = Найти(АлфавитHEX, Символ);
Если ПозицияВАлфавите = 0 Тогда
Возврат -1; // Возвращаем признак ошибки, если символ не входит в шестнадцатеричный алфавит
КонецЕсли;
Результат = Результат * 16 + (ПозицияВАлфавите - 1);
КонецЦикла;
Возврат Результат;
КонецФункции
// Вспомогательная функция перевода десятичного числа (0-255) в 2-значный HEX
Функция ДесятичноеВHex(Знач Число)
АлфавитHEX = "0123456789ABCDEF";
СтаршийРазряд = Цел(Число / 16);
МладшийРазряд = Число % 16;
СимволСтарший = Сред(АлфавитHEX, СтаршийРазряд + 1, 1);
СимволМладший = Сред(АлфавитHEX, МладшийРазряд + 1, 1);
Возврат СимволСтарший + СимволМладший;
КонецФункции
Внедрение данного платформенного решения дает разработчику весомые преимущества:
СуммаБайт, РезультатЧислом непосредственно через стандартный отладчик 1С, заходя внутрь функций, что невозможно при работе с «черным ящиком» в виде COM-объекта JScript.