0

我正在使用一个名为PyTCC的包装 LibTCC 的 Python 库。

我正在尝试在 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
4

2 回答 2

0

很可能是因为您在创建对象时没有 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 :(
于 2019-04-09T20:51:58.937 回答
0

不适用于PyTCC,但代码有问题。

根据[Python 3]:ctypes。PyDLL名称,模式=DEFAULT_MODE,句柄=无重点是我的):

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

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

注意CFUNCTYPE用于CDLL,与PYFUNCTYPE用于PyDLL相同。

因此,在pop_proto中,您应该替换ctypes.CFUNCTYPE为(请注意,您在c_voidpctypes.PyFUNCTYPE中有错字)。

接下来,同一页面指出,对于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)

于 2019-04-09T21:06:20.157 回答