我尝试将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 程序
一切似乎都很好。我真的
想了解为什么会发生这种情况......但目前我
我们非常感谢您的任何反馈。
BRjrsm
我稍微改变了你的代码。另外,我正在 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.Docs]:初始化、终结和线程 - void Py_Initialize() 调用。但后来我想起了 [Python.Docs]: class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None) 其中指出(强调是我的):
此类的实例的行为类似于 CDLL 实例,只不过 Python GIL 在函数调用期间不被释放,并且在函数执行后检查 Python 错误标志。如果设置了错误标志,则会引发 Python 异常。
因此,这仅对直接调用 Python C api 函数有用。
在 code00.py 中用 PyDLL 替换 CDLL,得到:
[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
输出(Nix与PyDLL):
(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.
可能相关: