【问题标题】:Embedding Python interpreter in C leads to segfault when loading with ctypes在使用 ctypes 加载时,在 C 中嵌入 Python 解释器会导致段错误
【发布时间】:2019-05-05 16:51:01
【问题描述】:

我尝试将 Python 解释器嵌入到 C 中。 为了对此进行测试,我创建了一个共享库并 尝试使用 ctypes 在 Python 中加载这个。不幸的是,这并没有 工作,我想了解原因。

这是一个示例 c - 代码:

#ifdef __cplusplus
extern "C" {
#endif

#include <Python.h>


int run_py(void);
int run_py2(void);

int
run_py(void)
{
    printf("hello from run_py\n");
    return 42;
}

int
run_py2(void)
{
    printf("entering c-function: run_py()\n");
    Py_Initialize();
    PyRun_SimpleString("print('hello world')");
    return 0;
}

#ifdef __cplusplus
}
#endif

所以我用 gcc 将它编译成“mylib.so”,并使用 python3.7-config --cflags 和 --ldflags 进行链接等等。

这是我用来加载它的 Python 代码..

import ctypes as c
import os
import sys


if __name__ == '__main__':
    print("running shared-lib integration test with python:\n{}".format(sys.version))

    path = os.path.dirname(os.path.realpath(__file__))
    dllfile = os.path.join(path, 'mylib.so')
    dll = c.CDLL(str(dllfile))

    print("loaded CDLL")
    dll.run_py.restype  = c.c_int
    dll.run_py2.restype  = c.c_int

    print("now calling dll.run_py()...")
    rv = dll.run_py()
    print("called dll.run_py: rv={}".format(rv))

    print("now calling dll.run_py2()...")
    rv2 = dll.run_py2()
    print("called dll.run_py2: rv={}".format(rv2))

所以这只是加载两个函数 run_py 和 run_py2 并执行它们。这是输出...

running shared-lib integration test with python:
3.7.1 (default, Oct 22 2018, 10:41:28) 
[GCC 8.2.1 20180831]
loaded CDLL
now calling dll.run_py()...
hello from run_py
called dll.run_py: rv=42
now calling dll.run_py2()...
entering c-function: run_py()
Segmentation fault (core dumped)

所以基本上这会导致调用 run_py2 时出现段错误。 造成这种情况的原因是 PyRun_SimpleString 的调用。 但是,如果我将其编译为独立的 C 程序 一切似乎都很好。我真的 想了解为什么会发生这种情况......但目前我 出了一些想法,所以在这里非常感谢任何反馈。

BR jrsm

【问题讨论】:

  • 看起来你正在混合两种不同的东西,嵌入 Python 和扩展 Python。如果你的库扩展了 Python(即可以从 Python 中加载),你不应该尝试在其中嵌入 Python 解释器(除非你想要两个独立的不相关的 Python 解释器,我对此表示怀疑)。
  • 是的,你是对的,最后我不会使用两个 python 解释器......只有 C 中的共享库(所以 Python 嵌入在 C 中)。但是,我尝试将此作为第一次测试(出于好奇),现在我想了解它为什么会损坏;-)

标签: python c ctypes


【解决方案1】:

我稍微更改了您的代码。另外,我正在 Win 上进行测试(因为此时它对我来说更方便),但我确信 Nix 中的情况是一样的。

dll.c

#include <stdio.h>
#include <Python.h>

#define PRINT_MSG_0() printf("From C - [%s] (%d) - [%s]\n", __FILE__, __LINE__, __FUNCTION__)

#if defined(_WIN32)
#define DLL_EXPORT_API __declspec(dllexport)
#else
#define DLL_EXPORT_API
#endif

#if defined(__cplusplus)
extern "C" {
#endif

DLL_EXPORT_API int test0(void);
DLL_EXPORT_API int test1(void);

#if defined(__cplusplus)
}
#endif


int test0(void) {
    PRINT_MSG_0();
    return 42;
}


int test1(void) {
    PRINT_MSG_0();
    Py_Initialize();
    PRINT_MSG_0();
    PyRun_SimpleString("print(\"Hello world!!!\")");
    PRINT_MSG_0();
    return 0;
}

code.py

#!/usr/bin/env python3

import sys
from ctypes import CDLL,\
    c_int


DLL = "./dll.so"


def main():
    dll_dll = CDLL(DLL)
    test0_func = dll_dll.test0
    test0_func.argtypes = None
    test0_func.restype = c_int
    test1_func = dll_dll.test1
    test1_func.argtypes = None
    test1_func.restype = c_int

    print("Calling {:}...".format(test0_func.__name__))
    res = test0_func()
    print("{:} returned {:d}".format(test0_func.__name__, res))
    print("Calling {:}...".format(test1_func.__name__))
    res = test1_func()
    print("{:} returned {:d}".format(test1_func.__name__, res))


if __name__ == "__main__":
    print("Python {:s} on {:s}\n".format(sys.version, sys.platform))
    main()

输出

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>"c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>dir /b
code.py
dll.c

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>cl /nologo /DDLL /MD /Ic:\Install\x64\Python\Python\3.5\include dll.c  /link /NOLOGO /DLL /OUT:dll.so /LIBPATH:c:\Install\x64\Python\Python\3.5\libs
dll.c
   Creating library dll.lib and object dll.exp

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>dir /b
code.py
dll.c
dll.exp
dll.lib
dll.obj
dll.so

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

Calling test0...
From C - [dll.c] (26) - [test0]
test0 returned 42
Calling test1...
From C - [dll.c] (32) - [test1]
From C - [dll.c] (34) - [test1]
Traceback (most recent call last):
  File "code.py", line 30, in <module>
    main()
  File "code.py", line 24, in main
    res = test1_func()
OSError: exception: access violation reading 0x0000000000000010

问题重现。首先,我认为是[Python 3]: void Py_Initialize() 电话。但后来我想起了[Python 3]: class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None)强调是我的),它说:

这个类的实例的行为类似于CDLL 实例,除了在函数调用期间释放 Python GIL,并且在函数执行后检查 Python 错误标志。如果设置了错误标志,则会引发 Python 异常。

因此,这仅对直接调用 Python C api 函数有用。

CDLL 替换为 code.py 中的 PyDLL,产生:

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932>"e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code.py
Python 3.5.4 (v3.5.4:3f56838, Aug  8 2017, 02:17:05) [MSC v.1900 64 bit (AMD64)] on win32

Calling test0...
From C - [dll.c] (26) - [test0]
test0 returned 42
Calling test1...
From C - [dll.c] (32) - [test1]
From C - [dll.c] (34) - [test1]
Hello world!!!
From C - [dll.c] (36) - [test1]
test1 returned 0

【讨论】:

  • 感谢您抽出时间和耐心进行测试。我替换了 CDLL,它似乎可以工作,所以这很好,谢谢!您知道为什么会发生此段错误吗?仅对 python c api 函数调用有用是什么意思?
  • 我个人,没有任何意义,这是官方文档的粘贴。您知道 Python 代码在 GIL 下运行,这意味着每个函数(包括 PyRun_SimpleString)都应该这样运行。 CDLL“停止”GIL,所以我猜它是Undefined Behavior。有关更多详细信息,您可能必须浏览 Python 源代码。
  • 是的,我认为可能很难说出发生了什么。但是我认为有 2 个 GIL,一个用于嵌入式 python,一个用于另一个。我的意思是当使用 multiprocessing 模块时,因此,产生多个 python 进程,也应该有超过 1 个 GIL ..
  • 不,每个进程都有一个 GIL(可以是活动的/非活动的)。我们的案例就是这种情况。但问题是一个应该在 GIL 下运行的函数 (PyRun_SimpleString) 不是因为它是由 CDLL 发布的。也许它引用了一些被 GIL 版本失效的指针。
  • 这意味着 2 个 python interpeters 共享 GIL?
猜你喜欢
  • 1970-01-01
  • 2021-12-23
  • 2017-11-22
  • 2020-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-08
  • 1970-01-01
相关资源
最近更新 更多