【问题标题】:instantiate python object within a c function called via ctypes在通过 ctypes 调用的 c 函数中实例化 python 对象
【发布时间】:2012-12-04 18:57:32
【问题描述】:

当我从 ctypes 调用的 c 函数实例化 python 对象时,我的嵌入式 Python 3.3 程序出现段错误。

设置解释器后,我可以成功地从 c main 实例化一个 python Int(以及自定义的 c 扩展类型):

#import <Python/Python.h>  

#define LOGPY(x) \
{ fprintf(stderr, "%s: ", #x); PyObject_Print((PyObject*)(x), stderr, 0); fputc('\n', stderr); }

// c function to be called from python script via ctypes.
void instantiate() {
  PyObject* instance = PyObject_CallObject((PyObject*)&PyLong_Type, NULL);
  LOGPY(instance);
}

int main(int argc, char* argv[]) {
  Py_Initialize();
  instantiate(); // works fine

  // run a script that calls instantiate() via ctypes.
  FILE* scriptFile = fopen("emb.py", "r");
  if (!scriptFile) {
    fprintf(stderr, "ERROR: cannot open script file\n");
    return 1;
  }

  PyRun_SimpleFileEx(scriptFile, scriptPath, 1); // close on completion
  return 0;
}

然后我使用 PyRun_SimpleFileEx 运行 python 脚本。它似乎运行得很好,但是当它通过 ctypes 调用 instantiate() 时,PyObject_CallObject 内部的程序段错误:

import ctypes as ct
dy = ct.CDLL('./emb')
dy.instantiate() # segfaults

lldb 输出:

instance: 0
Process 52068 stopped
* thread #1: tid = 0x1c03, 0x000000010000d3f5 Python`PyObject_Call + 69, stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
    frame #0: 0x000000010000d3f5 Python`PyObject_Call + 69
Python`PyObject_Call + 69:
-> 0x10000d3f5:  movl   24(%rax), %edx
   0x10000d3f8:  incl   %edx
   0x10000d3fa:  movl   %edx, 24(%rax)
   0x10000d3fd:  leaq   2069148(%rip), %rax       ; _Py_CheckRecursionLimit
(lldb) bt
* thread #1: tid = 0x1c03, 0x000000010000d3f5 Python`PyObject_Call + 69, stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
    frame #0: 0x000000010000d3f5 Python`PyObject_Call + 69
    frame #1: 0x00000001000d5197 Python`PyEval_CallObjectWithKeywords + 87
    frame #2: 0x0000000201100d8e emb`instantiate + 30 at emb.c:9

为什么仅从 ctypes 调用 instantiate() 失败?该函数仅在调用 python 库时才会崩溃,因此可能某些解释器状态正在被 ctypes FFI 调用破坏?

【问题讨论】:

  • dy_test() 如何实例化?您的日志消息(如果通过实例化)显示 PyObject_CallObject 已完成。否则,您将看不到 LOGPY 输出。您可以尝试 Python 中的 PyLongObject 或其他一些标准可调用对象吗?这会告诉您您的类型设置是否有问题。
  • 对不起,为了清楚起见,我重命名了 dy_test;固定的。传递 &PyLong_Type 以同样的方式失败;我会相应地减少复制。
  • CDLL换成PyDLL会更好吗?
  • 是的! PyDLL 工作 - docs 说唯一的区别是 GIL 保持锁定状态并在最后检查错误。想写答案还是应该写?

标签: python ctypes


【解决方案1】:

感谢 Armin Rigo 的提示。问题是通过 ctypes.CDLL() 加载的库会创建在调用本机代码时释放 GIL 的函数。据我所知,这意味着为了让本机函数回调 python 代码,它需要首先使用 python C API 获取 GIL。

更简单的替代方法是使用ctypes.PyDLL(),它不会释放 GIL(它还会检查 python 错误标志)。文档说:“因此,这仅对直接调用 Python C API 函数有用。”我的代码更间接,因为我有 python 代码调用我自己的 C 函数,然后调用 python C API,但问题是一样的。

【讨论】:

    猜你喜欢
    • 2022-06-19
    • 1970-01-01
    • 1970-01-01
    • 2019-10-21
    • 1970-01-01
    • 1970-01-01
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多