在C中嵌入Python解释器导致在使用ctypes加载时出现segfault

idv4meu8  于 11个月前  发布在  Python
关注(0)|答案(1)|浏览(74)

我尝试将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时出现segfault。这是因为调用了PyRun_SimpleString。但是如果我把它编译成一个独立的C程序,一切似乎都很好。我真的很想知道为什么会发生这种情况......但目前我没有任何想法,所以这里非常感谢任何反馈。
BR jrsm

kcugc4gi

kcugc4gi1#

我对你的代码做了一点修改。另外,我在 Win 上进行测试(因为这对我来说更方便),但我相信在 Nix 上也是一样的。

  • dll00.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();
DLL_EXPORT_API int test1();

#if defined(__cplusplus)
}
#endif

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

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

字符串

  • code00.py*:
#!/usr/bin/env python

import ctypes as cts
import sys

DLL_NAME = "./dll00.{:s}".format("dll" if sys.platform[:3].lower() == "win" else "so")

def main():
    dll = cts.PyDLL(DLL_NAME)
    test0 = dll.test0
    test0.argtypes = None
    test0.restype = cts.c_int
    test1 = dll.test1
    test1.argtypes = None
    test1.restype = cts.c_int

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

if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

输出

(py35x64_test) e:\Work\Dev\StackOverflow\q053609932> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> "c:\Install\x86\Microsoft\Visual Studio Community\2015\vc\vcvarsall.bat" x64

[prompt]> dir /b
code00.py
dll00.c

[prompt]> cl /nologo /DDLL /MD /Ic:\Install\x64\Python\Python\3.5\include dll00.c  /link /NOLOGO /DLL /OUT:dll00.so /LIBPATH:c:\Install\x64\Python\Python\3.5\libs
dll00.c
   Creating library dll00.lib and object dll00.exp

[prompt]> dir /b
code00.py
dll00.c
dll00.exp
dll00.lib
dll00.obj
dll00.so

[prompt]> "e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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 - [dll00.c] (26) - [test0]
test0 returned 42
Calling test1...
From C - [dll00.c] (32) - [test1]
From C - [dll00.c] (34) - [test1]
Traceback (most recent call last):
  File "code00.py", line 30, in <module>
    main()
  File "code00.py", line 24, in main
    res = test1_func()
OSError: exception: access violation reading 0x0000000000000010


这个问题又出现了。首先,我以为是[Python. Python]:void,Finalization,and Threads - void Py_Initialize()调用。但后来我想起了[Python. Python]:class ctypes.PyDLL(name,mode=DEFAULT_MODE,handle=None),它声明(强调是我的):
这个类的示例的行为类似于CDLL示例,除了Python GIL在函数调用期间 * 不 * 释放,并且在函数执行后检查Python错误标志。如果设置了错误标志,则引发Python异常。
因此,这只对直接调用Python C API函数有用。
code00.py 中的 CDLL 替换为 PyDLL,得到:

[prompt]> "e:\Work\Dev\VEnvs\py35x64_test\Scripts\python.exe" code00.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 - [dll00.c] (26) - [test0]
test0 returned 42
Calling test1...
From C - [dll00.c] (32) - [test1]
From C - [dll00.c] (34) - [test1]
Hello world!!!
From C - [dll00.c] (36) - [test1]
test1 returned 0

输出NixPyDLL):

(py_pc064_03.08_test0_lancer) [cfati@cfati-5510-0:/mnt/e/Work/Dev/StackExchange/StackOverflow/q053609932]> . ~/sopr.sh
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[064bit prompt]> gcc -fPIC -shared -o dll00.so dll00.c -I/usr/include/python3.8
[064bit prompt]> python ./code00.py 
Python 3.8.18 (default, Aug 25 2023, 13:20:30) [GCC 11.4.0] 064bit on linux

Calling test0...
From C - [dll00.c] (27) - [test0]
test0 returned 42
Calling test1...
From C - [dll00.c] (34) - [test1]
From C - [dll00.c] (36) - [test1]
Hello world!!!
From C - [dll00.c] (38) - [test1]
test1 returned 0

Done.


可能相关:

相关问题