Struct in `ctypes` abbilden

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hi,

ich brauche für ein Projekt ein paar Sachen aus der C-API der `libsoup`, die ich gerne per `ctypes` in mein Python-Programm holen würde. Für's erste erstmal nur den Contenttype einer URL.

Die nötigen Zeilen in C funktionieren: http://paste.pocoo.org/show/121785/

Ich benutze ein eigenes Python-Modul, das sozusagen die Schnittstelle zwischen C und Python darstellt: _chooks.py.

Ich habe mich für den Struct an die Doku zur SoupMessage gehalten.

Nun versuche ich mich in meinem Hauptmodul an einer Umwandlung und genau da kracht es:

Code: Alles auswählen

Python 2.6.2 (r262:71600, Jun  3 2009, 17:11:54)
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from weblib import Session
>>> session = Session()
>>> session.get('http://www.google.de/')
Speicherzugriffsfehler
Was ist nun das Problem? Mache ich bei der Klasse für den Struct etwas falsch, unterstützt `ctypes` keine `cast()`s für eigene Klassen oder ist es was ganz anderes?
Benutzeravatar
birkenfeld
Python-Forum Veteran
Beiträge: 1603
Registriert: Montag 20. März 2006, 15:29
Wohnort: Die aufstrebende Universitätsstadt bei München

Ich bin ctypes nicht gewohnt, aber ist es nicht normal, anstatt zu casten ``soup_message_new.restype = POINTER(SoupMessage)`` zuzuweisen?
Dann lieber noch Vim 7 als Windows 7.

http://pythonic.pocoo.org/
fred.reichbier
User
Beiträge: 155
Registriert: Freitag 29. Dezember 2006, 18:27

Der Rückgabewert von soup_message_new ist nicht ein SoupMessage - Struct, sondern ein Pointer zu einem. Du müsstest also als zweites Argument für `cast` POINTER(SoupMessage) benutzen. Glaube ich.
Aber birkenfelds Lösung ist die schönere :p
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ja, das scheint auch grundsätzlich zu funktionieren und auf die Werte kann ich jetzt auch zugreifen. Allerdings scheint mir das immer noch nicht so ganz koscher zu sein. Werde mich später nochmal melden. Erstmal danke. :)
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich bräuchte jetzt doch nochmal Hilfe. Zunächst einmal glaube ich, dass birkenfelds Vorschlag keine so gute Idee ist. Grund: Das liefert mir in jedem Fall ein `ctypes.Structure`-Objekt zurück. Ich muss aber AFAIK mit dem Originalobjekt weiterarbeiten, weil C mich sonst nicht mehr versteht. `restype` ist wohl nur dann angebracht, wenn man weiß, dass man das Ergebnis nur noch in Python verwenden möchte. Hunderprozentig sicher bin ich mir bei der Sache aber nicht. Wäre schön, falls jemand hier Klarheit schaffen könnte.

Und jetzt zu meiner von `Structure` abgeleiteten Klasse, die ja so aussieht:

Code: Alles auswählen

class SoupMessage(Structure):

    _fields_ = [('method', c_char_p),

                ('status_code', c_int),
                ('reason_phrase', c_char_p),

                ('request_body', c_int),      # SoupMessageBody
                ('request_headers', c_int),   # SoupMessageHeaders

                ('response_body', c_int),     # SoupMessageBody
                ('response_headers', c_int)]  # SoupMessageHeaders
Die letzten 4 `c_int`s waren erstmal als Dummy-Objekte gedacht. Die Klassen wollte ich danach schreiben. Jetzt besteht aber das selbe Problem wie schon gerade erwähnt: Wenn ich `response->response_headers` auf die Nachricht anwenden will (Zeile 28 meines C-Codes), dann wäre das für meine `SoupMessage(Structure)` in Python `response.contents.response_headers`. Das würde mir ein `ctypes`-Objekt zurückliefern (entweder `c_int` oder eine eigene `Structure`-Instanz). Nun kann ich damit aber nichts mehr in C anfangen. Denn der nächste Schritt wäre ja:

Code: Alles auswählen

const char* contenttype = soup_message_headers_get_content_type (headers, NULL);
Meine Frage ist also: Wie verweise ich mit `ctypes` auf das ursprüngliche C-"Objekt"? Intern scheint es damit ja keine Probleme zu haben, ich werde nur aus der Doku nicht schlau, welche Methode dafür genommen wird. Also das "echte" C-`headers` soll an den Namen `headers` in Python gebunden werden. Über den besagten `Structure`-struct - versteht sich.
fred.reichbier
User
Beiträge: 155
Registriert: Freitag 29. Dezember 2006, 18:27

Du kannst eine ctypes.Structure-Instanz problemlos weiterverwenden, auch bei Aufrufen von C-Funktionen.
snafu hat geschrieben:Ich muss aber AFAIK mit dem Originalobjekt weiterarbeiten, weil C mich sonst nicht mehr versteht.
Wie kommst du darauf?
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich möchte aber eigentlich keine `Structure`-Instanz weiterverwenden, sondern ich müsste wissen, wie ich die letzten 4 Elemente meiner eigenen `Structure`-Klasse konvertieren muss, damit sie sich weiterhin auf die Originale beziehen. Es muss doch irgendeine Art Vermerk geben, den `ctypes` als Typ benutzt, um sich die Speicheradresse der Objekte zu merken. Also konkret: In was muss `('request_headers', c_int)` ersetzt werden, damit mir das Resultat auf die Headers (also so, dass C etwas damit anfangen kann) zeigt? `c_int` ist es ja wohl nicht.
BlackJack

@snafu: Wenn es kein`c_int` ist, dann musst Du halt herausfinden *was* es ist. Das `Structure`-Exemplar ist eine "Python-Sicht" des originalen Speicherbereichs und wie schon gesagt wurde, kannst Du die Teile auch wieder so an C übergeben. Das ist ja gerade der Sinn von den Dingern. Wenn sie natürlich falsch deklariert sind…

Was hat `request_headers` denn für einen Typ?

Edit: Hab gerade mal geschaut, das ist da also eine Zeiger auf so ein `struct`, dass nicht näher beschrieben wird. Wenn man also immer nur mit diesem Zeiger hantiert, würde ich den einfach als `c_void_p` deklarieren.
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Das will immer noch nicht.

weblib.py

_chooks.py

Ausgabe:

Code: Alles auswählen

>>> from weblib import Session
>>> session = Session()
>>> google = session.get('http://www.google.de/')
>>> google.method
"\xd8\x87'\x08\xa0\x1d\x1d\x08\xc0\xa2\x9d\xb7\xc0^\x8c\xb7@]\x8c\xb7`j\x9d\xb7\x80`\x8c\xb7\xd0\x90\x9d\xb7"
Liegt es vielleicht an dem hier?

Code: Alles auswählen

    def get(self, url):
        GET = create_message('GET', url)
        send_message(self._session, GET)
        return Response(GET)
Also im C-Code kann man das veränderte `GET` (das eine Nachricht bekommen hat) ja so zurückgeben, aber würde Python es nicht eher so verstehen, dass es das Ergebnis von `create_message()` zurückliefert (also den Schritt des Sendens hier nicht beachtet)?

Vielleicht sollte ich mich doch besser an einer Python-Extension in C versuchen, wenn das auch nach einem ersten flüchtigen Blick alles andere als einfach aussieht. :(
BlackJack

@snafu: Ja es wird das Ergebnis von `create_message()` zurückgegeben, genau wie das in C auch passieren würde. Also wo soll da das Problem sein? Das ist ein Zeiger auf Speicher. Und wenn in dem Speicher etwas verändert wird, dann "sieht" man diese Veränderung natürlich auch über den Zeiger. Selbst wenn es direkt ein `struct` wäre, dann ist das Python-Objekt ja nur ein Proxy dafür und auch dann bekäme man Änderungen in den Daten mit.

Kann es sein, dass das Ergebnis noch gar nicht da ist? Ich sehe das das Wörtchen `async` bei der Sitzung und etwas mit Threads.

Edit: Bevor Du direkt in C anfängst, würde ich Pyrex bzw, Cython empfehlen. Das nimmt einem viel fehlerträchtige Arbeit ab.
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Mit einer `sync_session` funktioniert es leider auch nicht.

Ja, ich werde mir die beiden Sachen mal ansehen. Danke.
fred.reichbier
User
Beiträge: 155
Registriert: Freitag 29. Dezember 2006, 18:27

Hallo!

Keine Ahnung, ob das wirklich der Grund des Problems ist, aber ich halte es für möglich (EDIT: das war wirklich das Problem. http://paste.pocoo.org/show/121967/ <- hier eine _chooks.py - Version mit einem Dummy-Member von 12 Bytes, das `_GObject`-Struct ist bei mir nämlich 12 Bytes groß - achtung, das ist nicht portabel ;) Diese Version gibt mir bei deinem Testskript 'GET' aus)

Wenn du dir die Definition von _SoupMessage im Quellcode mal ansiehst (bei mir in soup-message.h:23), dann siehst du, dass da noch ein Member vor `method` ist:

Code: Alles auswählen

struct _SoupMessage {
	GObject parent;

	/*< public >*/
	const char         *method;

	guint               status_code;
	char               *reason_phrase;

	SoupMessageBody    *request_body;
	SoupMessageHeaders *request_headers;

	SoupMessageBody    *response_body;
	SoupMessageHeaders *response_headers;
};
Du müsstest also entweder auch GObject wrappen (das wäre ein bisschen Aufwand ;)) oder einen Dummy-Member einsetzen, der dieselbe Größe wie ein GObject hat (Achtung, `parent` ist kein Pointer). Das ist aber nicht besonders schön. Vielleicht wäre es besser, auf g_object_get zurückzugreifen, um das Property "method" zu bekommen.

Gruß,

Fred
Benutzeravatar
snafu
User
Beiträge: 6861
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich glaube, ich mache das jetzt so: Ich programmiere die benötigten Funktionen nativ in C, so dass immer nur Strings und Zahlen herauskommen können. (Mal gucken, ob ich da ein gutes Konzept finde.) Dann schreibe ich mir ein Headerfile für die Sachen, die ich in Python nutzen will, wrappe dieses Headerfile mit Cython und gebe dem ganzen dort den "objektorientierten Touch". ;)

Ich habe nämlich keine Lust, alle möglichen Dinge bis ins tiefste GObject zu nachzuimplementieren, nur für ein paar Abfragen. Deine zweite Idee mag zwar auch umsetzbar sein, aber selbst das geht mir eigentlich schon zu weit von der ursprünglichen API weg.

Ich versuche jetzt erstmal, Cython zu verstehen und das Gesagte umzusetzen.

Oh, das hier klingt gut:
If the header file declares a big struct and you only want to use a few members, you only need to declare the members you’re interested in. Leaving the rest out doesn’t do any harm, because the C compiler will use the full definition from the header file.
Antworten