DLL Funktion über ctype einbinden

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

DLL Funktion über ctype einbinden

Beitragvon quanux » Freitag 24. Februar 2017, 13:40

Hallo zusamme,
ich bräuchte ein bisschen Hilfe beim einbinden zweier Funktion aus einer dll.

Die Einbindung in C++ würde folgendermaßen funktionieren:
  1. enum TFlagState {fsFlagOpen, fsFlagClose, fsFlagOpening, fsFlagClosing, fsError};
  2.  
  3. struct FrameMetadata
  4. {
  5. unsigned short Size;
  6. unsigned int Counter;
  7. unsigned int CounterHW;
  8. long long Timestamp;
  9. long long TimestampMedia;
  10. TFlagState FlagState;
  11. float TempChip;
  12. float TempFlag;
  13. float TempBox;
  14. WORD PIFin[2];
  15. }
  16.  
  17. bool frameInitialized = false, Connected = false, Stopped = false;
  18. int FrameWidth, FrameHeight, FrameDepth, FrameSize;
  19. void *FrameBuffer = NULL;
  20. FrameMetadata Metadata;
  21.  
  22. GetFrameConfig(0, &FrameWidth, &FrameHeight, &FrameDepth)
  23.  
  24. FrameSize = FrameWidth * FrameHeight * FrameDepth;
  25. FrameBuffer = new char[FrameSize];
  26.  
  27.  
  28. GetFrame(0, 0, FrameBuffer, FrameSize, &Metadata)



Mein Ansatz das Ganze jetzt in Python zu übersetzen, war der folgende.

  1. import ctypes
  2.  
  3. class FrameMetadata(ctypes.Structure):
  4.    
  5.     _fields_ = [
  6.    
  7.     ('Size', ctypes.c_ushort),
  8.     ('Counter', ctypes.c_uint),
  9.     ('CounterHW', ctypes.c_uint),
  10.     ('Timestamp', ctypes.c_longlong),
  11.     ('TimestampMedia', ctypes.c_longlong),
  12.     ('FlagState', ctypes.c_int),
  13.     ('TempChip', ctypes.c_float),
  14.     ('TempFlag', ctypes.c_float),
  15.     ('TempBox', ctypes.c_float),
  16.     ('PIFin', ctypes.c_int)
  17.     ]
  18.  
  19. FrameCounter = ctypes.c_int()  
  20. FrameSize = ctypes.c_uint()
  21. Metadata = FrameMetadata()
  22. FrameBuffer = ctypes.c_void_p()
  23.  
  24. FrameWidth = ctypes.c_int()
  25. FrameHeight = ctypes.c_int()
  26. FrameDepth = ctypes.c_int()
  27.  
  28. OptrisDLL = ctypes.WinDLL("ImagerIPC2.dll")  
  29.  
  30. GetFrameConfig = OptrisDLL.GetFrameConfig
  31. GetFrameConfig.restype = ctypes.HRESULT
  32. GetFrameConfig.argtypes = [ctypes.c_ushort, ctypes.c_int, ctypes.c_int, ctypes.c_int]
  33.  
  34. GetFrame = OptrisDLL.GetFrame
  35. GetFrame.restype = ctypes.HRESULT
  36. GetFrame.argtypes=[ctypes.c_ushort, ctypes.c_ushort, ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(FrameMetadata) ]
  37.  
  38. GetFrameConfig(0, ctypes.byref(FrameWidth), ctypes.byref(FrameHeight), ctypes.byref(FrameDepth) )
  39.  
  40. FrameSize = FrameWidth * FrameHeight * FrameDepth
  41. FrameBuffer = [FrameSize]
  42.  
  43. Frame = GetFrame(0, 0, ctypes.byref(FrameBuffer), ctypes.byref(FrameSize), ctypes.byref(Metadata))


Nun bekomme ich folgende Fehlermeldung:

ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong type


Kann mir irgendjemand erklären was da falsch läuft.
Ich hab ctypes noch nicht so wirklich verstanden, denk ich.
Zuletzt geändert von Anonymous am Freitag 24. Februar 2017, 14:17, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

Re: DLL Funktion über ctype einbinden

Beitragvon BlackJack » Freitag 24. Februar 2017, 14:33

@quanux: Ich habe nur flüchtig drüber geschaut und vermute mal der erste Funktionsaufruf klappt. Es ist bei Ausnahmen in der Regel sinnvoll den kompletten Traceback zu zeigen, damit man nicht raten muss *wo* in dem Code die Ausnahme auftritt.

Beim zweiten Aufruf legst Du für da zweite Argument in C++ ein Array der grösse `FrameSize` an und in Python erstellst Du eine Pythonliste mit dem Wert von `FrameSize` als einziges Argument. Da musst Du auch ein Array oder einen Puffer eines entsprechenden Typs aus `ctypes` anlegen mit der entsprechenden Grössse. Also entweder tatsächlich ein `c_char`-, ein `c_uint8`-, oder ein `c_int8`-Array. Je nachdem was Du damit hinterher anstellen möchtest, eine dieser Varianten:
  1. FrameBuffer = (ctypes.c_char * FrameSize)()
  2. FrameBuffer = (ctypes.c_uint8 * FrameSize)()
  3. FrameBuffer = (ctypes.c_int8 * FrameSize)()
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

Re: DLL Funktion über ctype einbinden

Beitragvon quanux » Freitag 24. Februar 2017, 14:49

Hallo BlackJack,
danke für die schnelle Antwort.
Leider klappt schon die erste Funktion nicht.

Traceback:
Traceback (most recent call last):
File "C:\Workspace\OptrisDLL\Test.py", line 39, in <module>
GetFrameConfig(0, ctypes.byref(FrameWidth), ctypes.byref(FrameHeight), ctypes.byref(FrameDepth) )
ctypes.ArgumentError: argument 2: <type 'exceptions.TypeError'>: wrong type

Den FrameBuffer hab ich auf c_char geändert, da hast du natürlich recht, danke.

Fehler besteht aber immer noch.
BlackJack

Re: DLL Funktion über ctype einbinden

Beitragvon BlackJack » Freitag 24. Februar 2017, 14:53

@quanux: Bei der ersten stimmt die Funktionsignatur nicht. Dort steht für die drei letzten Argumente der Typ wäre ``int`` aber übergeben werden Pointer auf ``int``\s, was ja auch richtig ist.
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

Re: DLL Funktion über ctype einbinden

Beitragvon quanux » Freitag 24. Februar 2017, 16:55

Oh Mann, wie peinlich :oops:

Funktioniert jetzt, vielen Dank.
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

Re: DLL Funktion über ctype einbinden

Beitragvon quanux » Montag 27. Februar 2017, 13:09

Hallo nochmal,
ich hab mich geirrt, der eine Fehler ist zwar jetzt behoben, allerdings ist jetzt ein Neuer aufgetaucht.

Ich bekomme jetzt folgende Meldung:

v = GetFrameConfig(0, byref(FrameWidth), byref(FrameHeight), byref(FrameDepth) )
File "_ctypes/callproc.c", line 946, in GetResult
WindowsError: [Error -2147467259] Unbekannter Fehler

Hat irgendjemand eine Ahnung was das sein kann?
BlackJack

Re: DLL Funktion über ctype einbinden

Beitragvon BlackJack » Montag 27. Februar 2017, 13:43

@quanux: Ein Fehler? Vielleicht ist HRESULT nicht der richtige Rückgabetyp, oder es gibt tatsächlich Fehler und die DLL liefert HRESULT-Werte die in Windows keine Bedeutung haben. Was sollte die Funktion denn laut Dokumentation als Rückgabewert liefern? Ich habe auf die Schnelle nur eine Seite in einem Optris-Pi160-Handbuch gefunden wo zu der DLL der Hinweis „The description of the init procedure as well as the necessary command list you will find on the CD
provided.“
steht. Von einer Initialisierungsprozedur ist in Deinem Beispielcode nichts zu sehen. Fehlt das vielleicht?
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

Re: DLL Funktion über ctype einbinden

Beitragvon quanux » Dienstag 28. Februar 2017, 11:57

Hallo BlackJack,
ich denke auch, dass die Initialisierung nicht funktioniert, ich verstehe nur nicht so genau warum.
Der komplette C++ Code sieht folgendermaßen aus:

  1. #include "stdafx.h"
  2. #include <windows.h>
  3. #include "ImagerIPC2.h"
  4.  
  5. int main(int argc, char* argv[])
  6. {
  7.     HANDLE hStdout;
  8.     bool frameInitialized = false, Connected = false, Stopped = false;
  9.     int FrameWidth, FrameHeight, FrameDepth, FrameSize;
  10.     void *FrameBuffer = NULL;
  11.     FrameMetadata Metadata;
  12.     const char s[] = {"open         \0       closed\0 <-opening   \0   closing-> \0             \0"};
  13.  
  14.     if (InitImagerIPC(0) < 0) { printf("\nInit failed! Press Enter to exit..."); getchar(); return -1; }
  15.     if (RunImagerIPC(0) < 0)  { printf("\nRun failed! Press Enter to exit..."); getchar(); ReleaseImagerIPC(0); return -1; }
  16.  
  17.     hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
  18.     CONSOLE_SCREEN_BUFFER_INFO CSBI;
  19.     GetConsoleScreenBufferInfo(hStdout, &CSBI); // memorize last cursor pos to repeat output
  20.  
  21.     while(!GetAsyncKeyState(VK_ESCAPE) && !Stopped) // loop until ESC is pressed or stopped by server
  22.     {
  23.         WORD State = GetIPCState(0, true);
  24.         if(State & IPC_EVENT_SERVER_STOPPED) Stopped = true;
  25.         if(!Connected && (State & IPC_EVENT_INIT_COMPLETED)) Connected = true;
  26.         if((State & IPC_EVENT_FRAME_INIT) && (SUCCEEDED(GetFrameConfig(0, &FrameWidth, &FrameHeight, &FrameDepth))))
  27.         {
  28.             frameInitialized = true;
  29.             printf("------------------------------------------------------\nWidth,Height: (%d,%d)\n\n\n\n\n", FrameWidth, FrameHeight);
  30.             printf("------------------------------------------------------\nHit ESC to exit...\n");
  31.             CSBI.dwCursorPosition.Y += 2;
  32.             FrameSize = FrameWidth * FrameHeight * FrameDepth;
  33.             FrameBuffer = new char[FrameSize];
  34.         }
  35.         if(Connected && frameInitialized && (FrameBuffer != NULL) && GetFrameQueue(0))
  36.             if(SUCCEEDED(GetFrame(0, 0, FrameBuffer, FrameSize, &Metadata)))
  37.             {
  38.                 SetConsoleCursorPosition(hStdout, CSBI.dwCursorPosition);
  39.                 printf("Frame counter HW/SW: %d/%d\n", Metadata.CounterHW, Metadata.Counter);
  40.                 printf("PIF  DI:%d  AI1:%d  AI2:%d\n", (Metadata.PIFin[0] >> 15) == 0, Metadata.PIFin[0] & 0x3FF, Metadata.PIFin[1] & 0x3FF);
  41.                 printf("Target-Temp: %3.1f\370C\n", (float)GetTempTarget(0));
  42.                 printf("Flag: |%s|      <-- Hit SPACE to renew flag\n", &s[min(int(Metadata.FlagState),4)*14]);
  43.  
  44.                 unsigned short* FB = ((unsigned short*)FrameBuffer);
  45.                 printf("Frame: %hu",FB);
  46.                
  47.             }
  48.         ImagerIPCProcessMessages(0);
  49.         if(GetAsyncKeyState(VK_SPACE)) // renew flag if SPACE is pressed
  50.             RenewFlag(0);
  51.     }
  52.     printf("\n                    "); // clear last line
  53.     if(Stopped)
  54.     {
  55.         printf("\nIPC stopped by server! Press Enter to exit...");
  56.         getchar();
  57.     }
  58.  
  59.     if(FrameBuffer) delete [] FrameBuffer;
  60.     ReleaseImagerIPC(0);
  61.     return 0;
  62. }
  63.  


Meine Übersetzung sieht bis jetzt so aus:

  1. import ctypes
  2. from ctypes import byref, HRESULT
  3. from enum import Enum
  4.  
  5.  
  6. IPC_EVENT_INIT_COMPLETED =   0x0001
  7. IPC_EVENT_SERVER_STOPPED =   0x0002
  8. IPC_EVENT_CONFIG_CHANGED =   0x0004
  9. IPC_EVENT_FILE_CMD_READY =   0x0008
  10. IPC_EVENT_FRAME_INIT     =   0x0010
  11. IPC_EVENT_VIS_FRAME_INIT =   0x0020
  12.  
  13.  
  14. class TFlagState(Enum):
  15.     fsFlagOpen = 1
  16.     fsFlagClose = 2
  17.     fsFlagOpening = 3
  18.     fsFlagClosing = 4
  19.     fsError = 5
  20.  
  21. class FrameMetadata(ctypes.Structure):
  22.    
  23.     _fields_ = [
  24.    
  25.     ('Size', ctypes.c_ushort),
  26.     ('Counter', ctypes.c_uint),
  27.     ('CounterHW', ctypes.c_uint),
  28.     ('Timestamp', ctypes.c_longlong),
  29.     ('TimestampMedia', ctypes.c_longlong),
  30.     ('FlagState', ctypes.c_int),
  31.     ('TempChip', ctypes.c_float),
  32.     ('TempFlag', ctypes.c_float),
  33.     ('TempBox', ctypes.c_float),
  34.     ('PIFin', ctypes.c_int)
  35.     ]
  36.    
  37.    
  38.  
  39. FrameCounter = ctypes.c_int()  
  40. FrameSize = ctypes.c_uint()
  41. Metadata = FrameMetadata()
  42. FrameBuffer = ctypes.c_void_p()
  43.  
  44. FrameWidth = ctypes.c_int()
  45. FrameHeight = ctypes.c_int()
  46. FrameDepth = ctypes.c_int()
  47.  
  48. State = ctypes.create_string_buffer(40)    
  49.  
  50. OptrisDLL = ctypes.WinDLL("ImagerIPC2.dll")
  51. #OptrisDLL = ctypes.cdll.LoadLibrary("ImagerIPC2.dll")
  52. print OptrisDLL
  53.  
  54. GetFrame = OptrisDLL.GetFrame
  55. GetFrame.restype = HRESULT
  56. GetFrame.argtypes=[ctypes.c_ushort, ctypes.c_ushort, ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(FrameMetadata) ]
  57.  
  58.  
  59. GetFrameConfig = OptrisDLL.GetFrameConfig
  60. GetFrameConfig.restype = HRESULT
  61. GetFrameConfig.argtypes = [ctypes.c_ushort, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]
  62.  
  63. GetIPCState = OptrisDLL.GetIPCState
  64. GetIPCState.restype = ctypes.c_ushort
  65. GetIPCState.argtypes = [ctypes.c_ushort, ctypes.c_bool]
  66.  
  67. RunImagerIPC = OptrisDLL.RunImagerIPC
  68. RunImagerIPC.restype = ctypes.HRESULT
  69. RunImagerIPC.argtypes = [ctypes.c_ushort]
  70.  
  71. ReleaseImagerIPC = OptrisDLL.ReleaseImagerIPC
  72. ReleaseImagerIPC.restype = ctypes.HRESULT
  73. ReleaseImagerIPC.argtypes = [ctypes.c_ushort]
  74.  
  75. InitImagerIPC = OptrisDLL.InitImagerIPC
  76. InitImagerIPC.restype = ctypes.HRESULT
  77. InitImagerIPC.argtypes = [ctypes.c_ushort]
  78.    
  79. hr = -1
  80. while (hr != 0):
  81.     hr = InitImagerIPC(0)
  82.     print "InitImagerIPC = %s"%hr
  83.     if (hr < 0):
  84.         ReleaseImagerIPC(0)
  85.  
  86. hr = RunImagerIPC(0)
  87. print "RunImagerIPC = %s"%hr
  88.  
  89. if (hr < 0):
  90.     ReleaseImagerIPC(0)
  91.  
  92.    
  93. while True:
  94.      
  95.     State = GetIPCState(0, True)
  96.     print State
  97.    
  98.     #if(State & IPC_EVENT_FRAME_INIT):
  99.    
  100.     v = GetFrameConfig(0, byref(FrameWidth), byref(FrameHeight), byref(FrameDepth))
  101.     print v
  102.    
  103. FrameSize = FrameWidth * FrameHeight * FrameDepth
  104. FrameBuffer = (ctypes.c_char * FrameSize)()
  105.    
  106. Frame = GetFrame(0, 0, byref(FrameBuffer), byref(FrameSize), byref(Metadata))
  107. print 'Frame =%s'%Frame


Die Initialisierung gibt keine Fehlermeldung zurück, allerdings kann ich "State" nicht auslesen, da bekomme ich nur eine Null zurück anstelle eines Hex-Wertes den ich maskieren kann.
Zuletzt geändert von Anonymous am Dienstag 28. Februar 2017, 13:25, insgesamt 1-mal geändert.
Grund: Quelltext in Codebox-Tags gesetzt.
BlackJack

Re: DLL Funktion über ctype einbinden

Beitragvon BlackJack » Mittwoch 1. März 2017, 09:20

@quanux: Der C++-Code ruft `GetFrameConfig()` ja erst auf wenn bestimmte Flags über `GetIPCState()` zurückgegeben wurden. Ich vermute jetzt mal das man auf die wirklich warten muss. `IPC_EVENT_INIT_COMPLETED` klingt zum Beispiel so als wenn die Initialisierung erst durch ist, wenn man dieses Flag gelesen hat. Und bis dahin könnten andere Zugriffe, die eine Initialisierung voraussetzen vielleicht einfach krachend auf die Nase fallen.

Ich würde die API und das eigene Programm in Python besser trennen. Und dann auch so früh wie möglich anfangen Python auch zu nutzen. Also beispielsweise bei den Funktionen mit Fehlerwert als Rückgabe dem Funktionsobjekt einen `errorcheck` zuzuweisen der eine Ausnahme auslöst wenn ein Fehler aufgetreten ist. Dann muss man nicht überall auf Fehler prüfen, und bekommt trotzdem jeden mit.

Für das Aufräumen, also `ReleaseImagerIPC()` würde ich mindestens ``try``/``finally`` verwenden, um das nur einmal ins Programm schreiben zu müssen. Noch besser wäre ein „context manager“ damit man ``with`` verwenden kann. Das liesse sich mit einer Funktion und `contextlib.contextmanager()` als Dekorator machen. Allerdings habe ich den Verdacht, das die 0 die da jedes mal als erstes Argument übergeben wird für die Nummer der Kamera, also damit letztlich für ein Kameraobjekt steht‽ Dann könnte man das auch gleich in einer Klasse kapseln, die das „context manager“-Protokoll implementiert.

Den Rückgabewert von `GetIPCState()` könnte man Kapseln und Properties verpassen um die Und-Verknüpfungen nicht im eigenen Programm schreiben zu müssen. Und `FrameMetaData` könnte man Properties für die `DI` und `AI*`-Werte spendieren.

Eine `Frame`-Klasse in der die Daten und Metadaten eines Frames zusammengefasst werden, macht eventuell Sinn, und dann eine Generatorfunktion die kontinuierlich solche `Frame`-Objekte liefert.

Bezüglich der Schreibweisen würde ich wenigstens das eigene Programm nach Python-Konventionen schreiben. Eventuell sogar die eingebundene API anpassen, denn hier und dort wird man sowieso Prä- und Suffixe loswerden wollen die hauptsächlich dazu da sind die fehlenden Namensräume bei C auszugleichen oder die bei Properties keinen Sinn machen. Was IMHO gar nicht geht bei den Namen ist alles wie Klassennamen zu schreiben. Das ist massiv verwirrend. Zumindest für mich. Wenn man Namen von einer fremden API übernimmt, dann mag das eine gute Entscheidung sein die Schreibweise zu übernehmen, aber eigenen Kram so gegen die Richtlinien zu benennen ist IMHO nicht gut.

Edit: Und ich vermute ganz stark man muss die Ereignisverarbeitung am laufen halten, sonst ändert sich das Ergebnis von `GetIPCState()` wahrscheinlich nie.
quanux
User
Beiträge: 8
Registriert: Freitag 24. Februar 2017, 12:59

Re: DLL Funktion über ctype einbinden

Beitragvon quanux » Mittwoch 1. März 2017, 15:25

@BlackJack: Vielen Dank für die vielen Typs, ich werde mich daran halten und das Ganze nochmal neu aufsetzen.

Wer ist online?

Mitglieder in diesem Forum: Google [Bot]