LINUX.ORG.RU

Вызов питоновских методов из си питоновского модуля

 ,


1

3

Есть питоновский модуль, написанный на си. Он принимает callback от питоновского кода. И когда нужно вызывает.

Но вот так работает


def cb(args):
... print("callback")

mymodule.set_callback(cb)

А вот так


class myclass(object):

.. . def cb(self, args):
......  print("callback")

.... def __init__(self):
...... mymodule.set_callback(self.cb)

Нет, в тот момент, когда должен вызваться callback происходит segfault

Из кода модуля я для вызова пробовал использовать PyObject_Call и PyObject_CallObject

★★★★★

Последнее исправление: cvs-255 (всего исправлений: 1)

<мимокрокодил>

А не получается ли у тебя так, что в коде на C остаётся указатель на self.cb, но при этом внутри питона не остаётся ни одной ссылки на на него ни на self, и за экземпляром класса приходит сборщик мусора?

Deleted
()

очевидно mymodule инициализируется позже чем твой класс, либо cb(self, args) не успевает инициализироваться в момент ее передачи как параметра.

ну а еще,я не знаю как в пыхтоне,но в сипп указатель на функцию и указатель на метод класса это разные вещи,и для того чтобы тебе указатель передать,надо либо чтобы процедура принимала на вход указатель на метод класса, либо же сделать метод статическим, ну или через замыкание.

jo_b1ack ★★★★★
()
Последнее исправление: jo_b1ack (всего исправлений: 1)
Ответ на: комментарий от jo_b1ack

очевидно mymodule инициализируется позже чем твой класс, либо cb(self, args) не успевает инициализироваться в момент ее передачи как параметра.

все успевает

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от cvs-255

все успевает

Тогда надо смотреть как там указатели на методы в этом вашем пыхтоне работают, но как я выше писал, в си это не одно и тоже

jo_b1ack ★★★★★
()

Я поотлаживал немного, сегфолт происходит так:

сперва питону что-то не нравится в плане аргументов, передаваемых в callback, он хочет выйти из исполнения вызова, уменьшая счетчик ссылок на что-то и в момент уменьшения, и происходит сегфолт

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от jo_b1ack

В C вообще нет методов.

В сишных питоновских модулях ты не вызываешь напрямую метод питоновского класса, ты делаешь PyObject_Call(callable, arguments), где callable - питоновский объект (PyObject *), который может быть вызван.

Между методами и функциями в питоне разница в том, что и то и другое - callable, но функции это объекты класса class 'function', а методы - класса class 'method'

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 2)
Ответ на: комментарий от cvs-255

В C вообще нет методов.

но они есть в питоне, не улавливаешь?

попробуй так

... @staticmethod 
... def cb(self, args):
......  print("callback")

jo_b1ack ★★★★★
()
Ответ на: комментарий от jo_b1ack

но они есть в питоне, не улавливаешь?

Они есть, но и то и другое - сущности типа PyObject *

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от jo_b1ack

со @staticmethod работает, но там нет self.

статические методы это не объекты класса 'method', а объекты класса 'function'

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

со @staticmethod работает, но там нет self.
статические методы это не объекты класса 'method', а объекты класса 'function'

чем мог помог,я в ваших пыхтоновских извращениях не разбираюсь,может кто знающий зайдет.

jo_b1ack ★★★★★
()
Ответ на: комментарий от cvs-255

Методы экземпляра это, вроде, не просто method, а BoundMethod или как-то так, потому что там неявно self завернут и сама функция метода.

Есть результат, если колбэк сделать lambda: FooClass.foo(instance, args)?

WDWTFWW
()
Последнее исправление: WDWTFWW (всего исправлений: 1)
Ответ на: комментарий от Deleted

Здесь я вызываю callback, который хранится в conn->dgram_send_cb

static void dgram_send_cb(struct rdp_connection_s *c, const uint8_t *d, size_t l)
{
    PyObject *connection = c->user_arg;
    struct py_rdp_connection_s *conn = PyCapsule_GetPointer(connection, "RDP Connection");
    if (conn->dgram_send_cb != Py_None)
    {
        printf("TYPE: %s\n", Py_TYPE(conn->dgram_send_cb)->tp_name);
        printf("SEND %i bytes\n", l);
        //PyObject *args = Py_BuildValue("(Oy#)", connection, d, l);
        PyObject_CallObject(conn->dgram_send_cb, Py_BuildValue("()"));
        printf("READY\n");
    }
}

А тут я сохраняю этот callback

static PyObject* py_rdp_set_dgram_send_cb(PyObject* self, PyObject* args)
{
    PyObject *connection;
    PyObject *cb;
    if (!PyArg_ParseTuple(args, "OO", &connection, &cb))
        return NULL;
    struct py_rdp_connection_s *conn = PyCapsule_GetPointer(connection, "RDP Connection");
    if (conn == NULL)
        return NULL;
    conn->dgram_send_cb = cb;
    Py_RETURN_NONE;
}

Дальше питоновский код:

Вот так все работает:

import rdp.wrapper
import serial
import sys

def cb(*args, **kwargs):
    print("CALLBACK")

conn = rdp.wrapper.create_connection()
rdp.wrapper.set_dgram_send_cb(conn, cb)
rdp.wrapper.connect(conn,1,1)

А вот так падает (я пока временно убрал передачу аргументов в callback)

import rdp.wrapper
import serial
import sys

class test(object):
    def cb(self):
        print("CALLBACK")

    def __init__(self):
        self.conn = rdp.wrapper.create_connection()
        rdp.wrapper.set_dgram_send_cb(self.conn, self.cb)

    def connect(self):
        rdp.wrapper.connect(self.conn,1,1)

t = test()
t.connect()

Причем CALLBACK он успевает написать

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

я конечно не силен во всем этом но кажись

def cb(*args, **kwargs):
    print("CALLBACK")

отличается от

    def cb(self):
        print("CALLBACK")

почему в методе другие совсем параметры?

jo_b1ack ★★★★★
()
Ответ на: комментарий от jo_b1ack

Сейчас никаких аргументов временно не передается. Если сделать

def cb(self, *args, **kwargs):
    print("CALLBACK")
все точно так же

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

Можешь для теста добавить Py_INCREF() для connection и cb в py_rdp_set_dgram_send_cb()? Просто чтобы я понял что не прав и отстал от тебя =).

Deleted
()
Ответ на: комментарий от Deleted

Сейчас падение происходит в

Program received signal SIGSEGV, Segmentation fault.
0x000055555574827c in _PyObject_GC_UNTRACK_impl (filename=0x5555558108c7 "Objects/classobject.c", lineno=246, op=0x7ffff78ab200) at ./Include/internal/pycore_object.h:68
68	    PyGC_Head *prev = _PyGCHead_PREV(gc);

Но вообще место падения на чуть более сложном тесте, оказыватеся другим, и к тому же до того, как исполнение дойдет до callback, а в процессе вызова. Падало на Py_DECREF

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 1)
Ответ на: комментарий от cvs-255

Ты ж туда аргументы какие-то передаёшь? Попробуй их выплюнуть в консоль, поглядеть чего туда прилетает.

frob ★★★★★
()

Какой мрак.

Почему не написать этот клей на Cython и не мучаться?

t184256 ★★★★★
()
Ответ на: комментарий от cvs-255

Подозреваю, что для вызова функций и методов надо юзать разные сишные коллы:

PyObject* PyObject_CallMethod(PyObject *o, char *method, char *format, ...)
PyObject* PyObject_CallFunction(PyObject *callable, char *format, ...)

см. https://docs.python.org/2/c-api/object.html

Linfan ★★★★★
()
Ответ на: комментарий от cvs-255

А в целом, если функции у тебя вызываются норм, сделай проксирующую функцию на питонской стороне и не усложняй себе жизнь. Золотое правило - при работе со связкой питон-натив не пытаться все решить только на питонской стороне (аля ctypes) или только в нативе. Взаимодействие интерпретатора с нативом нужно подготавливать с обеих сторон.

Linfan ★★★★★
()
Ответ на: комментарий от Linfan

PyObject_CallMethod это если ты хочешь вызвать метод объекта, зная объект и имя метода. У меня же уже есть метод как callable

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от Linfan

То, что я делаю, это по сути аналог

def create():
        return dict()

def set_cb(d, cb):
        d["callback"] = cb

def call_cb(d):
        d["callback"]()

class Test(object):
        message = "Hello, world"
        def __init__(self):
                self.d = create()
                set_cb(self.d, self.foo)

        def foo(self):
                print("Self = ", self)
                print(self.message)

        def call(self):
                call_cb(self.d)

test = Test()
test.call()

но только create, set_cb и call_cb сидят в C коде

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 2)
Ответ на: комментарий от cvs-255

И, кстати, функция set_cb() и call_cb() в этом примере вообще ничего не знает про то, что передаваемый им callback это метод класса, а не отдельная функция

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от cvs-255

Скорее всего, ты рассуждаешь как питонист, приравнивая питонский код к сишному. А это совсем не одно и тоже.

Сегфолт в случае коллбека как правило происходит при деаллокации нативных ресурсов гарбадж коллектором. Попробуй сделать коллбек без аргументов и инкрементируй указатель на callable перед самим коллом: Py_INCREF(callable)

Linfan ★★★★★
()
Ответ на: комментарий от cvs-255

PyObject_CallObject(conn->dgram_send_cb, Py_BuildValue("()"));

Кстати, в доке написано юзать NULL а не пустой tuple, генерируемый Py_BuildValue("()")

Linfan ★★★★★
()
Ответ на: комментарий от Linfan

Скорее всего, ты рассуждаешь как питонист, приравнивая питонский код к сишному. А это совсем не одно и тоже.

Я умею писать на Си и на питоне. А вот связью одного с другим до сих пор не занимался

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от Linfan

Сегфолт происходит до выхода из PyObject_CallObject

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от Linfan

Если передавать NULL, то то же самое, падает до выхода из CallObject

cvs-255 ★★★★★
() автор топика
Ответ на: комментарий от Linfan

инкрементируй указатель на callable перед самим коллом: Py_INCREF(callable)

по прежнему падает

Но отладка показала, что падает из-за GC. Правда gc.disable() не помогает

cvs-255 ★★★★★
() автор топика
Последнее исправление: cvs-255 (всего исправлений: 2)

вот так, вроде, не падает:

--- test/ext.c	2019-07-01 04:08:23.000000000 +0300
+++ test/ext.c	2019-07-01 11:12:39.489546051 +0300
@@ -27,0 +28 @@ static void _destroy(PyObject *obj)
+    Py_DECREF(conn->cb);
@@ -53,0 +55 @@ static PyObject* py_set_cb(PyObject* sel
+    Py_INCREF(conn->cb);
anonymous
()
Ответ на: комментарий от cvs-255

У меня получилось похожее, как у anonymous. В контексте семпла проблему создавал GC утилизируя указатель на коллбек. Для предотвращения достаточно добавить Py_INCREF(cb) в py_set_cb(). В полноценной аппликухе при деструкте conn надо уменьшать референс коллбека. Относительно self (указателя на объект) - он нужен только в том случае, если необходимо использовать филды класса. На данный момент self=None и метод вызывается по сути как статик.

Linfan ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.