ctypes: Fremden Typ als Argument nutzen

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.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hi,

bei meinen Versuchen, die WebKit-API mit möglichst wenigen Abhängigkeiten anzusrpechen, probiere ich mich derzeit an einem Weg per [mod]ctypes[/mod]:

Code: Alles auswählen

In [1]: import ctypes

In [2]: from ctypes.util import find_library

In [3]: webkit = ctypes.CDLL(find_library('webkit-1.0'))

In [4]: webkit.webkit_network_request_new
Out[4]: <_FuncPtr object at 0x8bf9ecc>
Die Funktion kann also grundsätzlich angesprochen werden (da ich keine Dokumentation fand, habe ich in den Headerdateien gestöbert) und ist so definiert:

Code: Alles auswählen

WEBKIT_API WebKitNetworkRequest *
webkit_network_request_new      (const gchar          *uri);
So wie ich das sehe, kann hier eine beliebige URL mitgegeben werden, um so ein Request-Objekt zu erhalten (oder wie auch immer man das in C nennen würde).

Die Frage ist jetzt nur: Wie sage ich `ctypes`, dass es meinen String als `gchar` übergeben soll? In der Moduldokumentation wird zwar erklärt, wie man "normale" C-Typen benutzt, die ja über die Attribute von `ctypes` genutzt werden können, jedoch finde ich dort nichts über fremde Typen. :(

EDIT: Achso, als Abhängigkeit habe ich das Debianpaket `libwebkit-1.0-1` installiert.
BlackJack

Du musst halt herausfinden wie der Typ `gchar` definiert ist. Ich würde mal auf einen Alias für gewöhnliche `char`\s tippen!?

Was Du Da bekommst, ist übrigens in C ausgedrückt ein Zeiger auf eine `WebKitNetworkRequest`-Struktur.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ja, richtig. Die `gtypes.h` definiert `gchar` so:

Code: Alles auswählen

typedef char   gchar;
Das `const gchar *uri` von oben entspricht doch dann einem `c_char_p`, oder?

Ich habe mal beides versucht:

Code: Alles auswählen

In [21]: webkit.webkit_network_request_new.argtypes = [ctypes.c_char]

In [22]: webkit.webkit_network_request_new('http://www.google.de/')
---------------------------------------------------------------------------
ArgumentError                             Traceback (most recent call last)

/home/sebastian/<ipython console> in <module>()

ArgumentError: argument 1: <type 'exceptions.TypeError'>: wrong type

In [23]: webkit.webkit_network_request_new.argtypes = [ctypes.c_char_p]

In [24]: webkit.webkit_network_request_new('http://www.google.de/')

(process:4129): GLib-GObject-CRITICAL **: /build/buildd-glib2.0_2.20.1-2-i386-hGzT8z/glib2.0-2.20.1/gobject/gtype.c:2458: 
initialization assertion failed, use IA__g_type_init() prior to this function

(process:4129): GLib-CRITICAL **: g_once_init_leave: assertion `initialization_value != 0' failed
...und danach komme ich nicht mehr in die Shell zurück. Strg+C bewirkt gar nichts und ansonsten kann ich Buchstaben eingeben, die zwar da stehen, aber nichts bewirken. Auch nicht nach <Return>.

Oder habe ich da was falsch verstanden? Okay, die Exception wird wahrscheinlich geworfen, weil er vorher sieht, dass er eben keinen einzelnen `char` bekommt. Aber unabhängig davon: Muss man vielleicht einen eigenen Typen erstellen, der als `gchar` benannt und halt vom Typ `c_char` ist? Wie würde man das machen?

EDIT: Das drückt allerdings immer noch keinen Zeiger aus. Ich bin verwirrt gerade. :(
Darii
User
Beiträge: 1177
Registriert: Donnerstag 29. November 2007, 17:02

Lies die Fehlermeldung, er meckert beim 2. Mal gar nicht über den Pointer.
initialization assertion failed, use IA__g_type_init() prior to this function
Laut http://www.mail-archive.com/gtk-app-dev ... 10670.html fehlt da ein gtk_init, ansonsten würde ich mich einfach mal an die API-Doku halten, demnach ist diese Funktion zumindest nicht öffentlich.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Klappt leider nicht:

Code: Alles auswählen

#!/usr/bin/env python

import ctypes
from ctypes.util import find_library

gtk = ctypes.CDLL(find_library('gtk-x11-2.0'))

gtk.gtk_init()

Code: Alles auswählen

~$ ./test.py
Speicherzugriffsfehler
ansonsten würde ich mich einfach mal an die API-Doku halten, demnach ist diese Funktion zumindest nicht öffentlich.
Ja, schon. Aber ich habe bis jetzt keinen anderen Weg gefunden, um einen NetworkRequest direkt initialisieren zu können. Vielleicht geht es auch ganz einfach nicht so ohne Weiteres.

EDIT: Moment, das war viel zu voreilig. Argumente wollen ja auch noch mitgegeben werden...
BlackJack

@snafu: Schau doch einfach mal in die Doku, da wirst Du sehen, dass `gtk_init()` gerne zwei Argumente hätte. Du gibtst keine an, also werden einfach zwei Werte von Stack genommen und als Zeiger auf Speicherstellen verwendet. Wenn die nicht innerhalb des Datenbereichs des Prozessen liegen → Segfault beim Zugriff.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Oh, Mann. Es scheitert schon an der Initalisierung von gtk_init() (wieder Segfault). Was übersehe ich denn hier?

Code: Alles auswählen

import ctypes
from ctypes import c_char_p, c_int, POINTER
from ctypes.util import find_library
import sys


argc = len(sys.argv)
argv = (c_char_p * argc)(*sys.argv)

gtk = ctypes.CDLL(find_library('gtk-x11-2.0'))
gtk_init = gtk.gtk_init
gtk_init.argtypes = [c_int, POINTER(c_char_p)]
gtk_init(argc, argv)
Benutzeravatar
Trundle
User
Beiträge: 591
Registriert: Dienstag 3. Juli 2007, 16:45

Schau dir doch noch einmal an, welche Signatur `gtk_init()` hat.
"Der Dumme erwartet viel. Der Denkende sagt wenig." ("Herr Keuner" -- Bertolt Brecht)
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Funktionierts so?

Code: Alles auswählen

import ctypes
from ctypes import c_char_p, c_int, POINTER, byref
from ctypes.util import find_library
import sys


argc = len(sys.argv)
argv = (c_char_p * argc)(*sys.argv)

gtk = ctypes.CDLL(find_library('gtk-x11-2.0'))
gtk_init = gtk.gtk_init
gtk_init.argtypes = [POINTER(c_int), POINTER(POINTER(c_char_p))]
gtk_init(byref(argc), byref(argv))
BlackJack

@snafu: Wie sehen denn Deine C-Kenntnisse und Erfahrung aus? Ohne sollte man IMHO die Finger von `ctypes` lassen.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@BlackJack:

Meine C-Kenntnisse halten sich so in Grenzen. Ich kann's einigermaßen lesen aber nicht so gut "sprechen". Dazu kommt dann noch das Problem, das ganze in `ctypes` auszudrücken.

Ich habe jetzt bemerkt, dass ich auf den Inhalt des Arrays zeige, aber die Funktion verlangt das Array selbst (sind ja auch 3 Zeiger in der Signatur). Mein Problem ist gerade, welchen Typ ich `POINTER` (oder besser: `POINTER(POINTER)`) mitgeben muss. Einen Typ `Array` finde ich nämlich in der Auflistung nicht. Mag aber auch sein, dass ich's mir gerade schwerer mache als es ist, da C wie gesagt noch relatives Neuland für mich ist.
fred.reichbier
User
Beiträge: 155
Registriert: Freitag 29. Dezember 2006, 18:27

Du könntest dir den ctypes-Code auch generieren lassen. Ich hab da mit wraptypes von pyglet gute Erfahrungen gemacht ;)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

rayo hat geschrieben:Funktionierts so?

Code: Alles auswählen

[...]
gtk_init.argtypes = [POINTER(c_int), POINTER(POINTER(c_char_p))]
gtk_init(byref(argc), byref(argv))
Das erste Argument in Zeile 3 kann auf keinen Fall funktionieren. Schließlich ist `argc` ein Python-Integer. Was gehen würde, ist `byref(c_int(argc))`, aber ich weiß nicht, ob man unbedingt über die Referenz gehen muss.

Und der doppelte Zeiger bezieht sich auf das Array (hab ich wie gesagt auch schon bemerkt), du sagst ihm allerdings, dass ein `char*` kommt, was ebenfalls zum Fehler führt. Alle drei Zeiger dürften damit abgedeckt sein, aber man weiß halt nicht den Typ für `POINTER`.
rayo
User
Beiträge: 773
Registriert: Mittwoch 5. November 2003, 18:06
Wohnort: Schweiz
Kontaktdaten:

Klar muss man ueber die Referenz gehen:

Code: Alles auswählen

void gtk_init(int *argc, char ***argv);
Also das erste Argument ist ein Pointer auf argc, mittels byref uebergibst du den Pointer.

Das 2. Argument ist ein Pointer auf einen Array von Strings, darum der 3 fache Pointer, Pointer auf Array, Array selbst ist ein Pointer auf das erste Element und das Element selbst ist ein Array of Char. Darum POINTER(POINTER(char_p)).

So jetzt mit argc als c_int. Funktionierts so?

Code: Alles auswählen

import ctypes
from ctypes import c_char_p, c_int, POINTER, byref
from ctypes.util import find_library
import sys


argc = c_int(len(sys.argv))
argv = (c_char_p * argc)(*sys.argv)

gtk = ctypes.CDLL(find_library('gtk-x11-2.0'))
gtk_init = gtk.gtk_init
gtk_init.argtypes = [POINTER(c_int), POINTER(POINTER(c_char_p))]
gtk_init(byref(argc), byref(argv))
[/code]
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@ fred.reichbier:

Der wirft mir schon nen Fehler wenn ich nur das Beispiel aus dem Docstring der __init__.py ausprobieren will.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@ rayo:

Nein, immer noch das selbe:

Code: Alles auswählen

Traceback (most recent call last):
  File "./test.py", line 16, in <module>
    gtk_init(byref(argc), byref(argv))
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: expected LP_LP_c_char_p instance instead of pointer to c_char_p_Array_1
Übrigens muss man natürlich oben aufpassen, dass man einmal die von Python ermittelte Länge der `sys.argv`-Liste nimmt, um die Größe des Arrays mittels Multiplikation festzulegen und erst danach das `c_int`-Objekt für die Übergabe als Funktionsparameter für C. ;)
BlackJack

@snafu: Einen Typ Array gibt's indirekt über das Multiplizieren von Typen mit einer Zahl, aber Zeiger und Array sind ja in C fast das gleiche. Genau wie in C kannst Du mit `ctypes`-Zeigern auch Indexzugriffe machen. Bei `gtk_init()` sehe ich in der Signatur aber auch kein Array, sondern einen Zeiger auf einen Zeiger auf einen Zeiger auf `char`. Zeiger auf `char` gibt's schon als `c_char_p`, also müssen nur noch zwei der Sternchen durch `POINTER()` ersetzt werden.

Anscheinend ist `ctypes` da "typsicherer" als C, also bleibt nur `cast()`\en.
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Juchhu.

Code: Alles auswählen

#!/usr/bin/env python

from ctypes import byref, cast, c_char_p, CDLL, c_int, POINTER
from ctypes.util import find_library
import sys


def gtk_init(argv=sys.argv):
    assert isinstance(argv, list)
    args_no = len(argv)
    argc = c_int(args_no)
    argv = cast((c_char_p * args_no)(*argv), POINTER(c_char_p))

    gtk = CDLL(find_library('gtk-x11-2.0'))
    gtk_init = gtk.gtk_init
    gtk_init.argtypes = [POINTER(c_int), POINTER(POINTER(c_char_p))]
    return gtk_init(byref(argc), byref(argv)) == 1


if __name__ == '__main__':
    print gtk_init()
`ctypes` und ich werden, glaube ich, noch gute Freunde. :)
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal ein Beispiel für ein `web_view` in einem Fenster. Mit dem Herumreichen von Variablen sozusagen innerhalb des C-Kontextes habe ich also kein Problem.

Schwierig wird allerdings das testweise Auslesen der URL aus meinem `network_request`. Signatur:

Code: Alles auswählen

const gchar *       webkit_network_request_get_uri      (WebKitNetworkRequest *request);
In meinem Ansatz sage ich einfach, dass die Rückgabe von Typ `c_char_p` ist, aber das reicht offenbar nicht. Ich bekomme nur irgendwelche komischen Zeichen zurück. :(
BlackJack

Was hättest Du denn jetzt da erwartet? Woher soll da auf magische Weise eine URI kommen?
Antworten