Как исправить ошибку «Access Violation» при возврате PyObject из функции C с использованием библиотеки PyTCC - PullRequest
0 голосов
/ 09 апреля 2019

Я использую библиотеку Python, которая упаковывает LibTCC, которая называется PyTCC .

Я экспериментирую со способами компиляции кода JIT в Python.Проблема в том, что при вызове функции я могу правильно возвращать обычные типы данных C, но при возврате любого PyObject *.

я получаю ошибку «Access Violation». Я убедился, что код может выполняться из 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

Ответы [ 2 ]

0 голосов
/ 10 апреля 2019

Не работает с PyTCC , но в коде что-то не так.

Согласно [Python 3]: class ctypes. PyDLL ( имя, режим = DEFAULT_MODE, дескриптор = нет ) ( выделение - мое):

Экземпляры этогокласс ведет себя как CDLL экземпляры, за исключением того, что Python GIL не освобожден во время вызова функции, а после выполнения функции проверяется флаг ошибки Python.Если установлен флаг ошибки, возникает исключение Python.

Таким образом, это полезно только для непосредственного вызова API-функций Python C .

Примечание : 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]: Как привести указатель ctypes к экземпляру класса Python (ответ @ CristiFati) .

0 голосов
/ 09 апреля 2019

Скорее всего, это потому, что у вас нет 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)

вывод из моего прогона (изменил значение с 3 на 10 вpyobject просто чтобы показать, что сделал это)

It works: 3
But this does not for some reason:
10
Never gets here due to access violation :(
...