Objektorienierte C-dll in Python einbinden

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

Ich habe ein C Programm, das (ansatzweise) objektorienitert verfasst ist. Hierbei wird eine Datenstruktur generiert und als Referenz an die Funktionen übergeben. Die Funktionen arbeiten im Hintergrund immer mit dieser Datenstruktur!

Ich habe aus den Funktionenen und deren Unterfunktionen eine dll generiert. Ich möchte diese Funktionen via ctypes in Python als module einbinden. Ich habe bereits etwas mit ctypes und Python gearbeitet. Allerdings kenne ich mich mit ctypes und Python nicht wirkich aus (Bin C-Coder). Ich habe zwar bereits ein kleines Beispielprogramm via ctypes in Python zum Laufen gebracht. Aber beim konkreten Problem meckert ctypes z.B. bei "_fields_ rum wenn ich versuche eine Structure mit einem einzigen Element anzulegen(Class via Structure erstellen). Habe auch versucht, einfach mal ein array direkt aus dem channel_t zu generieren. Allerdings habe ich mit der Pointerdarstellung immense Probleme.

Kann mir bitte jemand helfen? Ein Beispielprogramm wäre super. Vor allem bräuchte ich Hilfe beim Pointerzeugs.

Hier die benötigten Structs:

Code: Alles auswählen

typedef struct channel_tag channel_t;

struct channel_tag {
	unsigned short *bla;
	unsigned short *muh;
	unsigned short type;
	unsigned char ch_number;
};
/*---*/


typedef struct tag gen_t;

struct tag {
	channel_t channel_number[16];
};

Hier die function prototypes:

Code: Alles auswählen

unsigned short ctor(gen_t *me);
/*---*/
unsigned short channel_ctor(channel_t *, unsigned short,  unsigned short,  unsigned short);
/*---*/
void copy_data(unsigned short *data);
/*---*/
void update_all_channels(void);
Hier ein main program:

Code: Alles auswählen

gen_t gen;

void main(){
int i;
unsigned short output[256][16];

ctor(&gen); //Rückgabewert wichtig, hier wird es initialisiert
channel_ctor(&gen.channel_number[0], 1, 20, 50);//Rückgabewert wichtig, hier wird ein channel aus der konkreten Datensturktur initialisiert

for(i = 0; i < 256; i++){
    copy_data((unsigned short)(&output[i]));//Kopiere generierte Daten
    update_all_channels();//Berechne neue Daten -> nutzt gen Datenstruktur im Hintergrund
}

}
BlackJack

@ddpf: „Meckert rum” ohne konkrete Fehlermeldung und den dazugehörigen Quelltext ist keine Fehlerbeschreibung bei der man sinnvoll helfen könne. Was hast Du versucht, und was war das Ergebnis? Wenn es keine Fehlermeldung war, wie weicht das Ergebnis vom Erwarteten ab?

Eine Struktur mit nur einem Feld zu erstellen ist jedenfalls einfach und unterscheided sich zum Erstellen von einer mit mehreren Feldern nur dadurch, dass man nur ein Feld statt mehrere definiert. Wer hätte das gedacht. :-)

Also in Deinem Fall einfach:

Code: Alles auswählen

class GEN(Structure):
    _fields_ = [('channel_number', CHANNEL * 16)]
Bei entsprechender Definitionen von `CHANNEL`.

Du zeigst ja leider nicht, was Du in *Python* versucht hast, und wo die Probleme dort liegen. Also rate ich mal, dass beim Aufruf von `channel_ctor()` beim ersten Argument nicht nur eine 0 als Index vorkommen kann, denn dann wäre es ja einfach, weil man das Array selbst, beziehungsweise einen Zeiger darauf (was wegen der C-Semantik auf's selbe raus kommt), übergeben könnte.

Um einen Zeiger auf ein Element innerhalb eines Arrays zu bekommen, bietet `ctypes` leider keine vorgefertigte Lösung an. Die Adressarithmetik dafür muss man komplett selbst schreiben. Also Adresse des Arrays plus `i` mal Grösse eines Elements berechnen, dann wieder einen `ctypes`-Proxy für das Element aus dieser neuen Adresse erstellen und darauf dann einen Zeiger an `channel_ctor()` übergeben.

Warum steht im C-Beispielprogramm in den Kommentaren eigentlich, dass der Rückgabewert wichtig ist, aber er wird dann überhaupt nicht verwendet? Zeigt der Rückgabewert Fehler an? Also zum Beispiel 0 bedeutet kein Fehler? Dann solltest Du überlegen eine Funktion an das `errcheck`-Attribut der `ctypes`-Proxy-Objekte für die Funktionen zu binden, die im Fehlerfall eine Ausnahme auslöst.

Edit: Konnte ich natürlich nicht mit Deiner Bibliothek testen, und da Deine Beispiel-`main()` in dem C-Programm nicht unbedingt verrät welche Argumente ”fest” und welche ”variabel” sind und wie die Funktionen semantisch zusammenhängen, weiss ich auch nicht ob die `Gen`-Klasse so vernünftig ist:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from ctypes import (
    addressof, byref, c_ubyte, c_ushort, cast, CDLL, sizeof, POINTER,
    Structure
)

GEN_LIB = CDLL('./libgen.so')


class Error(Exception):
    pass


class CHANNEL(Structure):
    _fields_ = [
        ('blah', POINTER(c_ushort)),
        ('muh', POINTER(c_ushort)),
        ('type', c_ushort),
        ('ch_number', c_ubyte),
    ]


class GEN(Structure):
    _fields_ = [('channel_number', CHANNEL * 16)]


CHANNEL_PTR = POINTER(CHANNEL)
GEN_PTR = POINTER(GEN)


def _error_check(result, func, arguments):
    if result != 0:
        raise Error('%s%r returned %d' % (func, arguments, result))


_ctor = GEN_LIB.ctor
_ctor.argtypes = [GEN_PTR]
_ctor.restype = c_ushort
_ctor.errcheck = _error_check

_channel_ctor = GEN_LIB.channel_ctor
_channel_ctor.argtypes = [CHANNEL_PTR, c_ushort, c_ushort, c_ushort]
_channel_ctor.restype = c_ushort
_channel_ctor.errcheck = _error_check

_copy_data = GEN_LIB.copy_data
_copy_data.argtypes = [POINTER(c_ushort)]
_copy_data.restype = None

_update_all_channels = GEN_LIB.update_all_channels
_update_all_channels.argtypes = []
_update_all_channels.restype = None


class Gen(GEN):
    def __init__(self):
        GEN.__init__(self)
        _ctor(byref(self))
    
    def init_channel(self, i, a, b, c):
        _channel_ctor(
            byref(
                CHANNEL.from_address(
                    addressof(self.channel_number) + sizeof(CHANNEL) * i
                )
            ),
            a,
            b,
            c
        )
    
    def do_something(self):
        result = (c_ushort * 16 * 256)()
        for row in result:
            _copy_data(cast(row, POINTER(c_ushort)))
            _update_all_channels()
        return result


def main():
    gen = Gen()
    gen.init_channel(0, 1, 20, 50)
    result = gen.do_something()
    for row in result:
        print list(row)


if __name__ == '__main__':
    main()
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

Vielen Dank!
Langsam wird mir klar wie Python werkelt. Ich hätte da allerdings noch eine Frage. Was ist der Unterschied wenn ich die argtypes mit () anstatt [] klammere (Siehe Code)?

Mit []:

Code: Alles auswählen

_channel_ctor = GEN_LIB.channel_ctor
_channel_ctor.argtypes = (CHANNEL_PTR, c_ushort, c_ushort, c_ushort)
_channel_ctor.restype = c_ushort
_channel_ctor.errcheck = _error_check
Mit ():

Code: Alles auswählen

_channel_ctor = GEN_LIB.channel_ctor
_channel_ctor.argtypes = [CHANNEL_PTR, c_ushort, c_ushort, c_ushort]
_channel_ctor.restype = c_ushort
_channel_ctor.errcheck = _error_check
arg, war eh schon im Vorpost beantwortet...
Zuletzt geändert von ddpf am Montag 25. Februar 2013, 10:55, insgesamt 1-mal geändert.
BlackJack

@ddpf: In einem Fall (eckige Klammern) sind es Listen, im anderen Fall (runde Klammern) sind es Tupel. Wobei das nicht an den Klammern liegt! Bis auf das leere Tupel sind es die Kommata die das Tupel ausmachen und nicht die Klammern. Und das ist auch der Grund warum ich da lieber Listen nehme um dem „Sonderfall” das der Sequenz mit einem Element zu entgehen. Mal der Unterschied zwischen Listen mit 0, 1, und 2 Elementen und dann das selbe mit Tupeln:

Code: Alles auswählen

# Listen
a = []
b = [42]
c = [23, 4711]

# Tupel
a = ()
b = 42,
c = 23, 4711
Ob man bei Tupeln Klammern setzt auch wenn sie gar nicht nötig sind um syntaktische Mehrdeutigkeiten zu vermeiden ist Geschmackssache.
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

sorry, war typo... Ich seh den Wald vor lauter Bäumen nicht mehr :(

Aber danke für die super Hilfe!
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

Was ist eigentlich der Unterschied zwischen cast und byref?
BlackJack

@ddpf: Naja, das eine ist ein cast von einem (Zeiger)Typen in einen anderen und das andere erzeugt einen Zeiger auf einen Wert. Wobei `byref()` speziell für Funktionsaufrufe gedacht ist, wo man den Zeiger übergibt und nicht in Python verwendet. Dafür ist `pointer()` da. `byref()` ist „leichtgewichtiger” als `pointer()`.
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

Mir ist noch etwas unklar. Beim Konstruktor (Siehe Code-Schnippsel 1) wird_ctor aufgerufen und es wird die vererbte Datenstruktur übergeben. Da verwendet man gleich self, weil man alles übergeben will ( bei _channel_ctor wird wiederum z.B. auf self.channel_number zugegriffen weil man auf einen spezifischen Channel zugreifen will. Sprich Anfangswert vom Speicher+Block*i=konrketer Channel; Falls merhere Elemente in der Struktur drinnen wären, würde man da einfach das erste Element als Startwert auswählen , oder?).

Was ist wenn man die Eigenschaften der Klasse aus irgendeinen Grund erweitern will. Geht da immer noch der gleiche Schmäh vor weil Python "clever" genug ist (Siehe Code-Schnipsel 2)? Oder muss man dann spezifisch werden (Siehe Code-Schnipsel 3). Oder überge ich einfach das erste Element als Addresse (Siehe Code-Schnipsel 4). Oder something completly different?

(Es isch 4, oder?)

Code-Schnippsel 1:

Code: Alles auswählen

#cut
class Gen(GEN):
    def __init__(self):
        GEN.__init__(self)
        _ctor(byref(self))
#cut 
Code-Schnippsel 2:

Code: Alles auswählen

#cut
class Gen(GEN):
    def __init__(self, irgendwas):
        GEN.__init__(self)
        self.__irgendwas = irgendwas
        _ctor(byref(self))
#cut 

Code-Schnippsel 3:

Code: Alles auswählen

#cut
class Gen(GEN):
    def __init__(self, irgendwas):
        GEN.__init__(self)
        self.__irgendwas = irgendwas
        _ctor(byref(GEN))
#cut 
Code-Schnippsel 4:

Code: Alles auswählen

#cut
class Gen(GEN):
    def __init__(self, irgendwas):
        GEN.__init__(self)
        self.__irgendwas = irgendwas
        _ctor(byref(GEN.from_address(addressof(self.channel_number))))
#cut 
BlackJack

@ddpf: Du hast mich mit der letzten Frage im ersten Absatz schon abgehängt.

Code-Schnippsel 2 dürfte das sein was Du willst. Ausser das ein Unterstrich bei `_irgendwas` sein sollte (oder vielleicht auch gar keiner). Zwei Unterstriche ist nicht „private” sondern „name mangeling” und dazu da, dass bei tiefer oder Mehrfachvererbung keine ungewollten Namenskollisionen entstehen. Dran kommen kann man an die Attribute trotzdem wenn man den Namen kennt.

Code-Schnippsel 3 funktioniert nicht weil `GEN` eine Klasse ist, die einen C-*Datentyp* repräsentiert und auf C-Typen kann man keine Pointer erstellen, nur auf C-Werte.

Code-Schnippsel 4 könnte gehen weil die Adresse von `self` gleich der Adresse vom ersten (und einzigen) Feld in der C-Struktur ist, aber das ist halt total unnötig umständlich. Das ist als wenn man in C statt ``ptr = &gen;`` das hier schreiben würde: ``ptr = (GEN*)(&gen.channel_number);``. Kommt auf's selbe hinaus, aber ist doch irgendwie umständlich. ;-)
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

@blackjack:
Laut http://openbook.galileocomputing.de/pyt ... 9c385b9ec3
heißt _: Das solltest du nicht von außen verwenden
und __: Das kann man man nicht von außen verwenden

Ich lasse mich natürlich gern eines besseren belehren. Es wäre aber toll da was zum Lesen zu haben. Mehrfachvererbung ist ja einer der Sache wo man sehr schnell ins Klo greifen kann.
BlackJack

@ddpf: Das Buch taugt nicht viel wenn es um OOP *in Python* geht.

Code: Alles auswählen

In [8]: class A:
   ...:     def __init__(self):
   ...:         self.__answer = 42
   ...: 

In [9]: a = A()

In [10]: a._A__answer
Out[10]: 42
So viel dazu dass man da nicht drauf zugreifen kann. Zuweisungen gehen auch genau so.

Wem ein „Da sollst Du nicht drauf zugreifen (solange Du nicht gute Gründe hast).” nicht ausreicht, der ist bei Python IMHO sowieso falsch.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Oder um mal aus PEP 8 zu zitieren:
http://www.python.org/dev/peps/pep-0008/ hat geschrieben:_single_leading_underscore: weak "internal use" indicator. E.g. from M import * does not import objects whose name starts with an underscore.

single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g.

Tkinter.Toplevel(master, class_='ClassName')

__double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).

__double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.
Das Leben ist wie ein Tennisball.
ddpf
User
Beiträge: 21
Registriert: Freitag 22. Februar 2013, 12:13

Sprich mit __irgendwas wird nichts nach oben weitervererbt.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

"nach oben" wird nie etwas vererbt. Und nein, Attribute mit zwei fuehrenden Unterstrichen werden auch weiter vererbt (nach unten!), nur nicht als `__foo` sondern eben als `_NameOfDefiningClass__foo`.
Antworten