这里有一些歧义,b/c 相同的名称适用于不同的步骤和不同的地方。让我解释一下结构(和历史),它甚至可以回答你的问题。
cppyy-generator 使用 Clang Python 绑定。因此,它访问的 AST 是 C++ 的,并且它完全(丑陋)的荣耀可用。您不需要 cppyy 的任何部分来使用 Clang Python 绑定。 cppyy-generator 服务于一个特定的用例,您希望将所有本地 C++ 实体预加载到 Python 模块中。由于 cppyy 利用惰性一切和自动加载,出于性能原因,“所有 C++ 实体”(本地或其他)的概念没有明确定义的含义。因此使用了 libclang,概念清晰。
cppyy-backend capi(或 C-API)是一个 API,它是在 reductio 中开发的,用于服务于 cppyy 的 PyPy 实现。它是一个用于引导 cppy/C++ 的 C 风格 API。它被简化为编写 Python-C++ 绑定的本质,隐藏了 Clang AST 的许多不相关的细节(例如,模板可以在 Clang AST 中存在的 15 种左右的方式被简化为“IsTemplate”等)。后端 C-API 完全不依赖或使用 Python。
后端 C-API 的实现相当不漂亮。部分是由于历史原因(一件坏事),部分是为了隐藏所有 Cling 和 Clang,以防止与可能正在使用 Clang 或 LLVM 的应用程序的其他部分发生冲突(一件好事;正在使用的 Clang 版本by Cling 是定制的,可能不适用于 Numba)。同样,所有这些都完全独立于 Python。
然后,它在 Python 中的使用。有两种不同的实现:用于 CPython 的 CPyCppyy,用 C 实现,和 PyPy _cppyy 模块,用 RPython 实现。两者都通过 C-API 执行从 Python 跨入 C++ 的咒语。既不生成也不使用 Python AST:都直接生成和操作 Python 实体。这是懒惰地发生的。仔细考虑这些步骤:在上面的示例中,Python 用户将输入类似cppyy.gbl.foo.Bar().DoSomething() 的内容。在cppyy中,使用Python的__getattr__来截取名字,然后简单的通过后端向Cling询问是否知道foo、Bar等。例如C-APIGetScope("foo")将返回一个有效的标识符,因此 CPyCppyy/_cppyy 知道生成一个 Python 类来表示命名空间。然而,它在任何时候都不会完全扫描 AST 中的全局(甚至foo)命名空间来生成先验绑定。根据您的描述,CPyCppyy/_cppyy 中没有对您有用的内容。
回到您的第一个语句,您想要生成其他类型的绑定。您没有说明什么类型的绑定,但选择 C-API 的主要原因是它位于 Cling 之上,而不是 Clang,因为 Clang AST 直接来自 C++ 或通过其 Python 绑定。 Cling 提供了对 JIT 的轻松访问,但您也可以直接从 Clang(它的库,而不是 AST)对其进行编程。作为这种 easyu 访问的示例,在后端 C-API 中,您只需将要 JITted 的 C++ 字符串转储到 compile 函数中(与您的示例中的 cppdef 完全相同)。 Cling 人员计划直接为来自 Cling 的动态语言提供更好的接口,但这是一项正在进行的工作,并且 (AFAIK) 尚不可用。
最后,请注意 Cling 包含 Clang,所以如果你安装 Cling,你仍然会得到 Clang(和 LLVM),这可能是一个严重的依赖项。
编辑:从根本上说,与其他工具相反,cppyy 不提供起点列表(例如“所有类”),也不提供完整/真实的 AST。您可以从后端复制cpp_cppyy.h 标头(否则它不是安装的一部分),只需包含它并使用它(所有符号都已导出),但您需要先验地知道类列表。示例:
import cppyy
cppyy.cppdef('#define RPY_EXPORTED extern')
cppyy.include('cpp_cppyy.h')
import cppyy
cppyy.cppdef("""
namespace foo {
class Bar {
public:
void DoSomething() {}
};
}""")
cpp = cppyy.gbl
capi = cpp.Cppyy
scope_id = capi.GetScope(cpp.foo.Bar.__cpp_name__) # need to know existence
for i in range(capi.GetNumMethods(scope_id)):
m = capi.GetMethod(scope_id, i)
print(capi.GetMethodName(m))
但正如您所见,它不提供与原始代码一对一的结果。例如,所有编译器生成的构造函数和析构函数都被列为方法。
后端 API 中也没有任何东西,如您链接的 pygccxml 文档中的 run_functions = unittests.member_functions('run')。原因是这样在 cppyy 的上下文中没有任何意义。例如。如果另一个头文件加载了更多run 函数怎么办?如果它是一个模板函数并且弹出更多实例怎么办?如果using namespace ... 出现在后面的代码中,引入更多run 重载怎么办?
cppyy 确实有 GetAllCppNames C-API 函数,但不能保证它是详尽无遗的。它的存在是为了代码编辑器中的制表符补全(在绑定范围的自定义__dir__ 函数中调用它)。事实上,正是因为不完整,cppyy-generator 使用了 libclang。
您在 cmets 中提到了 gInterpreter,但这是我之前提到的历史的一部分:它是 libclang 提供的完整 AST 和 Python 所需的极简 AST(例如后端 C -API)。是的,您可以直接使用它(事实上,它仍然在后端 C-API 下使用),但它更笨重,没有什么好处。
例如,要处理“获取所有'运行'方法”示例,您可以这样做:
import cppyy
cppyy.cppdef("""
namespace foo {
void run(int) {}
void run(double) {}
}""")
cpp = cppyy.gbl
# using the ROOT/meta interface
cls = cpp.CppyyLegacy.TClass.GetClass(cpp.foo.__cpp_name__)
print('num "run" overloads:"', cls.GetListOfMethodOverloads('run').GetSize())
# directly through gInterpreter
gInterp = cpp.gInterpreter
cls = gInterp.ClassInfo_Factory(cpp.foo.__cpp_name__)
v = cpp.std.vector['const void*']()
gInterp.GetFunctionOverloads(cls, 'run', v)
gInterp.ClassInfo_Delete(cls)
print('num "run" overloads:"', len(v))
但前一个界面(通过 CppyyLegacy.TClass)可能不会保留下来,而且 gInterpreter 真的很难看。
我很确定你不会乐意尝试让 cppyy 取代 pygccxml 的使用,如果我是你,我会改用 Clang Python 绑定。