Problem mit Daten in ctypes

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Hallo zusammen,
ich habe ein Problem, Daten über eine Funktion einer DLL abzurufen. Ich will von einem RFID-Reader Daten auslesen, dazu habe ich mit einer DLL mit dem Befehl

Code: Alles auswählen

b = WinDLL("C:\\...\\nameofdll.dll")
verbunden.
Mit der Funktion Inventory_G2 kann man nun die RFID-Daten abrufen. Mein Befehl sieht so aus:

Code: Alles auswählen

b.Inventory_G2(c_ubyte(int("ff", 16)), c_ubyte(0), c_ubyte(0), c_ubyte(0), (c_ubyte*1000)(), None, None, c_long(int("33f0000", 16)))
Die Fehlermeldung dazu lautet:
WindowsError: exception: access violation reading 0x00000030

Vom Hersteller wurde Quellcode in C# bereitgestellt, der die Funktion wie folgt erfolgreich verwendet:

Code: Alles auswählen

byte AdrTID = 0;
byte LenTID = 0;
byte TIDFlag = 0;
CardNum=0;
Totallen = 0;
byte[] EPC=new byte[5000];
fComAdr = 0xFF;
fCmdRet = StaticClassReaderB.Inventory_G2(ref fComAdr,AdrTID,LenTID,TIDFlag, EPC, ref Totallen, ref CardNum, frmcomportindex);
Kann mir jemand helfen? Wie rufe ich die Funktion korrekt in Python auf?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@manlud80: in der Signatur der Funktion gibt es mehrere Parameter, die eine Referenz (Pointer) auf die Variablen erwarten und nicht ihren Wert.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Super, das hat schonmal geholfen... Mit

Code: Alles auswählen

b.Inventory_G2(byref(c_ubyte(int("ff", 16))), c_ubyte(0), c_ubyte(0), c_ubyte(0), test, None, byref(c_ubyte(0)), c_long(int("33f0000", 16)))
bekomme ich schonmal "nur noch" die Fehlermeldung "55" zurück, aber noch keine Daten.
Kann mir jemand sagen, was ich noch falsch mache?
Vielen Dank schonmal Sirius3.
BlackJack

@manlud80: Wo sollten die Daten denn stehen? Und darf man bei `Totallen` aus dem C#-Beispiel einen NULL-Pointer angeben? Ich weiss auch nicht ob es so gut ist bei `byref()` direkt ein erzeugtes `ctypes`-Objekt zu übergeben weil `byref()` in der Dokumentation als ”leichtgewichtige” Alternative zu `pointer()` speziell für Funktionsaufrufe beschrieben wird. Es kann also gut sein dass diese Funktion nicht dafür sorgt das es eine Referenz auf das Argument gibt solange der Rückgabewert der Funktion verwendet wird und man sich so Pointer auf nicht (mehr) existierende Objekte/Speicherbereiche schafft. Ich würde an der Stelle das C#-Beispiel ähnlicher Nachbauen und vor dem Aufruf die Werte an Namen binden die da als Pointer übergeben werden.

Dieses `int('...', 16)` für die Hexadezimalzahlen ist übrigens unnötig umständlich. Python versteht auch die Notation für Hex-Literale wie bei der C-Sprachfamilie, also beispielsweise einfach ``0xff`` statt ``int('ff', 16)``.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Vielen Dank dafür.
Die Daten müssen in "test" stehen, wobei

Code: Alles auswählen

test=(c_ubyte*5000)()
ist.
Den Nullpointer habe ich jetzt durch ein c_ubyte(0) ersetzt und die byref durch pointer ersetzt. Leider immer noch der gleiche Rückgabewert ("55"). Kann es sein, dass meine test-Variable falsch ist?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@manlud80: was heißt denn 55? Es muß doch irgendwo eine Fehlermeldungsbeschreibung geben.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Nein, leider nicht. In der DLL-Beschreibung steht, dass bei erfolgreich ausgeführtem BEfehl die 0 zurückgegeben wird.
BlackJack

@manlud80: Was hast Du denn da genau? Welches Gerät? Wie heisst die DLL? Woher hast Du die Dokumentation?

Wenn ich nach RFID und dem Funktionsnamen im Netz suche, finde ich beispielsweise dieses PDF: http://www.rfidshop.com.hk/datasheet/UH ... 20v1.2.pdf

Da steht bei Fehlercode 55 (0x37) „Invalid Handle“, was mir auch sinnvoll erscheint, denn die 0x33f0000 fällt in Deinem Code ja irgendwie vom Himmel herab. Wo hast Du den Wert her? In der Dokumentation stehen die Funktionen mit denen man den tatsächlichen, dynamischen Wert bekommt.

Edit: Auch wenn das bei Windows nicht zwingend nötig ist, würde ich mir erst einmal die Funktionen aus der DLL holen und die Signaturen definieren. Dann sehen die Aufrufe etwas weniger überfrachtet aus und sind auch sicherer. Ausserdem kann man den Rückgabewert dann auch gleich in eine Ausnahme umwandeln lassen wenn etwas nicht geklappt hat und kann ”pythonischeren” Code schreiben. Eventuell auch das Handle in ein Objekt kapseln.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Ja, ich habe so ein ähnliches Gerät, die Inventory-Funktion ist nahezu identisch.

Ups, die Fehlercodes hatte ich bisher noch nicht gesehen. Bisher kam ich auch noch nie bis dort.

Das handle habe ich von der WinDLL b(siehe mein erster Post):

<WinDLL 'C:\...\nameofdll.dll', handle 33f0000 at 2d15fd0>

Die Funktion AutoOpenComPort() soll eigentlich einen gültigen handle übergeben, ich bekomme aber nur eine 0 für "erfolgreich verbunden".
BlackJack

@manlud80: Das ist das Handle für die DLL, Du brauchst eines für einen geöffneten COM-Port das mit einer der beiden Open-Funktionen zu bekommen ist. Der Rückgabewert dieser Funktionen ist ja nicht das einzige Ergebnis. Das scheint immer eine Art Fehlercode zu sein. Alle anderen Werte werden über die Zeiger zurückgegeben die als Argumente übergeben werden.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Ok, danke. Kannst du mir sagen, wie ich auf diese Werte zugreifen kann?
BlackJack

@manlud80: Du musst die Werte halt an Namen binden und beim Aufruf eine Referenz auf den Wert übergeben. Die C-Funktion ändert dann den Wert die von dem jeweiligen `ctypes`-Objekt gekapselt wird.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Nicht direkt eine Lösung für dein Problem (und auch gerade unrelated), empfehle dir aber cffi anzuschauen. Effizienter als ctypes und um seeeehr viel einfacher zu handhaben.
the more they change the more they stay the same
BlackJack

Vollkommen ungetestet:

Code: Alles auswählen

from ctypes import byref, c_long, c_uint8, POINTER, WinDLL

READER_DLL = WinDLL('nameofdll.dll')
(
    BAUD_9600,
    BAUD_19200,
    BAUD_38400,
    _,
    BAUD_56000,
    BAUD_57600,
    BAUD_115200
) = xrange(7)


class ReaderException(Exception):

    def __init__(self, error_code):
        Exception.__init__(self, 'Error code 0x{0:x}'.format(error_code))
        self.error_code = error_code


def _error_check(result, _func, _arguments):
    if result:
        raise ReaderException(result)
    return result


_auto_open_com_port = READER_DLL.AutoOpenComPort
_auto_open_com_port.argtypes = (
    POINTER(c_long), POINTER(c_uint8), POINTER(c_uint8), POINTER(c_long)
)
_auto_open_com_port.restype = c_long
_auto_open_com_port.errcheck = _error_check


def auto_open_com_port(port=0, com_address=0xff, baud=BAUD_57600):
    port = c_long(port)
    com_address = c_uint8(com_address)
    baud = c_uint8(baud)
    handle = c_long()
    _auto_open_com_port(
        byref(port), byref(com_address), byref(baud), byref(handle)
    )
    return port.value, com_address.value, baud.value, handle
Wobei man wie schon gesagt, dann am besten die entsprechenden Werte die immer wieder gebraucht werden in Objekten zusammenfasst statt die Funktionen tatsächlich als Funktionen 1:1 abzubilden.
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Hallo Blackjack,

das hat ja schonmal super funktioniert mit dem Comport. Vielen Dank.

Wenn du mir jetzt noch helfen könntest, die Inventory_G2 erfolgreich auszuführen, dann wäre ich restlos glücklich :-)

Der folgende Code spuckt den Fehler 0x01 aus (Return before Inventory finished):

Code: Alles auswählen

from ctypes import byref, c_long, c_uint8, POINTER, WinDLL, c_ubyte, cast
 
READER_DLL = WinDLL('nameofdll.dll')
(
    BAUD_9600,
    BAUD_19200,
    BAUD_38400,
    _,
    BAUD_56000,
    BAUD_57600,
    BAUD_115200
) = xrange(7)
 
 
class ReaderException(Exception):
 
    def __init__(self, error_code):
        Exception.__init__(self, 'Error code 0x{0:x}'.format(error_code))
        self.error_code = error_code
 
 
def _error_check(result, _func, _arguments):
    if result:
        raise ReaderException(result)
    return result
 
 
_auto_open_com_port = READER_DLL.AutoOpenComPort
_auto_open_com_port.argtypes = (
    POINTER(c_long), POINTER(c_uint8), POINTER(c_uint8), POINTER(c_long)
)
_auto_open_com_port.restype = c_long
_auto_open_com_port.errcheck = _error_check
 

_Inventory_G2 = READER_DLL.Inventory_G2
_Inventory_G2.argtypes = (
    POINTER(c_ubyte), c_ubyte, c_ubyte, c_ubyte, POINTER(c_ubyte), POINTER(c_long), POINTER(c_long), c_long
)
_Inventory_G2.restype = c_long
_Inventory_G2.errcheck = _error_check

 
def auto_open_com_port(port=8, com_address=0xff, baud=BAUD_57600):
    port = c_long(port)
    com_address = c_uint8(com_address)
    baud = c_uint8(baud)
    handle = c_long()
    _auto_open_com_port(
        byref(port), byref(com_address), byref(baud), byref(handle)
    )
    return port, com_address, baud, handle

def Inventory_G2(com_address, handle): 
    AdrTID=c_ubyte(0)
    LenTID=c_ubyte(0)
    TIDFlag=c_ubyte(0)
    EPClenandEPC=(c_ubyte*5000)()
    Totallen=c_long(0)
    CardNum=c_long(0)
    _Inventory_G2(
        byref(com_address), AdrTID, LenTID, TIDFlag, cast(EPClenandEPC, POINTER(c_ubyte)), byref(Totallen), byref(CardNum), handle
    )
    print EPClenandEPC
    return com_address, AdrTID, LenTID, TIDFlag, EPClenandEPC, Totallen, CardNum, handle

[port, com_address, baud, handle]=auto_open_com_port()
[com_address, AdrTID, LenTID, TIDFlag, EPClenandEPC, Totallen, CardNum, handle] = Inventory_G2(com_address, handle)
Kannst du nochmal helfen?
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Hallo zusammen,

ich wollte nur kurz mitteilen, dass ich die Lösung gefunden habe. Vielen Dank für eure Hilfe und Anregungen. Im Speziellen danke an BlackJack.
BlackJack

@manlud80: Was war denn die Lösung? Die Scanzeit erhöhen?

Neben dem PDF für die DLL gibt's da auch noch dieses: http://www.rfidshop.com.hk/datasheet/UH ... 20V1.2.pdf

Das Englisch ist ziemlich grauenhaft und so richtig schlau werde ich dort aus der Beschreibung von Fehlercode 0x01 nicht.

Aber so wie es aussieht ist dort das serielle Protokoll beschrieben was die DLL ja letztendlich umsetzt. Ich glaube ich würde eher das Protokoll in Python implementieren statt mich mit einer Windows-DLL herum zu schlagen. :-)
manlud80
User
Beiträge: 11
Registriert: Mittwoch 13. Januar 2016, 12:50

Die Lösung war, dass ich beim Inventory-Scan nicht den error-check machen darf bzw. erst warten muss, bis der Inventory Scan fertig ist.

Hmm, das Protokoll in Python implementieren klingt gut, aber ich habe da leider keine Erfahrung mit.
Antworten