有 C++ 编译的 dll 可以与 ctypes 一起使用,而 C++ 编译的 dll 不能直接与 ctypes 一起使用。
如果 API 仅公开 plain-old-data(例如 int、int *、char * 等)并被包裹在 extern "C" 中,则可以从 ctypes 使用它。
如果在接口中使用非 POD 类(例如 std::vector 或 std::string)或 API 未包装在 extern "C" 中,则 dll 不能与 ctypes 一起使用(至少在便携的方式,事不宜迟)。
为什么需要extern "C"?
以下声明在 C++ 中有效,但在 C 中无效:
void my_fun(int a);
void my_fun(double a);
因为 C 不执行名称修改,所以这两个函数都将映射到生成的目标文件中名为 my_fun 的符号,这是有问题的。
C++ 会创建两个名称不同的符号,例如 gcc 会创建符号 Z6my_funi 和 _Z6my_fund。 MSVC 有另一种命名方案 - 因此 dll(或共享对象)中的结果符号取决于构建它的编译器。
在ctypes 的帮助下,在 dll 中找到这些符号并非不可能 - 它只是不像 C 名称那样简单,因为需要额外的信息 - 使用哪个编译器构建 dll。
C 名称与 ctypes 一起使用,将声明包装到 extern "C" 中会关闭名称修改,因此
extern "C" {
void my_fun(int a);
void my_fun(double a);
}
将不再编译,因为编译器将确保一个符号没有多个定义。
为什么不能在接口中使用更复杂的 C++ 类?
extern "C" 不禁止使用std::vector<> 和其他 C++ 类:
extern "C" {
void my_fun(std::vector<double> a);
}
编译,它只在生成的目标文件中生成符号my_func 而不是?my_fun@@YANV?$vector@NV?$allocator@N@std@@@std@@@Z(使用MSVC)或_Z6my_funSt6vectorIdSaIdEE(使用gcc)。
对于 POD,比如说double[10],内存布局很清晰,例如内存布局。 std::vector 依赖于实现,ctypes 不知道。另一个问题是 - ctypes 无法处理的问题 - 构造函数/析构函数可能比简单的 POD 初始化更复杂。
因此my_fun(std::vector<double>) 的功能不能与ctypes 一起使用。但是,如果知道类的内存布局,可以用ctypes 模拟它们,通过调用 (right) 构造函数来初始化对象(顺便说一句。类方法的符号总是被破坏,即使包装在extern "C" ),调用函数my_fun,然后调用(右)析构函数销毁对象。