cython 重用包装 C++ class

cython reuse wrapped C++ class

我想用 cython 包装一些 C++ class,但我的问题是我必须在另一个 class 中使用一些 class。我还没有找到其他人遇到同样的问题,甚至没有找到类似的问题,如果已经有人问过,我很抱歉...我做了一个已经很长的简约示例...

所以我有以下文件结构,其中 BuildAll.sh 运行 通过文件夹,如果它有一个 BuildAll.sh 文件,它会更深,如果一个文件夹有一个 setup.py 文件,它将构建它(setup.py 文件将查找每个 *.pyx 文件并构建它们) 现在出于虚拟目的 classAC 是用 C++ 编写的 ClassA,它包含一个 int 作为成员。 同样在 B 文件夹中,但是 classBC 持有 classAC 的一个实例作为成员,我认为这是问题所在,因为 classBC 不知道 classAC 定义。

.
├── BuildAll.sh
├── main.py
└── src
    ├── A
    │   ├── classAC.cpp
    │   ├── classAC.hpp
    │   ├── classAC.pxd
    │   ├── classA.pyx
    │   ├── __init__.py
    │   └── setup.py
    ├── B
    │   ├── classBC.cpp
    │   ├── classBC.hpp
    │   ├── classBC.pxd
    │   ├── classB.pyx
    │   ├── __init__.py
    │   └── setup.py
    ├── BuildAll.sh
    └── __init__.py

BuildAll.sh:

#!/bin/bash

# Go into every directory
for D in */
do
    cd $D
    # If dir contains a BuildAll.sh script, run it.
    if test -f "BuildAll.sh"; then 
        ./BuildAll.sh
    fi
    # If dir contains a setup.py file, then build it.
    # setup.py will look for every .pyx extension and build it automatically.
    if test -f "setup.py"; then
        python3 setup.py build_ext --inplace
    fi
    cd ..
done

setup.py:

from setuptools import Extension, setup
from Cython.Build import cythonize
import glob

PYXFILES = glob.glob("*.pyx")
EXTNAMES = [i[:-4] for i in PYXFILES]

ext_list = []
for i in range(len(PYXFILES)):
    ext_list.append(
        Extension(
            EXTNAMES[i],
            [PYXFILES[i]],
            extra_compile_args=["-O3"]
        )
    )

setup(
    ext_modules = cythonize(
        ext_list,
        language_level = 3,
        build_dir = 'build',
        annotate = True
    )
)

classAC.cpp:

#include "classAC.hpp"

classAC::classAC()
    : data_(0)
{}

int classAC::data()
{
    return data_;
}

classAC.hpp:

#ifndef CLASSAC_H
#define CLASSAC_H

class classAC
{
private:
    int data_;
public:
    // Constructors
    // Null construct
    classAC();

    // Destructor
    ~classAC() = default;

    int data();
};

#endif // CLASSAC_H

classAC.pxd:

cdef extern from "classAC.cpp":
    pass

cdef extern from "classAC.hpp":
    cdef cppclass classAC:
        classAC()
        int data()

classA.pyx:

# distutils: language = c++

from classAC cimport *

cdef class ClassA:

    cdef classAC COBJ
    def __cinit__(self):
        pass

    def __init__(self):
        self.COBJ = classAC()

    def getAdata(self):
        return self.COBJ.data()

classBC.cpp:

#include "classBC.hpp"

classBC::classBC()
    : aobj_(classAC())
{}

classBC.hpp:

#ifndef CLASSBC_H
#define CLASSBC_H

#include "classAC.hpp"

class classBC
{
    /* Base class for an fvMesh */
private:
    classAC aobj_;
public:
    // Constructors
    classBC();

    // Destructor
    ~classBC() = default;
};

#endif // CLASSBC_H

classBC.pxd:

cdef extern from "classBC.cpp":
    pass

cdef extern from "classBC.hpp":
    cdef cppclass classBC:
        classBC()

classB.pyx:

# distutils: language = c++
# distutils: include_dirs = ../A

from classBC cimport *

cdef class ClassB:
    """
    """
    cdef classBC COBJ
    def __cinit__(self):
        pass

    def __init__(self):
        self.COBJ = classBC()

如果我尝试 运行 我的 main.py:

#!/usr/bin/python3

from src.A.classA import ClassA
from src.B.classB import ClassB

a = ClassA()
print(a.getAdata())

b = ClassB()

我收到以下错误:

Traceback (most recent call last):
  File "./main.py", line 4, in <module>
    from src.B.classB import ClassB
ImportError: <pathToThisFolder>/src/B/classB.cpython-38-x86_64-linux-gnu.so: undefined symbol: _ZN7classACC1Ev

我已经尝试 link 将 classA.longname.so 文件保存到 classBC 但没有帮助。
甚至有可能实现此功能吗?如果是,怎么做?
我想把所有东西分开,而不是有一个巨大的模块。
我的目标是保持每个 setup.py 文件原样,并且只在 pyx 文件中使用 #distutils 添加特定的扩展选项。

谢谢你的帮助,请告诉我这个结构是不是真的很糟糕...

您通常会在 setup.py 中使用“来源”选项,或者在您的 .pyx 文件中使用 # distutils: sources = filename.cpp, another_filename.cpp 指令。这记录在 https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#configuring-the-c-build 中。您这样做而不是将 cpp 文件逐字包含到您的 pxd 文件中!

因此您的 classB.pyx 包含行

# distutils: sources = classBC.cpp classAC.cpp

因此它们都将被编译并 linked 到扩展模块中。

distutils 指令方法可能比将它放在 setup.py 中更好,因为在使用它们的地方记录了依赖项。


另一种方法(根据评论可能对您更好)是 link 将所有 C++ 代码放在一个动态库中,然后 link 每个单独的 Cython 模块使用该库(使用 distutils: libraries = ...。同样,您只是告诉您在 Cython 代码中包含头文件(而不是 .cpp 文件)。