我正在使用一个包含LibTCC的Python库,名为PyTCC。
我正在尝试在Python中使用JIT编译代码的方法。问题是,在调用函数时,我可以正确返回正常的C数据类型,但是在返回任何PyObject *
时出现“访问冲突”错误。
我确保代码可以从PyTCC执行,正如我的代码示例所示。这也意味着代码示例正在成功编译。
import ctypes, pytcc
program = b"""
#include "Python.h"
/* Cannot return 3 due to access violation */
PyObject * pop(PyObject * self, PyObject * args, PyObject * kwargs) {
// Cannot return *any* Python object
return PyLong_FromLong(3);
}
int foobar() { return 3; } // Returns 3 just fine
// Needed to appease TCC:
int main() { }
"""
jit_code = pytcc.TCCState()
jit_code.add_include_path('C:/Python37/include')
jit_code.add_library_path('C:/Python37')
jit_code.add_library('python37')
jit_code.compile_string(program)
jit_code.relocate()
foobar_proto = ctypes.CFUNCTYPE(ctypes.c_int)
foobar = foobar_proto(jit_code.get_symbol('foobar'))
print(f'It works: {foobar()}')
pop_proto = ctypes.CFUNCTYPE(ctypes.c_voidp)
pop = pop_proto(jit_code.get_symbol('pop'))
print('But this does not for some reason:')
print(pop())
print('Never gets here due to access violation :(')
该计划的输出应为:
It works: 3
But this does not for some reason:
3
Never gets here due to access violation :(
但相反,我得到了这个确切的错误:
It works: 3
But this does not for some reason:
Traceback (most recent call last):
File "fails.py", line 40, in <module>
print(pop())
OSError: exception: access violation writing 0x00000000FFC000E9
很可能是因为你在创建对象时没有GIL。您还有返回类型的问题。 ctypes.c_voidp告诉python将它视为一个int而不是一个PyObject,所以你只看到它不是用于访问冲突的是值指针本身而不是它指向的东西。
尝试:
PyObject * pop() {
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
PyObject* obj = PyLong_FromLong(10);
PyGILState_Release(gstate);
return obj;
}
并切换
pop_proto = ctypes.CFUNCTYPE(ctypes.c_voidp)
至
pop_proto = ctypes.CFUNCTYPE(ctypes.py_object)
从我的运行输出(在pyobject中将值从3更改为10只是为了显示它已成功)
It works: 3
But this does not for some reason:
10
Never gets here due to access violation :(
没有使用PyTCC,但代码中有问题。
根据[Python 3]: class ctypes.PyDLL(name, mode=DEFAULT_MODE, handle=None)(重点是我的):
此类的实例表现得像CDLL实例,除了在函数调用期间未释放Python GIL,并且在函数执行后检查Python错误标志。如果设置了错误标志,则会引发Python异常。
因此,这仅对直接调用Python C api函数有用。
注意:CFUNCTYPE用于CDLL,与PYFUNCTYPE用于PyDLL的情况相同。
因此,在pop_proto中,你应该用ctypes.CFUNCTYPE
替换ctypes.PyFUNCTYPE
(注意你在c_voidp中有一个拼写错误)。
接下来,同一页面指出对于PyObject *(C),应该使用py_object(Python)。所以:
pop_proto = ctypes.PyFUNCTYPE(ctypes.py_object)
如果你想要严格,你必须在原型中包含参数,这会使代码看起来有点复杂,但对于这种特殊情况(它们被忽略),它不是强制性的:
pop_proto = ctypes.PyFUNCTYPE(ctypes.py_object, ctypes.py_object, ctypes.py_object, ctypes.py_object)
这是PyObject *PyBytes_Repr(PyObject *obj, int smartquotes)
的一个例子(以“老式”的方式调用C函数):
[cfati@CFATI-5510-0:C:\WINDOWS\system32]> "e:\Work\Dev\VEnvs\py_064_03.07.03_test0\Scripts\python.exe" Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 22:22:05) [MSC v.1916 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> >>> import sys >>> import os >>> import ctypes >>> >>> python_dll_name = os.path.join(os.path.dirname(sys.executable), "python" + str(sys.version_info.major) + str(sys.version_info.minor) + ".dll") >>> python_dll_name 'e:\\Work\\Dev\\VEnvs\\py_064_03.07.03_test0\\Scripts\\python37.dll' >>> >>> python_dll = ctypes.PyDLL(python_dll_name) >>> >>> pybytes_repr_proto = ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.py_object, ctypes.c_int) >>> pybytes_repr = pybytes_repr_proto(("PyBytes_Repr", python_dll)) >>> >>> b = b"abcd" >>> >>> reprb = pybytes_repr(b, 0) >>> reprb "b'abcd'"
您也可以查看[SO]: How to cast a ctypes pointer to an instance of a Python class (@CristiFati's answer)。