Embedded Python

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo zusammen,

ich bin neu hier und vorab einmal ein Gruß in die Expertenrunde. Arbeite jetzt seit ca. 10 Jahren mit Python und werde versuchen mich hier entsprechend einzubringen. Der Grund meiner Anmeldung ist aber, wie vermutlich bei den meisten hier, ein Problem was mich seit Tagen beschäftigt und ich hoffe sehr, dass mir hier jemand helfen kann.

Also, ich schreibe an einer C-DLL die als Schnittstelle zwischen Python und Java dienen soll. die DLL arbeitet mit einem Embedded Python Schnittstelle, was für reguläre Pythonfunktionen auch sehr gut funktioniert. Nun benötige ich aber die Möglichkeit Callbacks von C in Pythonfunktionen zu nutzen. D.h. ein Client ist mit der DLL verbunden und ruft doch eine Funktion auf mit Parametern auf, wovon einer eine C-Funktion ist. Die Umwandlung von normalen Datentypen wie int oder char * in Python Datentypen bringt Embedded Python von Haus aus mit sich (z.B. PyInt_FromLong(a);). Für Funktionen sieht das aber leider anders aus und die Dokumentation das Ganzen ist was das angeht leider auch sehr schlecht und in Teilen unvollständig. Ich habe zu dem Thema im Netz Folgendes gefunden (Quelle : http://stackoverflow.com/questions/1353 ... hon-with-c). Hier heißt es:

If you have a single C function that you want to provide as a callback, you can use PyCFunction_New to convert it into a Python callable:

Code: Alles auswählen

#include <python.h>

static PyObject *my_callback(PyObject *ignore, PyObject *args)
{
  /* ... */
}

static struct PyMethodDef callback_descr = {
  "function_name",
  (PyCFunction) my_callback,
  METH_VARARGS,                 /* or METH_O, METH_NOARGS, etc. */
  NULL
};

static PyObject *py_callback;

...
py_callback = PyCFunction_New(&callback_descr, NULL);
Dieser Fall wäre für eine "hard gecodete" Funktion in der DLL, was in meinem Fall leider nicht der Fall ist. Die Funktion wird über einen Client an die DLL geggeben. Dazu gibt es dort folgenden Ansatz:

This approach won't work if you want to choose different callbacks at run-time, e.g. to provide a generic c_to_python function that converts a C callback function to a Python callback. In that case, you'll need to implement an extension type with its own tp_call.

Code: Alles auswählen

typedef struct {
  PyObject_HEAD
  static PyObject (*callback)(PyObject *, PyObject *);
} CallbackObject;

static PyObject *
callback_call(CallbackObject *self, PyObject *args, PyObject *kwds)
{
  return self->callback(args, kwds);
}

static PyTypeObject CallbackType = {
    PyObject_HEAD_INIT(NULL)
    0,                          /*ob_size*/
    "Callback",                 /*tp_name*/
    sizeof(CallbackObject),     /*tp_basicsize*/
    0,                          /*tp_itemsize*/
    0,                          /*tp_dealloc*/
    0,                          /*tp_print*/
    0,                          /*tp_getattr*/
    0,                          /*tp_setattr*/
    0,                          /*tp_compare*/
    0,                          /*tp_repr*/
    0,                          /*tp_as_number*/
    0,                          /*tp_as_sequence*/
    0,                          /*tp_as_mapping*/
    0,                          /*tp_hash */
    (ternaryfunc) callback_call, /*tp_call*/
    0,                          /*tp_str*/
    0,                          /*tp_getattro*/
    0,                          /*tp_setattro*/
    0,                          /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
};

PyObject *
c_to_python(PyObject (*callback)(PyObject *, PyObject *))
{
  CallbackObject *pycallback = PyObject_New(CallbackObject, &CallbackType);
  if (pycallback)
    pycallback->callback = callback;
  return pycallback;
}
Wenn ich das Ganze so in meine Code einfüge, werden folgende Fehler generiert:

1>os4es.c(17): error C2071: 'callback': Ungültige Speicherklasse
1>os4es.c(23): error C2440: 'return': 'PyObject' kann nicht in 'PyObject *' konvertiert werden
1>os4es.c(56): warning C4133: 'return': Inkompatible Typen - von 'CallbackObject *' zu 'PyObject *'

Den C2071 hatte ich so auch noch nicht und ich versuche noch rauszufinden wo genau das Problem liegt. Inwieweit ich das so verwenden kann, bisher auch noch unklar, aber mir erscheint der Gesamtansatz sehr schlüssig. Ist hier jemand der Ähnliches schgon einmal gemacht hat und mir dazu etwas sagen kann?

Darüber hinaus noch eine weitere Frage. Wenn ich eine C-Funktion habe, void callback(int,int), wie kann ich diese mit der Funktion c_to_python nutzen? Wäre ein Cast auf PyObject (*callback)(PyObject *, PyObject *) hier überhaupot möglich? Es ist kleider schon ein ganzes Weilchen her als ich mit C gearbeitet habe, insofern seht mir manche dumme Frage bitte nach. Vielen Dank und Gruß!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@BugsBunny74: ich hätte bei Deinem Problem auch den Ansatz einer Klasse gewählt, und __call__ überschrieben. Natürlich mußt Du callback_call so schreiben, dass es args bzw. kwds mit PyArg_ParseTupleAndKeywords o.ä. nach zwei ints konvertiert, dann die Callback-Funktion aufruft und das Ergebnis wieder in ein PyObject zurückwandelt. Dann stünde in Zeile 17 auch

Code: Alles auswählen

typedef struct {
  PyObject_HEAD
  static int (*callback)(int, int);
} CallbackObject;
und die ersten zwei Fehler würden nicht auftreten. Der zweite Fehler sagt auch schon, was Du beim ersten falsch gemacht hast. Der Rückgabewert sollte PyObject* und nicht PyObject sein:

Code: Alles auswählen

typedef struct {
  PyObject_HEAD
  static PyObject* (*callback)(PyObject *, PyObject *);
} CallbackObject;
Letzter Fehler sollte klar sein. Du mußt explizit nach PyObject* konvertieren.
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo Sirius3,

vorab einmal vielen, vielen Dank! Du hast den Stein des Anstosses gegeben. Nachdem ich zuvor ehrlich gesagt erstmal wenig bis garnichts verstanden habe, hat dein Post mir die Augen geöffnet und ich konnte die Fehler im Code alle beseitigen. Der Code lautet nun wie folgt:

Code: Alles auswählen

typedef struct {
  PyObject_HEAD
  int (*callback)(int, int);
} CallbackObject;

static PyObject *
callback_call(CallbackObject *self, PyObject *args, PyObject *kwds)
{
	PyObject *pResult;
	int a,b, result;
	static char *kwlist[] = {"a", "b", NULL};
	if (!PyArg_ParseTupleAndKeywords(args, kwds, "i|i", kwlist, &a, &b))
        return NULL;	
	result = self->callback(a,b);
	pResult = PyInt_FromLong(result);
	return (pResult);
}

static PyTypeObject CallbackType = {
    PyObject_HEAD_INIT(NULL)
    0,                          /*ob_size*/
    "Callback",                 /*tp_name*/
    sizeof(CallbackObject),     /*tp_basicsize*/
    0,                          /*tp_itemsize*/
    0,                          /*tp_dealloc*/
    0,                          /*tp_print*/
    0,                          /*tp_getattr*/
    0,                          /*tp_setattr*/
    0,                          /*tp_compare*/
    0,                          /*tp_repr*/
    0,                          /*tp_as_number*/
    0,                          /*tp_as_sequence*/
    0,                          /*tp_as_mapping*/
    0,                          /*tp_hash */
    (ternaryfunc) callback_call, /*tp_call*/
    0,                          /*tp_str*/
    0,                          /*tp_getattro*/
    0,                          /*tp_setattro*/
    0,                          /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT,         /*tp_flags*/
};

PyObject * c_to_python(int (*callback)(int, int))
{
  CallbackObject *pycallback = PyObject_New(CallbackObject, &CallbackType);
  if (pycallback)
    pycallback->callback = callback;
  return (PyObject *) pycallback;
}
Wenn ich nun mit meiner C++ Testapp auf die DLL zugreife und die entsprechende Funktion mit den Callback zugreife und ausführe, so wird der Callback tatsächlich in der Python Funktion ausgeführt. Ich erhalte eine entsprechende Ausgabe von der Funktion. Nur verabschiedet sich leider im gleichen Zug meine DllTest App. Das auch unabhängig davon ob ich den Callback nutze oder nicht. Heißt, in c_to_Python muss irgendetwas speichermässig schieflaufen. Hast du oder jemand hier eine Idee wo ich ansetzen müsste, oder was ich falsch gemacht habe??
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo nochmal,

es ist gelöst! Falls jemamd mal ein ähnliches Prolem haben sollte, die Referenzen müssen solange der Callback aktiv ist natürlich behalten werden. Ich habe nach Funktionsaufruf Diese mit

Code: Alles auswählen


Py_DECREF(pArgs);
Py_DECREF(pValue);

gelöscht, was natürlich dann zu einer Access violation führt.

Vielen Dank nochmal und würde mich freuen wenn dies hier später nochmal für jemand mit ähnlichem Problem von Nutzen wäre!

Grüsse,
Bugs
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Hab dein Problem nur knapp überflogen, wenn du nicht unbedingt eine C Extension brauchst sondern nur eine C Schnittstelle kannst du dir Python-CFFI anschauen.

Ich bringe das immer an wenn es irgendwie um C und Python geht, denn das Modul ist einfach nur genial. Funktioniert auch ziemlich gut mit PyPy!
the more they change the more they stay the same
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@BugsBunny74: da in Deinem gezeigten Abschnitt weder pArgs noch pValue vorkommen, außer natürlich "achtet auf die Referenzen".
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

@Sirius3

Natürlich! Etwas verwirrend, ich gebe es zu. (-; pArgs und pValue stammen aus der Funktion die die Python Funktion mit dem Callback aufruft und diese habe ich in dem Fall nicht gepostet. Letzteres kann ich nur unterstreichen!

@Dav1d
Besten Dank für den Tip, werde ich mir mal anschauen. Problematik in meinem Falle war eine Schnittstelle zwischen Python und Java zu implementieren. Brauchte also zwingend die Möglichkeit Python in meinem C-Code auszuführen.

Grüsse,
Bugs
BlackJack

@BugsBunny74: Jython oder bestehende Java/Python-Anbindungen wären keine Lösung gewesen? Frage nur aus Neugier.
BugsBunny74
User
Beiträge: 8
Registriert: Montag 8. Februar 2016, 13:14

Hallo BlackJack,

sorry die späte Antwort, aber lese den Beitrag leider erst jetzt wegen stetigen Zeitmangels.

Also, die eine Seite, ein in Python geschriebener Kommunikationsstack (XMPP) bestand bereits auf den mittels
Java zugegriffen werden musste. Eine Implementierung des Stacks in Jython war deshalb nicht möglich, zumal der
Stack auf Python3 basierte. Bin mir grad garnicht sicher ob Jython und Python3 überhaupt kompatibel sind?!

Bestehende Java/Python Anbindungen würde mich sehr interessieren was du da genau meinst. Die einzig pragmatische,
bzw. schnell umsetzbare, Lösung habe ich in der DLL mit Python embedded gesehen. Hast du was bestehende Java/Python
Bridges angeht vieleicht den ein oder anderen Link für mich?

Besten Dank!

Grüsse,
Bugs
BlackJack

@BugsBunny74: Solange es nur Python-Code ist und der nicht über irgendwelche Besonderheiten von Jython stolpert, kann man den auch von Java aus benutzen. Das kann so ähnlich wie einbetten in C aussehen. Man kann von Java aus einen Jython-Interpreter als Objekt erstellen und dort dann Python-Code ausführen. Es gibt eine `PyObject`-Java-Klasse von der Objekte für die Python-Grunddatentypen abgeleitet sind, so ähnlich sie es eine `PyObject`-Struktur in C für CPython gibt und spezielisiertere Strukturen für die Grunddatentypen.

Die Version ist hier aber definitiv ein Problem, weil Jython kein Python 3 kann.

Ich habe selbst noch keine alternative Anbindung verwendet, und Jython nur in der anderen Richtung — von Python aus Java-Bibliotheken verwenden. Ich weiss aber das es JPype und noch mindestens ein anderes Projekt gibt. Kann da aber auch nicht sagen welche Versionen die unterstützen, also insbesondere ob es Python 3 Anbindungen gibt.
Antworten