Можно ли использовать C++Builder и CMake для создания модулей Python?


32-разрядный компилятор C++Builder, bcc32, по умолчанию создает общие библиотеки, используя соглашение о вызовах cdecl, добавляя префиксы экспортируемых функций с подчеркиванием, например, "_functionName". Visual studio, с другой стороны, не использует префиксы экспортируемых функций.

Python при импорте модуля pyd ожидает функцию с именем PyInitialize_modulename. Поскольку bcc32 префиксирует эту функцию с подчеркиванием, делая ее _PyInitialize_modulename, Python не сможет импортировать bcc32 создан модуль.

Используя CMake, кто-нибудь знает, как добавить файл определения модуля,.def, для псевдонима префиксной функции, к "нефиксированной", при компиляции / создании модуля pyd?

Обновление. в ответ на приведенный ниже Ответ г-на Лебо (в качестве комментария); Этот вопрос не под, Но глоток. CTypes позволяет простой способ обернуть DLL C/C++, но в основном имеет дело со структурами C и POD-данными.

Swig, с другой стороны, позволяет клиенту получить объект ориентированные объекты в Python, аналогичные тем, которые экспортируются из библиотеки DLL C++. Swig может сделать это, как он обрабатывает заголовки C++.

Swig создает файл C++, который компилируется в a .pyd файл (по сути DLL, как уже отмечалось). Первая экспортируемая функция, которую Python ищет при попытке загрузить ее как модуль,- этоPyinit_MyModule (Python 3). При использовании C++ Builder эта функция экспортируется как _Pyinit_MyModule, как уже упоминалось. Проблема в том, что именно Swig экспортирует это функция, и я не могу как клиент Swig изменить соглашение о вызове (afaik) для этой функции.

Я считаю, что мое первоначальное убеждение в том, что Python нужен _ _ stdcall для этих функций неверно, так как VS по умолчанию использует cdecl, но без добавления'_', и это работает нормально.

Однако установка флага компилятора для подавления подчеркиваний также не работает, так как тогда некоторые функции в библиотеках импорта Python становятся невидимыми и становятся неразрешенными внешними. Так возможно, эта проблема сложнее, чем может показаться на первый взгляд. Но я думаю, что это не имеет ничего общего с вызовом условностей.

2 2

2 ответа:

cdecl это просто типичноесоглашение о вызове по умолчанию , которое использует каждый компилятор C++. Если вы хотите использовать stdcall вместо этого, вы, конечно, можете сделать это (имеет смысл, почему Python ожидал бы stdcall, так как это то, что использует Win32 API).

В коде C++ модуля можно явно использовать __stdcall в объявлениях экспортируемых функций.

Или можно изменить соглашение о вызовах по умолчанию в параметрах проекта C++Builder, а затем опустить любое соглашение о вызовах в настройках проекта. объявление функции.

Теперь, чтобы ответить на ваш вопрос - при импорте модуля pyd, если модуль использует cdecl, используйте ctypes.cdll для вызова его функций. Если модуль использует stdcall, Используйте ctypes.windll вместо этого. Это описано в документации Python:

Https://docs.python.org/3/library/ctypes.html#loading-dynamic-link-libraries

16.16.1.1. Загрузка динамических библиотек ссылок

ctypes экспортирует объекты cdll, а также объекты Windows windll и oledll для загрузка динамических библиотек ссылок.

Вы загружаете библиотеки, обращаясь к ним как к атрибутам этих объектов. cdll загружает библиотеки, которые экспортируют функции, используя стандартное соглашение о вызове cdecl, в то время как библиотеки windll вызывают функции, используя соглашение о вызове stdcall . oledll также использует соглашение о вызове stdcall и предполагает, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического вызова исключения OSError при вызове функции неудачи.

Https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll

- это *.pyd файл такой же, как DLL?

Да, .pyd файлы являются dll-файлами, но есть несколько отличий. Если у вас есть DLL с именем foo.pyd, то он должен иметь функцию PyInit_foo(). Затем вы можете написать Python "import foo", и Python будет искать foo.pyd (а также foo.py фу.pyc) и если он его найдет, попытается вызвать PyInit_foo() для инициализации оно. Вы не связываете свои .ехе с Фу.lib, так как это приведет к тому, что Windows потребует наличия DLL.

После некоторой возни я могу сказать, что можно создавать расширения Python с помощью компилятора C++ Builder, swig и CMake.

Ниже приведен краткий обзор шагов, которые я использовал, чтобы обернуть простой класс C++ (ATObject) в Python37.

Заголовочный файл C++ выглядит следующим образом:

#ifndef atATObjectH
#define atATObjectH
#include <string>
//---------------------------------------------------------------------------
using std::string;
int MyTest(int r);

class ATObject
{
    public:
                                ATObject();
        virtual                 ~ATObject();
        virtual const string    getTypeName() const;
};
#endif

И источник

#pragma hdrstop
#include "core/atATObject.h"
//---------------------------------------------------------------------------
ATObject::ATObject()    {}
ATObject::~ATObject()    {}

const string ATObject::getTypeName() const
{
    return "TYPENAME NOT IMPLEMENTED";
}

int MyTest(int r)
{
    return r;
}

1) Создайте библиотеки импорта с помощью implip на Python3 и Python37 dll. используйте флаг implibs-aa, чтобы получить библиотеку импорта, понятную C++ builder. Положить их в папке Python37 / libs.

2) создайте файл интерфейса swig, который определяет, какой класс, функции и данные нужно обернуть. Файл интерфейса swig:
// atexplorer.i
%include "std_string.i"
%include "windows.i"

%module atexplorer
%{    
#include "atATObject.h"
%}

//Expose class ATObject to Python
%include "atATObject.h"

3) Создайте файлы CMake для проекта, используя SWIG_ADD_LIBRARY для создания .модуль pyd, например

SWIG_ADD_LIBRARY(atexplorer MODULE LANGUAGE python SOURCES
atexplorer.i ${ATAPI_ROOT}/source/core/atATObject.cpp )
SWIG_LINK_LIBRARIES (atexplorer 
   ${PYTHON_LIB_FOLDER}/Python3_CG.lib
   ${PYTHON_LIB_FOLDER}/Python37_CG.lib
)

И заставьте CMake пройти .def-файл в компоновщик, добавив:

set (CMAKE_MODULE_LINKER_FLAGS ${CMAKE_CURRENT_SOURCE_DIR}/atexplorer.def)

Атэксплорер.файл def должен выглядеть следующим образом:

EXPORTS
   PyInit__atexplorer=_PyInit__atexplorer

4) скомпилируйте проект и убедитесь, что вы получили сгенерированный "MyModule" PYTHON_wrap.cxx и a MyModule.py файл в папке сборки CMake. "MyModule" PYTHON_wrap.файл cxx содержит сгенерированный код C++ для модуля Python, а файл.py содержит сгенерированный код Python. Модуль Python называется atexplorer.

На рисунке ниже показано, что экспортируется в _atexplorer.модуль pyd.

Псевдоним функции PyInit

Копировать .pyd и файлы .py в папку Pythons site packages.

В случае успеха вы будете возможность импортировать модуль в Python, например

Введите описание изображения здесь

На снимке экрана выше показано, как импортируется модуль atexplorer. Он имеет функцию под названием "MyTest", которая возвращает любой свой аргумент, когда выполняется.

Он также имеет класс, ATObject, для которых создается объект с именем "а", является. Последняя строка выводит результат выполнения функции-члена класса ' getTypeName ()', которая просто возвращает, TYPENAME не реализовано.