ctypes union als rückgabewert

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Hallo zusammen,

Ich greife mit Python auf eine Dll (testapi.dll) zu und nutze dabei ctypes, das klappt auch alles soweit ganz gut, nur möchte ich jetzt eine Funktion in der Dll nutzen, die eine Union als Übergabewert verlangt nutzen und genau daran scheitere ich. Mache ich da irgendeinen (oder mehrere) Fenkfehler? Anbei folgt die Funktion auf die ich zugreifen möchte, die Union-Beschreibung, und ein Beispiel eines fehlgeschlagenen Versuchs das Problem zu lösen. (Weiß nicht ob das relevant ist, aber ich nutze ctypes-0.6.3 und Python 2.2.1 das muss auch leider so bleiben) Wäre toll wenn ihr mir dabei weiterhelfen könntet.

Die Funktion sieht so aus:

Code: Alles auswählen

bool ReadObject(TAsap3Hdl hdl, TModulHdl module,
                   const char *ObjectName, TFormat format,
                   TObjectValue * value);

Die Union beschreibt sich laut api wie folgt:				   
				   
typedef union {

  TValueType type;

  struct {
    TValueType type;

    double value; 
  } value;       

  struct {        
    TValueType type;

    short dimension;     
    double*       axis;   
  } axis;     

  struct {       
    TValueType type;      

    short len;             
    char*          ascii;   
  } ascii;      

  struct {
    TValueType type;

    short dimension;       
    double*       axis;   
    double*       values;  
  } curve;     
               
  struct {
    TValueType type;

    short xDimension;      
    short yDimension;     
    double*       xAxis;  
    double*       yAxis; 
    double*       values; 
  } map;       
} TObjectValue;

typedef enum {
  VALUE = 0, 
  CURVE = 1, 
  MAP   = 2, 
  AXIS  = 3,
  ASCII = 4
} TValueType;
Einer meiner Versuche das ganze zu lösen sieht so aus:

Code: Alles auswählen

>>> class value(Structure):
...     _fields_=[
...     ("ValueType", c_int),("value", c_double)]
...     
>>> class tcov(Union):
...     _fields_=[("ValueType", c_int),("value", value)]
...     
>>> co_val=tcov()
>>> windll.testapi.ReadObject(b, a, "channel1", 1, byref(co_val))
1								    <- die Funktion antwortet brav mit einem True
>>> print co_val.value.value
8.74235474698e-317 <- das ist jedoch nicht der erwartete Wert, es müste was mit 11.95 ungefähr rausspringen
BlackJack

Ich bezweifele das die Union so aussieht wie Du sie dargestellt hast. Die einzelnen Komponenten einer Union sind die einzelnen Auswahlmöglichkeiten und die erste Möglichkeit ist ``TValueType type;`` was als Information alleinstehend total unbrauchbar ist.

Normalerweise würde man so etwas erwarten:

Code: Alles auswählen

typedef struct {
    int type;
    union {
       struct {
          /* ... */
       } bar;
       struct {
          /* ... */
       } baz;
       struct {
          /* ... */
       } frobz;
       /* ... */
    } u;
} foo;
Je nach `type` kann man dann entscheiden ob man auf `bar`, `baz` etc. zugreifen muss.
Gast

Der erste Eintrag "TValueType type" ist nicht unbrauchbar, dieser erscheint ja auch wieder in den nachfolgenden Strukturen. Die Union ist ja nur ein vorbereiteter Speicherbereich, der die Größe der größten Struktur (speichermäßig) abbildet. Wird diese Union nun mit einem Eintrag belegt, so weiß man über die Abfrage von TValueType, welche Struktur zu verwenden ist. Das dann TValueType auch in den nachfolgenden Strukturen auftauchen muss ist auch klar, denn der Speicherbereich am Anfang der Union ist ja schon durch diesen Eintrag belegt.

kurz gesagt: die Union sieht so aus, hab ich der API so entnommen
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Hatte vergessen mich einzuloggen, der vorherige Eintrag war von mir.
BlackJack

Anonymous hat geschrieben:Der erste Eintrag "TValueType type" ist nicht unbrauchbar, dieser erscheint ja auch wieder in den nachfolgenden Strukturen.
Okay, dann mach aus "unbrauchbar" ein "überflüssig".

Ich habe so eine Union in der Form das erste mal gesehen. Die Form die ich beschrieben habe dagegen schon öfter. Es kommt vom Speicherlayout auf's gleiche hinaus, man spart sich aber das Wiederholen der Typinformation in jeder alternativen Struktur.
Joghurt
User
Beiträge: 877
Registriert: Dienstag 15. Februar 2005, 15:07

BlackJack hat geschrieben:Okay, dann mach aus "unbrauchbar" ein "überflüssig".
Naja, so kannst du

Code: Alles auswählen

switch (foobar.type)
anstatt

Code: Alles auswählen

switch (foobar.value.type)
schreiben
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Okay, dann mach aus "unbrauchbar" ein "überflüssig".
Der Meinung bin ich ja auch, doch an der dll bzw der api kann ich nichts ändern.

So, mein Problem ist mehr oder weniger gelöst. Nochmal zur Info, mein Wunsch war es eine Funktion einer DLL aufzurufen, die als Übergabewert einen Zeiger auf eine Union verlangt. Die möglichen Elemente der Union sind Strukturen, die jeweils aus einem Integer und dem ein oder anderen weiteren Datentyp (Double short etc.) bestehen und genau dort liegt jetzt der Knackpunkt. Ich habe mal eine Struktur aus einem c_int und einem c_double erzeugt. Die größe dieser Struktur müsste ja eigentlich 12 Bytes betragen (4 für c_int und 8 für c_double), doch wenn ich mir über sizeof(name der Struktur) die Größe anzeigen lasse, besteht die Struktur aus 16 Bytes.
Wenn ich nun diese Struktur verwende, um die vorher angesprochene Funktion aufzurufen, wir der int-Wert in den ersten 4 Bytes abgelegt und der double-Wert in den folgenden 8-Bytes d.h., der double-Wert befindet sich an der falschen Position. Lasse ich mir anschließend den double-Wert anzeigen, so ist klar, das dabei nur Müll herauskommen kann.

Meine nächste Frage wäre jetzt: Darf das denn überhaupt so sein? Und ist das so gewollt, dass die Struktur so erzeugt wird?

Sagt bescheid, falls ich mich unklar ausdrücke, bzw. falls ich notwendige Infos vergessen haben sollt.

PS: danke für die schnellen Kommentare
BlackJack

Kann es sein das Du einen 64 bittigen Prozessor in Deinem Rechner hast? Dann könnte es sein das ein C Compiler, und damit auch `ctypes`, auf die Idee kommen kann den `double` Wert so auszurichten. Bei mir ist so eine Struktur jedenfalls nur 12 Bytes gross.
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Ich habe keinen 64-bit Rechner. Als Beispiel mal ein Auszug aus dem was bei mir passiert.

Code: Alles auswählen

>>> from ctypes import *
>>> class a(Structure):
...     _fields_=[
...     ("b", c_int),
...     ("c", c_double)
...     ]
...     
>>> sizeof(a)
16
>>>
Da ist doch der Wurm drin, oder mache ich irgendetwas falsch?
BlackJack

Wo hast Du denn das `ctypes` Modul her? Ist das vielleicht für eine 64-Bit Architektur übersetzt worden?
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Mein ctypes habe ich unter:
http://prdownloads.sourceforge.net/ctyp ... e?download bezogen.

Übrigens liefert folgendes das gleiche Ergebnis:

Code: Alles auswählen

>>> import struct
>>> a=struct.pack("id",1,2)
>>> len(a)
16
>>>
Kann mir vielleicht jemand sagen, ob das nun so sein soll?
BlackJack

`struct` richtet die Daten so aus wie der C Compiler mit dem der Python-Interpreter übersetzt wurde. Wäre jetzt also die nächste Frage wo Du Deinen Python-Interpreter her hast und mit welchem Compiler auf welcher Architektur der übersetzt wurde.
Thomas_S
User
Beiträge: 9
Registriert: Donnerstag 12. Januar 2006, 08:37

Ich habe mal folgendes ausprobiert:

>>> a=struct.pack("id",1,2)
>>> len(a)
16
>>> a=struct.pack("<id",1,2)
>>> len(a)
12

Der untere Fall ist ja genau der, den ich über ctypes realisieren möchte. Gibt es bei ctypes irgendeine Möglichkeit auf byteorder, size und alignment Einfluß zu nehmen?? In der Dokumentation von ctypes habe ich leider nichts finden können. Aber vielleicht gibt es ja noch eine andere Möglichkeit, die ich noch nicht berücksichtigt habe.
wyattroerb
User
Beiträge: 1
Registriert: Donnerstag 13. September 2007, 16:35

Hallo Thomas,
hab das gleiche Problem mit der CANapAPI.dll. Hast du damals eine Lösung gefunden?
Ich wäre dir für eine Antwort sehr dankbar!

Robert
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

@wyattroerb:
Als Quickfix fallen mir 2 Möglichkeiten ein:
- ein dünner C-Wrapper, der das "falsche" Alignment korrigiert und Deine Typen für ctypes benutzbar macht
- die Typengrößen und Offsets bestimmen und händisch mit struct auslesen

Für letzteres verlierst Du zwar den ctypes-Komfort, allerdings kannst Du Dir eine Structure-Proxyklasse bauen, welche für die Attributzugriffe die Werte an die richtige Speicherstelle schreibt und von dieser liest.
BlackJack

Vielleicht hilft die `ctypes`-Dokumentation hier weiter: Structure/union alignment and byte order
Antworten