Python в 1С: создаем NativeAPI-компоненту для выполнения скриптов без глубоких знаний C++

20.10.25

Разработка - Разработка внешних компонент

Создать NativeAPI-компоненту для Python-скриптов? Это просто!
 
Оглавление
  1. Введение
  2. Настройка IDE
  3. Настройка шаблона для работы с Python
  4. Краткое описание структуры и методов шаблона.
  5. Использование метода PyRun_SimpleString
  6. Использование метода PyRun_String
  7. Использование компоненты на основе шаблона в 1С
  8. Отладка и обработка ошибок.
  9. Заключение

 

Введение

В практике разработки 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" как на скриншоте:

 
 Скриншот "Visual Studio Build Tools"

 

 

В настройках CLion (File -> Settings -> Build, Execution, Deployment -> Toolchains) нужно указать в поле "Toolset" путь к установленному BuildTools.

 
 Скриншот "CLion"

 

Так как мы собираемся разрабатывать компоненту для работы с Python-скриптами, нам понадобится установленный Python. В настройках CLion (File -> Settings -> Build, Execution, Deployment -> Python Interpreter) также необходимо добавить Python interpreter.

 
 Скриншот "CLion"

 

В основном это всё. Шаблон нужно распаковать, открыть в 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-скрипта необходимо:

  1. Определить функцию C++, которая будет выполнять скрипт
  2. Зарегистрировать её методом 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 (аналогично ВключитьПитон и ВыключитьПитон из статьи) и метод для обработки ошибок — получим вполне рабочее решение.

Если скрипт выполняется из файла на терминале там, где будет запускаться компонента, то при передаче текста из файла скрипта в параметр метода Компонента.ВыполнитьСтроку(ТекстФайла) он также выполнится.

Основные недостатки этого подхода:

  1. Отсутствие возвращаемых значений — метод не возвращает ничего из скрипта Python, только 0 в случае успеха или -1 при ошибке. На практике даже заведомо некорректный скрипт может возвращать 0.
  2. Ограниченная диагностика — один из путей решения этой проблемы заключается в записи лога непосредственно из скрипта.
  3. Обработка исключений — в компоненте недостаточно проработана обработка исключений 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, что особенно актуально в эпоху цифровой трансформации и развития технологий искусственного интеллекта.

Вступайте в нашу телеграмм-группу Инфостарт

См. также

Разработка внешних компонент Программист 1С v8.3 1C:Бухгалтерия 1С:Управление нашей фирмой 1.6 1С:Бухгалтерия 3.0 Платные (руб)

Внешняя компонента позволяет работать c TWAIN-совместимым оборудованием (сканерами, камерами) . Полностью совместима со стандартной TWAIN-компонентой из БСП и может применяться как ее замена без изменения вызовов, при этом может работать с 64-разрядной платформой, а так же имеет расширенную функциональность, например, сохранение результата непосредственно в PDF без использования сторонних утилит. Прекрасно работает на сервере, тонком клиенте и веб-клиенте (проверена работа в браузерах Google Chrome, Mozilla Firefox и Microsoft Internet Explorer).

5000 руб.

12.05.2020    31338    142    100    

98

Разработка внешних компонент Телефония, SIP Программист 1С v8.3 1C:Бухгалтерия Россия Платные (руб)

Внешняя компонента выполнена по технологии Native API для 1С 8.х, обеспечивает доступ к программным АТС Asterisk (FreePBX, Elastix) через AMI интерфейс. Через него можно управлять многими функциями Asterisk (определение номеров, перевод звонков, набор телефона и т. д.)

5160 руб.

04.05.2018    49462    126    68    

70

Разработка внешних компонент Системный администратор Программист Стажер Бесплатно (free)

Библиотека для работы с базами SQLite из 1С на основе внешней компоненты. Для Linux и Windows, бесплатно и с открытым исходным кодом!

14.01.2025    4597    bayselonarrend    14    

52

Разработка внешних компонент Программист 1С v8.3 Россия Бесплатно (free)

В статье описывается приложение-конструктор внешних компонент (native API). Конструктор упрощает процесс разработки за счет удобного добавления всех нужных функций и процедур в графическом режиме, с указанием их параметров и типов параметров. На выходе приложение генерирует готовый код на С++ и Rust и позволяет сразу приступить к реализации, без настройки API компоненты вручную.

04.12.2024    8531    kovalevdmv    28    

83

Разработка внешних компонент Программист 1С v8.3 Бесплатно (free)

А давайте запилим 8.3.26 до релиза, или оповещение с сервера...

19.02.2024    8784    starik-2005    38    

60

Разработка внешних компонент Механизмы платформы 1С Программист Стажер 1С v8.3 Бесплатно (free)

Некоторые практические аспекты создания внешних компонент на языке С++ для платформы 1С 8.3++.

26.01.2024    9817    starik-2005    40    

49

Инструментарий разработчика Разработка внешних компонент Программист 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Пример взаимодействия 1С с Apach Kafka посредством внешней компоненты, разработанной на основе официальной библиотеки librdkafka (the Apache Kafka C/C++ client library).

22.11.2023    6855    132    ivan1703    26    

43

Разработка внешних компонент Программист 1С v8.3 1C v8.2 Платные (руб)

Внешняя компонента, позволяющая посылать команды и получать ответы по GraphQL протоколу из 1С.Может быть использована при интеграции. В 1С работает на стороне "клиента".

4600 руб.

27.06.2023    4583    3    0    

5
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. SerVer1C 992 20.10.25 12:19 Сейчас в теме
Интересно, но: можно ли передавать параметры в скрипт и возвращать значение выполнения скрипта? Если нет, то чем вся эта затея отличается от КомандаСистемы ?
2. IlyaKuznetsov 20.10.25 14:44 Сейчас в теме
Да, можно! Метод PyRun_String() - дает доступ к переменным скрипта.
3. SerVer1C 992 20.10.25 15:13 Сейчас в теме
(2) я правильно понимаю, что скрипт summa(a, b) корректно вернёт мне результат суммирования в переменную 1с, если я на вход буду подавать переменные 1с с разными значениями ??
4. IlyaKuznetsov 20.10.25 16:32 Сейчас в теме
(3) Будет что- вроде такого: "Результат = Компонента.ВыполнитьPyRun_String("result_st = a+b", ЗначениеА, ЗначениеБ); При этом имена переменных (result_st, a, b) будут предопределены в компоненте. Можно:в той же компоненте result_st = a*b (вернет произведение), но нельзя в той же компоненте - "res = с+d". .
5. SerVer1C 992 20.10.25 16:56 Сейчас в теме
(4) Посмотрите публикацию, где НЕ нужно предопределять имена переменных в компоненте. Может, получится также сделать?
6. xlmel 20.10.25 18:25 Сейчас в теме
НачатьУстановкуВнешнейКомпоненты - на сервере не доступна, получается всем пользователям ставить Python?
Для отправки сообщения требуется регистрация/авторизация