Python char Pointer mit ctypes an C-DLL Funktion übergeben

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Hallo, ich glaube das Thema ist bekannt von anderen Beiträgen jedoch versteh ich es noch immer nicht so ganz, bzw. funktioniert es auch nicht so wie gedacht.

Ich habe eine DLL gegeben welche eine C-Funktion enthält, die genauen Abläufe der Funktion kann ich mir leider nicht besorgen. Jedoch ist der Prototyp der Funktion bekannt.

Code: Alles auswählen

funktion ( 
const unsigned char * inputarray,
unsigned int id,
unsigned char * outputarray
);
Wie man aus dem Prototyp erkennen kann, soll die Funktion den Inhalt eines Arrays einlesen und damit eine Berechnung durchführen. Das Resultat der Berechnung wird dann in dem Outputarray reingeschrieben.

So weit so gut, also habe ich mich mit ctypes ein bisschen beschäftigt und googelt und bin zu folgendem Ergebnis-Code gekommen:

Code: Alles auswählen

from ctypes import *
lib = CDLL('funktionlib.dll')

lib.funktion.argtypes = [
(c_char*6),
c_uint,
(c_char*8)
]

inputarray = (c_char*6)()
inputarray.value = b'001005'

id = 5

outputarray = (c_char*8)()
outputarray.value = b'00000000'

lib.funktion(inputarray, id, outputarray)
Leider bekomme ich keine Antwort bzw. änderung im Outputarray. Nach weiterer Suche habe ich dann gedacht, dass ein Pointer übergeben werden muss. Also habe ich die Argtypes auf "c_char_p" geändert und mit folgenden Code einen Pointer erstellt:

Code: Alles auswählen

p_to_outputarray =  cast(outputarray, POINTER(c_char))
Ergebnis ist jedoch das Gleiche und nun bin ich zu der Erkenntnis gekommen, dass ich ctypes wohl nicht verstehe....
Kann mir jemand helfen?
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tryanderror: Wenn man einen Wert hat und darauf beim Aufruf einen Pointer übergeben möchte nimmt man `byref()`. Wenn man längerfristiger einen Pointer braucht die `pointer`-Funktion. Falls die Typen passen macht `ctypes` das bei Aufrufen auch schon von selbst.

Wie sieht es denn mit dem Rückgabetyp der Funktion aus? Den würde ich auch ”deklarieren”.

Woher weiss die Funktion denn die Länge des Arrays das übergeben wird? Ist die fest bei 6 oder ist das eigentlich ein C-String und da fehlt ein Nullbyte am Ende?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

@__blackjack__ Der Prototyp der Funktion enthält auch noch mehr Parameter als von mir gelistet, jedoch sind diese alle einfache Int-Werte welche wie richtig von dir Vermutet die Länge vom Array angeben. Ich habe diese weggelassen, da die Übergabe dieser Werte funktioniert.

Ein bisschen mehr Kontext von mir: Die Funktion berechnet mit einem mir unbekannten Algorithmus aus einem Anfang-Wert (Inputarray) einen Schlüssel und schreibt den in ein von mir bereit gestelltes Array (Outputarray). Da gewisse Sicherheit geboten werden soll, habe ich keine Infos über den Internen Ablauf der Funktion. Ich weiß nur wie der Prototyp aussieht und was für Fehlercodes ich erhalten kann.

Ich kann 4 unterschiedliche Fehler-Codes, einer dieser sagt auch was über die Länge des Arrays aus und dieser ist nicht vertreten. Das dumme hierbei ist aber auch die Tatsache, dass für diesen Fehlerfall nur einer der zu übertragenenen Int-Werte geprüft wird. Somit kann ich nicht feststellen, ob das Array richtig angekommen ist, da ja nur der Int-Wert geprüft wird. (Abhängigkeit des Fehlercodes vom Int-Wert mit Try and Error festgestellt) => Int-Parameter stimmen und kommen richtig an.

Den Fehlercode welchen ich erhalte sagt mir, dass der Inhalt im Array nicht stimmt. Nun habe ich mit LabView die DLL in Betrieb genommen und genau die gleichen Parameter bzw. den gleichen Inhalt des Arrays übergeben und keine Fehler erhalten => Array was die Funktion erhält stimmt nicht?

Daraus kann ich nur noch schlussfolgern, dass ich nicht in der Lage bin in Python das Array so zu übergeben wie im Prototyp gewollt.

Du meintest das ich auch den Rückgabewert einem Typen zuordnen sollte. Danke für den Hinweis aber da ich unterschiedliche Fehlercodes erhalte und die Interpretieren kann, liegt hier kein Problem.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Security by Oscurity wurde schon sehr oft sehr erfolgreich angewendet (also für die, die an die Geheimnisse heranwollten).

Die Argumente für ein char-Array sind korrekt, wenn die Funktion eine fixe Länge erwartet. char-Arrays kann man nur als Pointer übergeben, so dass Du da nichts weiter machen mußt.

Woher weißt Du, was die Funktion macht und dass sie ein Ergebnis liefert?

Wie sieht denn ein kompletter Beispielaufruf in C aus?
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du solltest den Rueckgabe-Typen *trotzdem* zuordnen, das es geht ist einzig und alleine Glueck, weil das dann wohl ein int ist, und das wird dann einfach angenommen. Auf sowas sollte man sich aber nicht verlassen, falls sich das mal aendert - und aendert kann an dieser Stelle zB einfach nur der Umstieg von 32 auf 64 Bit sein.

Wenn der Inhalt des Arrays nicht stimmt, dann sag doch mal, wie er sein *soll*. Ich sehe, dass du b'001...' schreibst, ich hoffe dir ist klar, dass das nicht mit Bytes 0, 0, 1, ... korrespondiert, sondern 48, 48, 49, ..., weil du ASCII-Werte setzt?
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

@Sirius3 Nun der Funktion sind die Limits sage ich mal bekannt also sie weiß, dass der Bereich von 4-8 Bytes geht. Mit dem Int sag ich aber den genauen Wert.

Die Libary bzw. die Funktion habe ich wie schon erwähnt mit LabView eingebunden und zum Testen die gleichen Inhalte der Parameter übergeben. LabView ist mehr grafisch angelegt, man erstellt dort einen Funktionsblock(wie Blackbox zu betrachen) in welchen man die DLL reinlädt. LabView erkennt, dann von alleine welche Funktionen in der Lib sind bzw. was die Prototypen sind.
Dadurch konnte ich simple die Funktion auswählen und dann die gewünschten Parameter übergeben (man kann hierbei die Datentypen festlegen). Dadurch weiß ich, mit welchen "Startwerten" sozusagen die Funktion arbeiten kann. Die Werte mit dennen ich erfolgreich ein Ergebnis bekam habe ich dann in Python versucht zu übertragen. Jedoch erhalte ich immer den gleichen Fehler, dass der Inhalt eines Arrays nicht stimmen kann.

Ich übertrage mehre Arrays bzw. Pointer auf char Variablen, ich habe den Funktionsprototypen nur kleiner gehalten, da sich das Prinzip bei allen anderen Variablen wiederholt und ich hier nicht 8 Variablen vom Prototyp spammen wollte.

Vieleicht hilft die Tatsache noch, dass der zu übertragene String beliebig sein kann, da der Prototyp auch Parameter enthält die noch nicht in die Funktion eingebunden sind. Ich übertrage hierbei deswegen den gleichen String wie in LabView (womit es ja funktionier) => Daraus interpretiere ich einen Fehler meiner Seite, dass der Prototyp ein Fehlerhaften Var-Typ erhält.

Code: Alles auswählen

Funktion ( 
const unsigned char * startwert,
unsigned int laenge,
const char * der_Fehler_macht,
unsigned int laenge_antwort,
unsigned char antwort,
);
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wir haben deine Funktion nicht vorliegen. Wir koennen es darum nicht selbst pruefen. Was wir koennen ist, einen gegebenen C-Prototypen und deinen Aufruf des ganzen in Python miteinander abzugleichen, um zu sehen, ob uns da was auffaellt. Was du stattdessen praesentierst sind Aussschnitte, die irgendwie so halbwegs das sind, was da vermeintlich steht. Und auch klar Fehler enthalten, wie fehlende Rueckgabetypen und zu viele Kommas. Dazu kann man dann ehrlich gesagt nicht so viel sagen. Wie waere es, wenn du mal den tatsaechlichen Header-Ausschnitt hier reinkopiertst, und den tatsaechlichen Code, den du akut nutzt, um die Funktion aufzurufen? Und wenn es eine inhaltliche Pruefung des uebergebenen Arrays gibt, dann waere es auch gut zu wissen, welche Kriterien da angelegt werden. Um beurteilen zu koennen, ob das, was du in Python formuliert hast, dieser Annahme entspricht. Eine moegliche(!) Fehlerquelle habe ich genannt, dazu hast du auch noch keine Stelleng bezogen.
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Ich habe leider keinen Funktion-Header vorliegen, ich habe nur die DLL und dazugehörige Dokus mit Beschreibungen der Prototypen.
Hier Link zur Doku, die Funktion auf Seite 4 wird verwendet. Aus direkter Quelle weiß ich, dass ipVariant nicht verwendet wird, da noch nicht implementiert, sondern nur Platzhalter.

Den Code welchen ich verwende:

Code: Alles auswählen

lib = CDLL('tolledll.dll')
lib.GenerateKeyExOpt.argtypes = [  (c_char*16),
                                c_uint,
                                c_uint,
                                (c_char*5),
                                (c_char*6),
                                (c_char*12),
                                c_uint,
                                c_uint     ]

lib.GenerateKeyExOpt.restype = c_uint

SeedArray = (c_char*16)()
SeedArray.value = b'0000AFBF04B9238D'

SeedArraySize = c_uint(8)
SecurityLevel = c_uint(1)

Variant = (c_char*5)()
Variant.value = b''

Options = (c_char*6)()
Options.value = b'E00S00'

KeyArray = (c_char*12)()
MaxKeyArraySize = c_uint(6)
ActualKeySize = c_uint()

response = lib.GenerateKeyExOpt(   SeedArray,
                                SeedArraySize,
                                SecurityLevel,
                                Variant,
                                Options,
                                KeyArray,
                                MaxKeyArraySize,
                                ActualKeySize,
                            )

print(response)
Response ist meine Error Variable welche mir immer sagt, dass der Variant nicht stimmen kann, also wahrscheinlich nicht so ankommt wie gewollt.

Bezüglich deiner Anmerkung, dass ich ASCII code schicke, meines Wissenstandes nach arbeitet die Funktion mit dem Code und Convertiert den sich selber.
Im Fall das hier der Fehler liegt, ist meine Frage: Wie schreibe ich denn direkt Bytes?
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Das ist immer noch keine Interface-Beschreibung und auch nicht ein C-Code der funktioniert, sondern immer noch Dein nicht funktionierender Python-Code. Wenn Variant nicht stimmt, dann sollte man da wohl keinen leeren String übergeben, sondern was auch immer erwartet wird.

Die meisten Argumente kann ctypes automatisch konvertieren, da muß man selbst nichts machen:

Code: Alles auswählen

lib = CDLL('tolledll.dll')
generate_key_exopt = lib.GenerateKeyExOpt
generate_key_exopt.argtypes = [c_char_p, c_uint, c_uint, c_char_p, c_char_p, c_char_p, c_uint, c_uint]
generate_key_exopt.restype = c_uint

seed = b'0000AFBF04B9238D'
seed_size = 8
security_level = 1
variant = b'\0'*5
options = b'E00S00'

key = (c_char*12)()
max_key_size = c_uint(6)
actual_key_size = c_uint()

response = generate_key_exopt(seed, seed_size, security_level, variant, options, key, max_key_size, actual_key_size)
print(response)
Hier also alles bis auf die Rückgabewerte. Wobei, wenn ich allein vom Sinn der Namen aus gehe, actual_key_size wahrscheinlich ein Pointer auf ein uint sein sollte.
Warum sind die Sizes immer genau die Hälfte von den eigentlichen Bytelängen?
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Danke erstmal. Ich habe keine weiteren Dokumente auch keinen C-Code oder ein Aufrufbeispiel bekommen.
sondern immer noch Dein nicht funktionierender Python-Code
Wenn ich deine Aussage richtig interpretiere, ist der Python - Code mit ctypes richtig, danke fürs Feedback.
Wobei, wenn ich allein vom Sinn der Namen aus gehe, actual_key_size wahrscheinlich ein Pointer auf ein uint sein sollte.
Ja mit dieser Aussage hast du recht, wie aus der Doku zu entnehmen ist, ändert die Funktion am Ende den Wert, damit der Nutzer weiß wie groß der Key nun wirklich ist. Muss ich denn hier eine andere Übergabe vornehmen? Also bei den argtypes einen c_void_p angeben und einen Pointer für actual_key_size erstellen, bzw. mit byref() angeben?
Warum sind die Sizes immer genau die Hälfte von den eigentlichen Bytelängen?
Hat mich am Anfang auch irritiert, jedoch scheint es so, dass die Funktion immer pro Byte zwei Felder aus dem Array nutzt (obwohl char ja bis 255 bzw. 0xff geht), andere Kombinationen wie eine Länge von 16 erkennt er nicht an und gibt mir den Fehler aus, dass die Bytelänge nicht stimmen kann.

Nochmal Danke für die Hilfe bis hier, ich werde erstmal mit Personen bezüglich der idVariant Kontakt aufnehmen. Wenn ich die Problemlösung gefunden habe, werde ich mich nochmal melden. Bzw. wenn ich merke, dass ich Pythonrelevante Probleme entdeckt habe.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Die Hälfte bei der Grösse und nur Zeichen aus dem Hexadezimalziffernbereich lässt ja schon stark vermuten das da eben keine ASCII-Zeichenkette mit Hexadezimalziffern übergeben werden soll sondern Bytes mit diesen Werten.

Und dann wprde ich die beiden Grössenangaben mit konkreten Zahlen auch nicht redundant in den Code schreiben sondern `len()` auf die Eingabebytes anwenden und ``* max_key_size`` statt der 12 beim Ergebnisarray.

Code: Alles auswählen

lib = CDLL('tolledll.dll')
generate_key_exopt = lib.GenerateKeyExOpt
generate_key_exopt.argtypes = [
    c_char_p,
    c_uint,
    c_uint,
    c_char_p,
    c_char_p,
    c_char_p,
    c_uint,
    POINTER(c_uint),
]
_generate_key_exopt.restype = c_uint

seed = b'\x00\x00\xAF\xBF\x04\xB9\x23\x8D'
seed_size = 8
security_level = 1
variant = b'\0' * 5
options = b'E00S00'

max_key_size = 6
key = (c_char * max_key_size)()
actual_key_size = c_uint()

response = _generate_key_exopt(
    seed,
    len(seed),
    security_level,
    variant,
    options,
    key,
    max_key_size,
    actual_key_size,
)
print(response, actual_key_size, key.value[: actual_key_size.value])
Ich habe die Funktion in `_generate_key_exopt` umbenannt, weil wenn's dann mal funktioniert, sollte man die in eine Python-Funktion verpacken die den `ctypes`-Kram versteckt.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

@__blackjack__ Da könntest du auch recht haben, leider bekommen ich jetzt beim Testen immer einen "access violation writing 0x00..." Fehler, welcher wohl dafür spricht, dass die Funktion an einer falschen Stelle schreiben möchte. Ist das jetzt ein Fehler in der DLL?
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Vor der Mittagspause geschrieben, und jetzt gibts neue Beitraege - ich poste das trotzdem mal, und dann nochmal was zu neueren Problemen:

Deine Annahme bezueglich einer automatischen Konvertierung ist nicht korrekt. Deine oben gezeigte Initialisierung zugrunde gelegt sieht der Inhalt so aus:

Code: Alles auswählen

>>> for b in inputarray: print(b, ord(b))
...
b'E' 69
b'0' 48
b'0' 48
b'S' 83
b'0' 48
b'0' 48
Da wird also aus 'E' keine 14, und aus '0' keine 0. Wenn das ok ist, dann kannst du das natuerlich so machen. Nur bewusst muss es dir sein.

Was ich noch probieren wuerde ist die Arrays wirklich als Pointer zu uebergeben. __blackjack__ hat ja schon auf byref hingewiesen. Also byref(SeedArray) etc. Wobei die C FFI glaube ich nicht wirklich arrays uebergeben kann, cytpes das also wahrscheinlich auch zu einem Pointer kollabiert.

Was auffaellt: der letzte Parameter ist mit einem & gekennzeichnet. Mir ist das in C eigentlich gar nicht bekannt, in C++ waere das ein Referenz-Argument, und damit ist deine Deklaration da auch falsch. Das muesste ein pointer sein. Was auch Sinn macht, wenn man den Zweck betrachtet: das ist ja ein out-Argument, und wenn man da wie du ein int reingibt, kommt da nix raus.
__deets__
User
Beiträge: 14480
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das klingt eher nach einem Fehler der Parameter - die DLL funktioniert ja, und jetzt kommt der Code ggf. ein Stueck weiter als vorher. Kannst du irgendwie darstellen, wie der Aufruf in Labview aussieht? Also zB ein Screenshot des Dialogs zum konfigurieren des Aufrufs oder so.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Welchen Code hast Du denn jetzt genau ausgeführt?
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Leider habe ich auf dem aktuellen Rechner kein LabView mein Kollege hat das auf seinem Laptop, nur leider ist dieser heute nicht da...

Verwendet habe ich jetzt den Code von __blackjack__

Code: Alles auswählen

from ctypes import *

lib = CDLL('tolledll.dll')
generate_key_exopt = lib.GenerateKeyExOpt
generate_key_exopt.argtypes = [
    c_char_p,
    c_uint,
    c_uint,
    c_char_p,
    c_char_p,
    c_char_p,
    c_uint,
    POINTER(c_uint),
]
generate_key_exopt.restype = c_uint

seed = b'\x00\x00\xAF\xBF\x04\xB9\x23\x8D'
seed_size = 8
security_level = 1
variant = b'\0' * 5
options = b'E00S00'

max_key_size = 6
key = (c_char * max_key_size)()
actual_key_size = c_uint()


response = generate_key_exopt(
    seed,
    len(seed),
    security_level,
    variant,
    options,
    key,
    max_key_size,
    actual_key_size,
)
print(response, actual_key_size, key.value[: actual_key_size.value])
Ich weiß nur bezüglich der Parameter die Datentypen und meines Wissens nach wurden auch die Datentypen aus dem Prototyp in LabView verwendet.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Wie sieht denn die Komplette Fehlermeldung aus? Ist die Speicheradresse 0 oder irgend ein anderer Wert?
Wenn es sich um eine 32bit-DLL handelt, kann es sich noch um die falsche Call-Konvention handeln

Code: Alles auswählen

lib = WinDLL('tolledll.dll')
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Es handelt sich um eine 32-Bit Dll, verwende auch Python 3.7.3-32 bit.
Fehlercode ist:

Exception has occured: OSError
exception: access violation writing 0x00000006

es wird auf die Funktionsaufrufzeile verwiesen also diese:

Code: Alles auswählen

response = generate_key_exopt(
    seed,
    len(seed),
    security_level,
    variant,
    options,
    key,
    max_key_size,
    actual_key_size,
)
Hab jetzt auch mit WinDll versucht, es bleibt jedoch der gleiche Fehler.
Benutzeravatar
__blackjack__
User
Beiträge: 12984
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tryanderror: `max_key_size` hat den Wert 6, ändert sich was an der Adresse wenn Du da einen anderen Wert übergibst? Ist die Signatur/Reihenfolge der Argumente eventuell einfach falsch?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
tryanderror
User
Beiträge: 24
Registriert: Mittwoch 19. Februar 2020, 08:30

Ja ich habe gerade den Wert paar mal verändert und der Fehlercode ist immer gleich des Wertes von max_key_size in hex.
Antworten