DLL Funktion über ctype einbinden

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

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

Die Einbindung in C++ würde folgendermaßen funktionieren:

Code: Alles auswählen

enum TFlagState {fsFlagOpen, fsFlagClose, fsFlagOpening, fsFlagClosing, fsError};

struct FrameMetadata
{
unsigned short Size;
unsigned int Counter;
unsigned int CounterHW;
long long Timestamp;
long long TimestampMedia;
TFlagState FlagState;
float TempChip;
float TempFlag;
float TempBox;
WORD PIFin[2];
}

bool frameInitialized = false, Connected = false, Stopped = false;
int FrameWidth, FrameHeight, FrameDepth, FrameSize;
void *FrameBuffer = NULL;
FrameMetadata Metadata;

GetFrameConfig(0, &FrameWidth, &FrameHeight, &FrameDepth)

FrameSize = FrameWidth * FrameHeight * FrameDepth;
FrameBuffer = new char[FrameSize];


GetFrame(0, 0, FrameBuffer, FrameSize, &Metadata)



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

Code: Alles auswählen

import ctypes

class FrameMetadata(ctypes.Structure):
    
    _fields_ = [
    
    ('Size', ctypes.c_ushort),
    ('Counter', ctypes.c_uint),
    ('CounterHW', ctypes.c_uint),
    ('Timestamp', ctypes.c_longlong),
    ('TimestampMedia', ctypes.c_longlong),
    ('FlagState', ctypes.c_int),
    ('TempChip', ctypes.c_float),
    ('TempFlag', ctypes.c_float),
    ('TempBox', ctypes.c_float),
    ('PIFin', ctypes.c_int)
    ]
  
FrameCounter = ctypes.c_int()   
FrameSize = ctypes.c_uint()
Metadata = FrameMetadata()
FrameBuffer = ctypes.c_void_p() 

FrameWidth = ctypes.c_int()
FrameHeight = ctypes.c_int()
FrameDepth = ctypes.c_int()

OptrisDLL = ctypes.WinDLL("ImagerIPC2.dll")  

GetFrameConfig = OptrisDLL.GetFrameConfig
GetFrameConfig.restype = ctypes.HRESULT
GetFrameConfig.argtypes = [ctypes.c_ushort, ctypes.c_int, ctypes.c_int, ctypes.c_int]

GetFrame = OptrisDLL.GetFrame
GetFrame.restype = ctypes.HRESULT
GetFrame.argtypes=[ctypes.c_ushort, ctypes.c_ushort, ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(FrameMetadata) ]

GetFrameConfig(0, ctypes.byref(FrameWidth), ctypes.byref(FrameHeight), ctypes.byref(FrameDepth) )

FrameSize = FrameWidth * FrameHeight * FrameDepth
FrameBuffer = [FrameSize]

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

@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:

Code: Alles auswählen

FrameBuffer = (ctypes.c_char * FrameSize)()
FrameBuffer = (ctypes.c_uint8 * FrameSize)()
FrameBuffer = (ctypes.c_int8 * FrameSize)()
quanux
User
Beiträge: 12
Registriert: Freitag 24. Februar 2017, 12:59

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

@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: 12
Registriert: Freitag 24. Februar 2017, 12:59

Oh Mann, wie peinlich :oops:

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

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

@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: 12
Registriert: Freitag 24. Februar 2017, 12:59

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:

Code: Alles auswählen

#include "stdafx.h"
#include <windows.h>
#include "ImagerIPC2.h"

int main(int argc, char* argv[]) 
{
	HANDLE hStdout;
	bool frameInitialized = false, Connected = false, Stopped = false;
	int FrameWidth, FrameHeight, FrameDepth, FrameSize;
	void *FrameBuffer = NULL;
	FrameMetadata Metadata;
	const char s[] = {"open         \0       closed\0 <-opening   \0   closing-> \0             \0"};

	if (InitImagerIPC(0) < 0) {	printf("\nInit failed! Press Enter to exit..."); getchar();	return -1; }
	if (RunImagerIPC(0) < 0)  {	printf("\nRun failed! Press Enter to exit...");	getchar(); ReleaseImagerIPC(0);	return -1; }

    hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 
	CONSOLE_SCREEN_BUFFER_INFO CSBI;
	GetConsoleScreenBufferInfo(hStdout, &CSBI); // memorize last cursor pos to repeat output 

	while(!GetAsyncKeyState(VK_ESCAPE) && !Stopped) // loop until ESC is pressed or stopped by server
	{
		WORD State = GetIPCState(0, true);
		if(State & IPC_EVENT_SERVER_STOPPED) Stopped = true;
		if(!Connected && (State & IPC_EVENT_INIT_COMPLETED)) Connected = true;
		if((State & IPC_EVENT_FRAME_INIT) && (SUCCEEDED(GetFrameConfig(0, &FrameWidth, &FrameHeight, &FrameDepth))))
		{
			frameInitialized = true;
			printf("------------------------------------------------------\nWidth,Height: (%d,%d)\n\n\n\n\n", FrameWidth, FrameHeight);
			printf("------------------------------------------------------\nHit ESC to exit...\n");
			CSBI.dwCursorPosition.Y += 2;
			FrameSize = FrameWidth * FrameHeight * FrameDepth;
			FrameBuffer = new char[FrameSize];
		}
		if(Connected && frameInitialized && (FrameBuffer != NULL) && GetFrameQueue(0))
			if(SUCCEEDED(GetFrame(0, 0, FrameBuffer, FrameSize, &Metadata)))
			{
				SetConsoleCursorPosition(hStdout, CSBI.dwCursorPosition);
				printf("Frame counter HW/SW: %d/%d\n", Metadata.CounterHW, Metadata.Counter);
				printf("PIF  DI:%d  AI1:%d  AI2:%d\n", (Metadata.PIFin[0] >> 15) == 0, Metadata.PIFin[0] & 0x3FF, Metadata.PIFin[1] & 0x3FF);
				printf("Target-Temp: %3.1f\370C\n", (float)GetTempTarget(0));
				printf("Flag: |%s|      <-- Hit SPACE to renew flag\n", &s[min(int(Metadata.FlagState),4)*14]);

				unsigned short* FB = ((unsigned short*)FrameBuffer);
				printf("Frame: %hu",FB);
				
			}
		ImagerIPCProcessMessages(0);
		if(GetAsyncKeyState(VK_SPACE)) // renew flag if SPACE is pressed
			RenewFlag(0);
	}
	printf("\n                    "); // clear last line
	if(Stopped)
	{
		printf("\nIPC stopped by server! Press Enter to exit...");
		getchar();
	}

	if(FrameBuffer) delete [] FrameBuffer;
	ReleaseImagerIPC(0);
	return 0;
}



Meine Übersetzung sieht bis jetzt so aus:

Code: Alles auswählen

import ctypes
from ctypes import byref, HRESULT
from enum import Enum


IPC_EVENT_INIT_COMPLETED =   0x0001
IPC_EVENT_SERVER_STOPPED =   0x0002
IPC_EVENT_CONFIG_CHANGED =   0x0004
IPC_EVENT_FILE_CMD_READY =   0x0008
IPC_EVENT_FRAME_INIT     =   0x0010
IPC_EVENT_VIS_FRAME_INIT =   0x0020


class TFlagState(Enum):
    fsFlagOpen = 1
    fsFlagClose = 2
    fsFlagOpening = 3
    fsFlagClosing = 4
    fsError = 5

class FrameMetadata(ctypes.Structure):
    
    _fields_ = [
    
    ('Size', ctypes.c_ushort),
    ('Counter', ctypes.c_uint),
    ('CounterHW', ctypes.c_uint),
    ('Timestamp', ctypes.c_longlong),
    ('TimestampMedia', ctypes.c_longlong),
    ('FlagState', ctypes.c_int),
    ('TempChip', ctypes.c_float),
    ('TempFlag', ctypes.c_float),
    ('TempBox', ctypes.c_float),
    ('PIFin', ctypes.c_int)
    ]
    
    

FrameCounter = ctypes.c_int()   
FrameSize = ctypes.c_uint()
Metadata = FrameMetadata()
FrameBuffer = ctypes.c_void_p() 

FrameWidth = ctypes.c_int()
FrameHeight = ctypes.c_int()
FrameDepth = ctypes.c_int()

State = ctypes.create_string_buffer(40)    

OptrisDLL = ctypes.WinDLL("ImagerIPC2.dll")
#OptrisDLL = ctypes.cdll.LoadLibrary("ImagerIPC2.dll")
print OptrisDLL

GetFrame = OptrisDLL.GetFrame
GetFrame.restype = HRESULT
GetFrame.argtypes=[ctypes.c_ushort, ctypes.c_ushort, ctypes.c_void_p, ctypes.c_uint, ctypes.POINTER(FrameMetadata) ]


GetFrameConfig = OptrisDLL.GetFrameConfig
GetFrameConfig.restype = HRESULT
GetFrameConfig.argtypes = [ctypes.c_ushort, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int)]

GetIPCState = OptrisDLL.GetIPCState
GetIPCState.restype = ctypes.c_ushort
GetIPCState.argtypes = [ctypes.c_ushort, ctypes.c_bool]

RunImagerIPC = OptrisDLL.RunImagerIPC
RunImagerIPC.restype = ctypes.HRESULT
RunImagerIPC.argtypes = [ctypes.c_ushort]

ReleaseImagerIPC = OptrisDLL.ReleaseImagerIPC
ReleaseImagerIPC.restype = ctypes.HRESULT
ReleaseImagerIPC.argtypes = [ctypes.c_ushort]

InitImagerIPC = OptrisDLL.InitImagerIPC
InitImagerIPC.restype = ctypes.HRESULT
InitImagerIPC.argtypes = [ctypes.c_ushort]
    
hr = -1
while (hr != 0):
    hr = InitImagerIPC(0)
    print "InitImagerIPC = %s"%hr
    if (hr < 0):
        ReleaseImagerIPC(0)

hr = RunImagerIPC(0)
print "RunImagerIPC = %s"%hr

if (hr < 0):
    ReleaseImagerIPC(0)

   
while True:
     
    State = GetIPCState(0, True)
    print State
    
    #if(State & IPC_EVENT_FRAME_INIT):
    
    v = GetFrameConfig(0, byref(FrameWidth), byref(FrameHeight), byref(FrameDepth))
    print v
    
FrameSize = FrameWidth * FrameHeight * FrameDepth
FrameBuffer = (ctypes.c_char * FrameSize)()
    
Frame = GetFrame(0, 0, byref(FrameBuffer), byref(FrameSize), byref(Metadata))
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

@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: 12
Registriert: Freitag 24. Februar 2017, 12:59

@BlackJack: Vielen Dank für die vielen Typs, ich werde mich daran halten und das Ganze nochmal neu aufsetzen.
GrapefruIT
User
Beiträge: 1
Registriert: Dienstag 6. März 2018, 22:29

Dear Sirs

@quanux
Ich habe momentan dasselbe Problem. Hast du das Pythonscript zum laufen gekriegt? Kannst du evtl. deine aktuelle Version posten?

Gruss
Antworten