是否可以使用 C++Builder 和 CMake 创建 Python 模块?

Is it possible to use C++Builder and CMake to create Python modules?

C++Builder 的 32 位编译器 bcc32 默认使用 cdecl 调用约定创建共享库,在导出函数前加上下划线前缀, 例如'_functionName'。 Visual studio,另一方面,不要为导出的函数添加前缀。

Python,当导入 pyd 模块时,需要一个名为 PyInitialize_modulename 的函数。由于 bcc32 在此函数前加上下划线,将其呈现为 _PyInitialize_modulename,Python 将无法导入 bcc32 创建的模块。

使用 CMake,有谁知道如何在 compiling/creating pyd 模块时将模块定义文件 .def 添加到 'unprefixed' 中作为前缀函数的别名?

更新。 回应 Lebeau 先生在下面的回答(作为评论); 这个问题不是关于 CTypes,而是关于 SWIG。 CTypes 允许一种简单的方法来包装 C/C++ DLL,但主要处理 C 结构和 POD 数据。

另一方面,

Swig 允许客户端在 Python 中获得面向 object 的 object,类似于从 C++ DLL 导出的那些。 Swig 可以在处理 C++ 时执行此操作 headers。

Swig 确实创建了一个 C++ 文件,该文件被编译为 .pyd 文件(如前所述,本质上是一个 DLL)。 Python 在尝试将其作为模块加载时查找的第一个导出函数是 Pyinit_MyModule (Python 3)。如前所述,使用 C++ Builder 时,此函数导出为 _Pyinit_MyModule。问题是导出此函数的是 Swig,作为 Swig 的客户端,我不能更改此函数的调用约定 (afaik)。

我相信我最初认为 Python 需要 __stdcall 这个功能是错误的,因为默认情况下 VS 使用 cdecl,但没有添加 '_',它工作正常。

但是,将编译器标志设置为抑制下划线也不起作用,因为这样 Python 导入库中的某些函数将变得不可见,并成为未解析的外部函数。所以也许这个问题比乍看起来更复杂。但我想这与调用约定无关。

cdecl 只是每个 C++ 编译器使用的典型 default 调用约定。如果您想改用 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. Loading dynamic link libraries

ctypes exports the cdll, and on Windows windll and oledll objects, for loading dynamic link libraries.

You load libraries by accessing them as attributes of these objects. cdll loads libraries which export functions using the standard cdecl calling convention, while windll libraries call functions using the stdcall calling convention. oledll also uses the stdcall calling convention, and assumes the functions return a Windows HRESULT error code. The error code is used to automatically raise an OSError exception when the function call fails.

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

Is a *.pyd file the same as a DLL?

Yes, .pyd files are dll’s, but there are a few differences. If you have a DLL named foo.pyd, then it must have a function PyInit_foo(). You can then write Python “import foo”, and Python will search for foo.pyd (as well as foo.py, foo.pyc) and if it finds it, will attempt to call PyInit_foo() to initialize it. You do not link your .exe with foo.lib, as that would cause Windows to require the DLL to be present.

经过一些尝试,我可以说可以使用 C++ Builder 编译器、swig 和 CMake 创建 Python 扩展。

下面简要概述了我用来将简单的 C++ class (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) 在 Python3 和 Python37 dll 上使用 implip 创建导入库。使用 implibs 标志 -aa,以获得 C++ 构建器理解的导入库。将它们放在 Python37/libs 文件夹中。

2) 创建定义要包装的 class、函数和数据的 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)

atexplorer.def 文件应如下所示:

EXPORTS
   PyInit__atexplorer=_PyInit__atexplorer

4) 编译项目,并确认你得到一个生成的 "MyModule"PYTHON_wrap.cxx 和一个 MyModule.py CMake 构建文件夹中的文件。 "MyModule"PYTHON_wrap.cxx 文件包含为 Python 模块生成的 C++ 代码,.py 文件包含生成的 Python 代码。 Python 模块名为 atexplorer。

下图显示了 _atexplorer.pyd 模块中导出的内容。

将 .pyd 和 .py 文件复制到 Pythons 站点包文件夹。

如果成功,您将能够导入 Python 中的模块,例如

上面的屏幕截图显示了模块 atexplorer 是如何导入的。它有一个名为 'MyTest' 的函数,在执行时 returns 无论它的参数是什么。

它还有一个 class,ATObject,为此创建了一个名为 'a' 的对象。最后一行打印执行 class 成员函数 'getTypeName()' 的输出,它只是 returns,TYPENAME NOT IMPLEMENTED.