Оглавление
- Введение
- Настройка IDE
- Настройка шаблона для работы с Python
- Краткое описание структуры и методов шаблона.
- Использование метода PyRun_SimpleString
- Использование метода PyRun_String
- Использование компоненты на основе шаблона в 1С
- Отладка и обработка ошибок.
- Заключение
Введение
В практике разработки 1С часто возникают задачи, которые проще реализовать на Python благодаря богатой экосистеме библиотек. В этой статье я покажу, как создать универсальный NativeAPI-компонент для 1С, который позволяет выполнять произвольные Python-скрипты. Этот подход особенно полезен для задач, которые сложно реализовать средствами 1С, таких как работа с MinIO S3, сложные математические вычисления или интеграция со специализированными библиотеками. Данная статья это пример использования шаблона для NativeApi-компоненты 1С от Infactum. Скачать шаблон можно здесь. Мы создадим компоненту для вызова произвольного скрипта на Python. Создание компоненты то технологии NativeApi подразумевает программирование на C++ и предложенный шаблон значительно упрощает этот процесс. Разработка велась на основе статьи Как вызвать скрипты Python из 1С по технологии NativeApi. Рекомендую ознакомиться как с самой статьей, так и с материалами, на которые она ссылается. Здесь я постарался свести к минимуму программирование на С++ и более подробно осветить некоторые вопросы, с которыми столкнется новичок при попытке разработки собственной компоненты. Цель — продемонстрировать возможность быстрой разработки NativeApi-компоненты для запуска произвольного python скрипта без глубоких знаний C++ и Python.
Настройка IDE
Разрабатывать компоненту будем в IDE CLion. Несколько слов о настройке CLion для тех, кто не занимается программированием на C++. CLion использует внешний компилятор и сборщик, поэтому их необходимо установить. Можно использовать Microsoft Visual Studio Build Tools.
Скачать Microsoft Visual Studio Build Tools можно здесь. Называется "Инструменты сборки для Visual Studio 2022". Находится в разделе "Инструменты для Visual Studio"
Достаточно установить: "Средства CMake C++ для Windows" и "MSVC" как на скриншоте:
В настройках CLion (File -> Settings -> Build, Execution, Deployment -> Toolchains) нужно указать в поле "Toolset" путь к установленному BuildTools.
Так как мы собираемся разрабатывать компоненту для работы с Python-скриптами, нам понадобится установленный Python. В настройках CLion (File -> Settings -> Build, Execution, Deployment -> Python Interpreter) также необходимо добавить Python interpreter.
В основном это всё. Шаблон нужно распаковать, открыть в CLion и запустить сборку. Если всё настроено верно, сборка пройдёт без ошибок, и вы получите в проекте, в папках «cmake-build-release», «cmake-build-debug» или «cmake-build-release-visual-studio», файл «SampleAddIn.dll». Это и есть файл компоненты, который будет подключен в 1С.
Настройка шаблона для работы с Python
Теперь приступим к созданию самой компоненты. Нам потребуется отредактировать всего три файла:
- CMakeLists.txt (в нём мы укажем сборщику, что будем использовать Python)
- SampleAddIn.cpp — файл с нашими функциями
- SampleAddIn.h — соответствующий заголовочный файл
Как описано в статье, добавляем в CMakeLists.txt после строки
add_library(${TARGET} SHARED ${SOURCES}):
find_package(Python3 3.13.2 REQUIRED COMPONENTS Development)
if (Python3_FOUND)
message("Python include directory: " ${Python3_INCLUDE_DIRS})
include_directories(${Python3_INCLUDE_DIRS})
target_link_libraries(${TARGET} ${Python3_LIBRARIES})
endif (Python3_FOUND)
где Python3 3.13.2 соответствует используемой версии Python.
В заголовочный файл SampleAddIn.h добавляем:
#include "Python.h"
— это библиотеки Python, которые мы будем использовать в проекте.
Краткое описание структуры и методов шаблона
В конструкторе класса SampleAddIn() вызываются методы для инициализации переменных и регистрации методов компоненты, которые реализуют интерфейс взаимодействия с платформой 1С.
Если необходимо передать данные в компоненту или прочитать их из 1С, это делается с помощью метода AddProperty, определенного в классе Component. Его сигнатура:
AddProperty(const std::wstring &alias, const std::wstring &alias_ru,
std::function<std::shared_ptr<void>(void)> getter,
std::function<void(variant_t&&)> setter)
Первые два параметра — псевдонимы свойства на английском и русском языках. Третий и четвертый параметры — это функции getter (для чтения значения из компоненты) и setter (для записи значения в переменную компоненты). Также можно указать переменную компоненты третьим параметром — в этом случае значение будет автоматически сохраняться в ней и передаваться в 1С. Пример из шаблона:
AddProperty(L"SampleProperty", L"ОбразецСвойства", sample_property);
Чтобы компонента выполнила какой-либо код (в нашем случае — код на Python), необходимо добавить метод. Регистрация методов осуществляется с помощью метода AddMethod, также определенного в классе Component:
AddMethod(const std::wstring &alias, const std::wstring &alias_ru, C *c,
T(C::*f)(Ts ...), std::map<long, variant_t> &&def_args)
Первые два параметра задают псевдонимы аналогично методу AddProperty, третий — указатель на вызывающий класс (this), четвертый — вызываемый метод, пятый — значения параметров по умолчанию.
Пример из шаблона:
AddMethod(L"Message", L"Сообщить", this, &SampleAddIn::message);
Еще один метод класса Component, который, возможно мы будем использовать для обработки ошибок — AddError :
AddError(unsigned short code, const std::string &src, const std::string &msg, bool throw_excp)
Это основной инструментарий для разработки нашей компоненты.
Таким образом, для выполнения Python-скрипта необходимо:
- Определить функцию C++, которая будет выполнять скрипт
- Зарегистрировать её методом AddMethod
Для выполнения скрипта можно использовать функции Python API: PyObject_CallMethod, PyObject_CallFunction, PyObject_Call и другие. Однако у этого подхода есть недостаток — на каждую строку Python-скрипта требуется несколько строк кода на C++, что может вызвать затруднения у разработчиков, не обладающих глубокими знаниями C++ и Python.
Мы воспользуемся методами PyRun_SimpleString или PyRun_String, которые позволяют:
- Написать скрипт на Python
- Проверить и отладить его на целевом компьютере
- Передать на исполнение в компоненту
Основное преимущество — возможность вносить изменения в Python-код без модификации компоненты.
В данной статье мы продемонстрируем использование обоих методов. Кроме того, перед выполнением любого кода с использованием Python C-API его необходимо инициализировать, а после выполнения — деинициализировать. Таким образом, нам нужно добавить две функции (executePyRun_SimpleString, executePyRun_String), два метода (initializePython, uninitializePython) и одно свойство (isInitialized). Ранее определенные в компоненте свойства и методы можно оставить или удалить — это не повлияет на работоспособность. Аналогично, вам решать, нужно ли переименовывать компоненту. В файле SampleAddIn.cpp определен метод extensionName():
std::string SampleAddIn::extensionName() {
return "Sample";
}
Это простой метод возращающий строку "Sample". Это имя, которое будет использоваться при обращении к компоненте из 1С. Обратите внимание на это имя, если посчитаете нужным переименовать компоненту.
В конструктор класса SampleAddIn() добавляем следующие строки:
AddProperty(L"isInitialized", L"ПитонВключен", [&]() { return std::make_shared<variant_t>(isInitialized); });
AddMethod(L"initializePython", L"ВключитьПитон", this, &SampleAddIn::initializePython);
AddMethod(L"uninitializePython", L"ВыключитьПитон", this, &SampleAddIn::uninitializePython);
AddMethod(L"executePyRun_SimpleString", L"ВыполнитьPyRun_SimpleString", this, &SampleAddIn::executePyRun_SimpleString);
AddMethod(L"executePyRun_String", L"ВыполнитьPyRun_String", this, &SampleAddIn::executePyRun_String);
В файле SampleAddIn.h объявляем наши функции и свойство, добавив после private: следующие строки:
variant_t isInitialized;
void initializePython();
void uninitializePython();
variant_t executePyRun_SimpleString(const variant_t &pyCode);
variant_t executePyRun_String(const variant_t &importCode, const variant_t &pyсode);
В файл SampleAddIn.cpp добавляем реализацию после последней фигурной скобки:
void SampleAddIn::initializePython() {
Py_Initialize();
if (!Py_IsInitialized()) {
isInitialized = false;
//Здесь будет обработка ошибки инициализации
}
isInitialized = true;
}
void SampleAddIn::uninitializePython() {
if (Py_IsInitialized()) {
Py_Finalize();
if (Py_IsInitialized()) {
//Здесь будет обработка ошибки деинициализации
}
}
isInitialized = false;
}
variant_t SampleAddIn::executePyRun_SimpleString(const variant_t &pyCode) {
// Здесь будет выполняться Python-скрипт методом PyRun_SimpleString()
variant_t returnValue = false; // Инициализируем возвращаемое значение как false
return returnValue;
}
variant_t SampleAddIn::executePyRun_String(const variant_t &importCode, const variant_t &pyCode) {
// Здесь будет выполняться Python-скрипт методом PyRun_String()
variant_t returnValue = false; // Инициализируем возвращаемое значение как false
return returnValue;
}
Методы initializePython() и uninitializePython() служат для инициализации и деинициализации интерпретатора Python. Подробнее об этом можно прочитать здесь.
Об обработке ошибок и деталях реализации методов executePyRun_String() и executePyRu_SimpleString() подробно рассказано в следующих главах.
Использование метода PyRun_SimpleString
PyRun_SimpleString() — это функция Python C API для выполнения Python-кода из строки. Это самый простой способ выполнения Python-кода из C/C++ приложений. С официальной документацией по методу можно ознакомится здесь.
Простейшая реализация будет выглядеть следующим образом:
variant_t SampleAddIn::executeString(const variant_t &pyCode) {
// Извлекаем строку с Python-кодом из variant_t
// variant_t может содержать разные типы, поэтому используем std::get для получения string
std::string codeStr = std::get<std::string>(pyCode);
// Получаем C-строку для передачи в Python API
// pyCode_ указывает на данные внутри codeStr, время жизни управляется codeStr
auto pyCode_ = codeStr.c_str();
// Выполняем Python-код с помощью PyRun_SimpleString
// Функция возвращает:
// 0 - при успешном выполнении
// -1 - при ошибке выполнения
auto result = PyRun_SimpleString(pyCode_);
// Проверяем результат выполнения
if (result == -1) {
// Вывод ошибки, если выполнение не удалось
}
// Возвращаем результат как variant_t
// result имеет тип int (0 или -1), который автоматически преобразуется в variant_t
return result;
}
Добавим функции для инициализации и деинициализации Python (аналогично ВключитьПитон и ВыключитьПитон из статьи) и метод для обработки ошибок — получим вполне рабочее решение.
Если скрипт выполняется из файла на терминале там, где будет запускаться компонента, то при передаче текста из файла скрипта в параметр метода Компонента.ВыполнитьСтроку(ТекстФайла) он также выполнится.
Основные недостатки этого подхода:
- Отсутствие возвращаемых значений — метод не возвращает ничего из скрипта Python, только 0 в случае успеха или -1 при ошибке. На практике даже заведомо некорректный скрипт может возвращать 0.
- Ограниченная диагностика — один из путей решения этой проблемы заключается в записи лога непосредственно из скрипта.
- Обработка исключений — в компоненте недостаточно проработана обработка исключений Python, что может приводить к неявным ошибкам выполнения.
Использование метода PyRun_String
PyRun_String() — это функция Python C API, которая предоставляет полный контроль над выполнением Python-кода из C/C++ приложений. В отличие от PyRun_SimpleString(), она позволяет работать с пространствами имен, получать результаты выполнения и гибко управлять ошибками, но он не такой простой и безотказный. С официальной документацией можно ознакомится здесь. Рассмотрим этот метод более подробно:
PyObject* PyRun_String(
const char *code, // Код для выполнения
int start, // Тип кода
PyObject *globals, // Словарь глобальных переменных
PyObject *locals // Словарь локальных переменных
);
Метод имеет три режима работы, определяемых вторым параметром:
- Py_file_input Многострочный код (как модуль)
- Py_eval_input Одно выражение (возвращает результат)
- Py_single_input Одна строка (как в интерактивном режиме)
В нашем случае будем использовать значение этого параметра заданное константой Py_file_input.
Простой пример использования этого метода:
variant_t SampleAddIn::execute_python_code() {
// Создание пространств имен
PyObject *globals = PyDict_New();
PyObject *locals = PyDict_New();
// Добавление builtins (обязательно!)
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
// Python-код для выполнения
const char* python_code =
"x = 10 + 20\n"
"y = x * 2\n"
"final_result = f'Sum: {x}, Double: {y}'"; // Просто строка вместо словаря
// Выполнение кода
PyObject *result = PyRun_String(
python_code,
Py_file_input, // Многострочный режим
globals,
locals
);
// Создаем и инициализируем возвращаемое значение
variant_t returnValue = false;
// Проверка результата
if (!result) {
// Здесь дожлна быть обработка ошибки
returnValue = std::string(u8"Ошибка выполнения Python");
} else {
PyObject *final_result = PyDict_GetItemString(locals, "final_result");
if (final_result && PyUnicode_Check(final_result)) {
returnValue = std::string(PyUnicode_AsUTF8(final_result));
} else {
returnValue = std::string(u8"Результат не найден или не является строкой");
}
Py_DECREF(result);
}
// Очистка памяти
Py_DECREF(globals);
Py_DECREF(locals);
return returnValue;
}
Как видно, даже для самого простого случая, код становится значительно сложнее. Разберем некоторые ключевые моменты:
- Необходимо создать пространства имен, добавить в глобальное пространство имен builtins
- Результат извлекаем и переменой final_result и преобразуем в строку
В этом примере мы используем простейший код на Python, не требующий импорта библиотек. При необходимости такого импорта все становится еще сложнее.
Пример реализации функции executePyRun_String:
variant_t SampleAddIn::executePyRun_String(const variant_t &importCode, const variant_t &pyCode) {
variant_t returnValue = false; // Инициализируем возвращаемое значение как false
try {
// Извлекаем строки кода из variant_t параметров
std::string codeStr = std::get<std::string>(pyCode); // Основной код для выполнения
auto pyCode_ = codeStr.c_str(); // Получаем C-строку основного кода
std::string importStr = std::get<std::string>(importCode); // Код импорта библиотек
auto importCode_ = importStr.c_str(); // Получаем C-строку кода импорта
// Проверяем инициализацию интерпретатора Python
if (!Py_IsInitialized()) {
return std::string(u8"Интерпретатор Python не инициализирован");
}
// Создаем пространства имен для выполнения кода
PyObject *globals = PyDict_New(); // Глобальное пространство имен
PyObject *locals = PyDict_New(); // Локальное пространство имен
// Добавляем встроенные функции Python (обязательно для работы базовых функций)
PyDict_SetItemString(globals, "__builtins__", PyEval_GetBuiltins());
// Выполняем код импорта библиотек
// Используем globals как locals, чтобы импортированные модули были доступны в основном коде
PyObject *import_result = PyRun_String(
importCode_, // Код импорта (например: "import math, json")
Py_file_input, // Режим выполнения многострочного кода
globals, // Глобальное пространство имен
globals // Используем globals как locals для сохранения импортированных модулей
);
// Проверяем результат выполнения кода импорта
if (!import_result) {
// Если импорт не удался, выводим ошибку и очищаем состояние ошибки Python
returnValue = std::string(u8"Ошибка импорта библиотек Python");
// Py_XDECREF безопасно уменьшает счетчик ссылок, даже если указатель NULL
Py_XDECREF(import_result);
} else {
// Импорт успешен, уменьшаем счетчик ссылок объекта результата
Py_DECREF(import_result);
// Выполняем основной код Python
PyObject *result = PyRun_String(
pyCode_, // Основной код для выполнения
Py_file_input, // Режим выполнения многострочного кода
globals, // Глобальное пространство (с импортированными модулями)
locals // Локальное пространство для хранения результатов
);
// Проверяем результат выполнения основного кода
if (result) {
// Код выполнен успешно, ищем переменную result_dict в локальном пространстве
PyObject *resultObj = PyDict_GetItemString(locals, "result_str");
if (resultObj) {
// Переменная result_str найдена, преобразуем ее в C++ тип
if (PyUnicode_Check(resultObj)) {
// Если результат - строка Unicode
returnValue = std::string(PyUnicode_AsUTF8(resultObj));
}
else {
// Неподдерживаемый тип - возвращаем строковое представление
PyObject *str_repr = PyObject_Str(resultObj);
if (str_repr) {
returnValue = std::string(PyUnicode_AsUTF8(str_repr));
Py_DECREF(str_repr);
} else {
returnValue = std::string(u8"Неподдерживаемый тип результата");
}
}
} else {
// Переменная result_str не найдена в локальном пространстве
returnValue = std::string(u8"Переменная 'result_str' не найдена в Python-коде");
Py_XDECREF(keys);
}
// Уменьшаем счетчик ссылок объекта результата выполнения
Py_DECREF(result);
} else {
// Ошибка выполнения основного кода Python
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback); // Получаем информацию об ошибке
std::string error_msg = u8"Ошибка выполнения Python кода: ";
if (value) {
PyObject *str = PyObject_Str(value);
error_msg += PyUnicode_AsUTF8(str);
Py_XDECREF(str);
}
returnValue = error_msg;
// Очищаем объекты ошибки
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
// Очистка памяти: уменьшаем счетчики ссылок созданных объектов
Py_DECREF(globals);
Py_DECREF(locals);
}
catch (const std::bad_variant_access&) {
// Ошибка извлечения строки из variant_t - неверный тип параметра
returnValue = std::string(u8"Неверный тип параметра - ожидались строки");
}
catch (const std::exception& e) {
// Обработка стандартных исключений C++
returnValue = std::string(u8"Ошибка C++: ") + e.what();
}
catch (...) {
// Обработка любых других исключений
returnValue = std::string(u8"Неизвестная ошибка при выполнении Python кода");
}
return returnValue;
}
Обратите внимание на то, что импорт библиотек осуществляется отдельным вызовом функции PyRun_String. При этом используем globals как locals для сохранения импортированных модулей. Для вывода значения из Python-скрипта, необходимо, чтобы в скрипте была переменная строкового типа "result_str" и ей было присвоено выводимое значение. Например, выполнение такого Python кода:
import math
result_str = math.factorial(10)
В 1с вызов будет выглядеть так:
Результат = Компонента.ВыполнитьPyRun_String("import math", "result_st = math.factorial(10)");
Использование в Python-скрипте переменной с определённым именем, заданным в компоненте, накладывает определённые ограничения на скрипт. Уже нельзя сказать, что мы можем взять произвольный скрипт, передать его из 1С в компоненту и получить результат, не внося в скрипт никаких изменений и доработок. Конечно, можно усложнить код модуля: получить значения всех переменных из скрипта, упаковать их в JSON-строку и вернуть в 1С. Однако мы оставим эту задачу пытливому читателю, поскольку наша цель — максимальная простота!
Полный текст SampleAddIn.cpp:
#ifndef SAMPLEADDIN_H
#define SAMPLEADDIN_H
#include "Component.h"
#include "Python.h"
class SampleAddIn final : public Component {
public:
const char *Version = u8"1.0.0";
SampleAddIn();
private:
std::string extensionName() override;
variant_t isInitialized;
void initializePython();
void uninitializePython();
variant_t executeString(const variant_t &pyCode);
};
#endif
Реализация SampleAddIn.cpp:
cpp
#include "SampleAddIn.h"
std::string SampleAddIn::extensionName() {
return "Sample";
}
SampleAddIn::SampleAddIn() {
// Пример регистрации свойства
AddProperty(L"Version", L"ВерсияКомпоненты", [&]() {
auto s = std::string(Version);
return std::make_shared<variant_t>(std::move(s));
});
AddProperty(L"isInitialized", L"ПитонВключен", [&]() {
return std::make_shared<variant_t>(isInitialized);
});
// Регистрация методов
AddMethod(L"initializePython", L"ВключитьПитон", this, &SampleAddIn::initializePython);
AddMethod(L"uninitializePython", L"ВыключитьПитон", this, &SampleAddIn::uninitializePython);
AddMethod(L"executeString", L"ВыполнитьСтроку", this, &SampleAddIn::executeString);
}
variant_t SampleAddIn::executeString(const variant_t &pyCode) {
std::string codeStr = std::get<std::string>(pyCode);
auto pyCode_ = codeStr.c_str();
auto result = PyRun_SimpleString(pyCode_);
if (result == -1) {
AddError(ADDIN_E_INFO, extensionName(), u8"Ошибка выполнения скрипта.", false);
}
return result;
}
void SampleAddIn::initializePython() {
Py_Initialize();
if (!Py_IsInitialized()) {
isInitialized = false;
AddError(ADDIN_E_INFO, extensionName(), u8"Ошибка инициализации Python.", false);
}
isInitialized = true;
}
void SampleAddIn::uninitializePython() {
if (Py_IsInitialized()) {
Py_Finalize();
if (Py_IsInitialized()) {
AddError(ADDIN_E_INFO, extensionName(), u8"Ошибка выключения Python.", false);
}
}
isInitialized = false;
}
Использование компоненты на основе шаблона в 1С
Использование компоненты на сервере приложений, тонком, толстом и веб-клиенте имеет свои особенности. В основном мы рассмотрим применение на сервере, но также уделим внимание особенностям использования на тонком и толстом клиенте.
Возможно подключение внешней компоненты по пути, навигационной ссылке или из макета двоичных данных. Рассмотрим последний вариант.
Файл компоненты вместе с файлом MANIFEST.xml упаковываем в ZIP-архив. Примерное содержание MANIFEST.xml:
<?xml version="1.0" encoding="UTF-8"?>
<bundle xmlns="http://v8.1c.ru/8.2/addin/bundle">
<component os="Windows" path="SampleAddIn.dll" type="native" arch="x86_64"/>
</bundle>
Если используется другая операционная система (не Windows) или архитектура, то в файле MANIFEST.xml указываем соответствующие значения.
В 1С создаем макет с видом "Двоичные данные" и загружаем в него наш ZIP-архив.
На сервере не требуется установка компоненты — можно сразу подключать:
Удалось = ПодключитьВнешнююКомпоненту(ПутьКМакету, ИмяКомпоненты, ТипВнешнейКомпоненты.Native);
- ПутьКМакету — путь к макету, например: "ОбщийМакет.МакетДляВыполненияСкриптаНаПитоне"
- ИмяКомпоненты — произвольное символическое имя, которое будет использоваться при обращении к компоненте
- Третий параметр — тип компоненты, должен быть ТипВнешнейКомпоненты.Native
Существует также четвертый необязательный параметр: "ТипПодключения". Он принимает два значения:
- ТипПодключения.Изолированно
- ТипПодключения.НеИзолированно
По умолчанию на сервере используется ТипПодключения.Изолированно (если режим совместимости выше 8.3.20). При указании ТипПодключения.НеИзолированно на сервере компоненту не удастся подключить.
Режим "Изолированно" означает, что компонента будет выполняться в отдельном процессе, и в случае сбоя завершится только этот процесс. Подробнее об этом можно узнать в документации.
На тонком клиенте необходимо предварительно установить компоненту с помощью вызова:
НачатьУстановкуВнешнейКомпоненты(ОписаниеОповещения, ПутьКМакету);
Файл компоненты будет скопирован в каталог:
C:\Users\Имя_пользователя\AppData\Roaming\1C\1Cv82\ExtCompT\
Важно учитывать, что при необходимости пересобрать компоненту, этот каталог нужно предварительно очистить.
Отладка и обработка ошибок
Несмотря на простоту использования шаблона, может возникнуть ситуация, когда выполнение компоненты приведет к полному падению приложения 1С. Это будет происходить, если не отлавливать исключения. Если понятно, где может возникнуть исключительная ситуация, то выполняемый код можно обернуть в блок try-catch. Однако не всегда понятно, в какой именно строке кода возникнет критическая ситуация. Для отладки можно использовать два подхода: логирование в текстовый файл и отладку средствами IDE (в нашем случае — CLion).
Пример кода для логирования в файл. В начале файла SampleAddIn.cpp добавляем, чтобы подключить заголовочный файл для работы с библиотеками ввода - вывода:
#include <fstream>
В метод, который надо отладить код:
std::ofstream logFile(R"(C:\temp\log.txt)", std::ios::app);
logFile << "[INFO] Какая-то информация для вывода в файл" << std::endl;
logFile.close();
Этот код помещаем в метод, который необходимо отладить. Пример для метода executePyRu_SimpleString:
variant_t SampleAddIn::executePyRu_SimpleString(const variant_t &pyCode) {
std::ofstream logFile(R"(C:\temp\python_log.txt)", std::ios::app);
logFile << "=== ВЫПОЛНЕНИЕ PYTHON СКРИПТА ===" << std::endl;
logFile << "Время: " << GetCurrentDateTime() << std::endl;
std::string codeStr = std::get<std::string>(pyCode);
auto pyCode_ = codeStr.c_str();
logFile << "Код (" << codeStr.length() << " символов): " << codeStr << std::endl;
auto result = PyRun_SimpleString(pyCode_);
if (result == -1) {
logFile << "СТАТУС: ОШИБКА" << std::endl;
} else {
logFile << "СТАТУС: УСПЕХ" << std::endl;
}
logFile << "Результат: " << result << std::endl;
logFile << "=== ЗАВЕРШЕНО ===" << std::endl << std::endl;
logFile.close();
return result;
}
Использование отладки средствами CLion.
Прежде всего необходимо выполнить Debug-сборку компоненты. Файлы Debug-сборки будут находиться по другому пути, чем Release-сборки. Обычно это папка cmake-build-debug. Точный путь будет указан в консольном выводе CMake — там будет строка вида:
-- Build files have been written to: E:/C++/addin-python/cmake-build-debug
DLL-файл — это новый файл, и его необходимо обновить там, где он используется. Если используется установка компоненты из макета, нужно обновить макет и удалить старый файл по пути: C:\Users\Имя_пользователя\AppData\Roaming\1C\1cv8\ExtCompT.
В документации на ИТС рекомендуется поместить PDB-файл в C:\Users\Имя_пользователя\AppData\Roaming\1C\1cv8\ExtCompT, хотя в моем случае в этом не было необходимости.
Далее необходимо настроить профиль отладки: Run → Edit Configurations... В поле Target укажите ваш проект, в поле Executable — путь к тонкому клиенту (если используете его для запуска компоненты).
Установите точку останова на нужной строке в CLion. При запуске отладки должен запуститься тонкий клиент. Подключите компоненту, и выполнение должно остановиться в заданной точке.
Также можно подключиться к запущенному процессу. Для этого в CLion нажимаем: Run → Attach to Process (или CTRL - ALT - F5), выбираем процесс 1cv8c (тонкий клиент) или rphost (для сервера). Для того, чтобы подключиться к процессу rphost пришлось запустить службу сервера 1С от текущего пользователя.
Заключение
Разработанная NativeAPI компонента для выполнения Python-скриптов из 1С демонстрирует эффективный подход к интеграции современных технологий в устоявшиеся бизнес-процессы. Несмотря на кажущуюся сложность, использование шаблона Infactum значительно упрощает разработку, позволяя сосредоточиться на бизнес-логике, а не на низкоуровневых деталях взаимодействия между платформами.
Ключевые преимущества решения:
-
Мощность Python становится доступной непосредственно из 1С
-
Простота поддержки - Python-скрипты можно изменять без перекомпиляции компоненты
-
Производительность NativeAPI обеспечивает быстрое выполнение
-
Гибкость архитектуры позволяет адаптировать решение под различные задачи
Как показано в статье, даже для простых случаев код требует внимания к деталям: правильной инициализации Python, управлению памятью и обработке ошибок. Однако эти усилия окупаются возможностью использовать богатую экосистему Python-библиотек для анализа данных, машинного обучения, работы с облачными сервисами и других сложных задач непосредственно из 1С.
Предложенное решение открывает путь для создания мощных гибридных приложений, сочетающих устойчивость и бизнес-логику 1С с вычислительными возможностями Python, что особенно актуально в эпоху цифровой трансформации и развития технологий искусственного интеллекта.
Вступайте в нашу телеграмм-группу Инфостарт