Callback-Funktion mit Pointer als Parameter

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
markus87
User
Beiträge: 5
Registriert: Montag 12. Mai 2014, 01:49

Hallo,

ich versuche seit einigen Stunden eine shared libary (ngSpice) mit Python zu verwenden. Dabei muss ich in einer Callback-Funktion den Wert eines Pointers setzen, aber dieser wird einfach nicht rein geschrieben. Ist der Garbage-Collector schuld, oder was mache ich falsch? Unten ist der Teil vom Code. Ich hab auch versucht die CB-Funktion selbst aufzurufen und auch da wird der Wert nicht übernommen.

Code: Alles auswählen

import ctypes
from ctypes import *

def py_get_vsrc_data_cb(voltage, time, name, id, rtptr):
	
	voltage.contents = c_double(1.0)
	print "py_get_vsrc_data: name= "+name+", time= "+str(time)+", value= "+str(voltage.contents)
	return 0
	
ngdll = CDLL("libngspice.so")
	
GETVSRCDATA = CFUNCTYPE(None, POINTER(c_double), c_double, c_char_p, c_int,c_void_p)
getvsrcdata = GETVSRCDATA(py_get_vsrc_data_cb)

#....
# ngdll.ngSpice_Init_Sync(getvsrcdata,getisrcdata,0,0,None)

pnt = POINTER(c_double)
pnt.content = c_double(0.0)
py_get_vsrc_data_cb(pnt,0,"test",0,None)
print pnt.content
Wenn ich den Code ausführe bekomme ich immer:

$ python cb.py
py_get_vsrc_data: name= test, time= 0, value= c_double(1.0)
c_double(0.0)

Was muss ich machen, damit nach dem Aufruf von py_get_vsrc_data_cb in pnt 1.0 steht?

Danke!
Benutzeravatar
snafu
User
Beiträge: 6744
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Wie ist denn der Prototyp im entsprechenden C-Header der Bibliothek definiert und was erwartet die C-Bibliothek genau von dem Callback? Und noch was: Hast du das in deinem tatsächlich verwendeten Code auch so, dass du das Attribut ``.contents`` befüllst und dann versuchst, es mit ``.content`` wieder abzufragen? Das kann ja schlecht funktionieren, wenn beide Namen unterschiedlich sind.
BlackJack

@markus87: Das sieht ein bisschen komisch aus was Du da machst. `POINTER()` erstellt ein *Typ*objekt. Das erstellt ein Objekt das für den Datentyp „pointer auf …” steht und noch nicht für einen tatsächlichen Zeiger. In der Funktionssignatur ist das deshalb auch richtig, da will man den Datentyp angeben.

Beim Aufruf macht man das selbe wie in C, da dekariert man ja auch nicht einen Zeiger auf eine Zahl sondern eine Zahl und übergibt dann die Adresse davon beim Aufruf. Um einen tatsächlichen Zeiger auf ein Wertobjekt zu bekommen ist die `pointer()`-Funktion da, oder wenn es nur ein Zeiger ist der ausschliesslich für den Aufruf benutzt wird, die `byref()`-Funktion, die etwas effizienter ist.

Wobei — wenn da gar kein Zeiger auf eine Zahl sondern einer auf eine Rückruffunktion übergeben werden soll die einen Zeiger auf eine Zahl bekommt, dann ist das doch alles völlig egal weil Du dann weder die Zahl noch den Zeiger selber anlegen oder irgendwo übergeben musst‽
markus87
User
Beiträge: 5
Registriert: Montag 12. Mai 2014, 01:49

Ähm ja, als ich den Eintrag geschrieben hab, war ich schon ein bisschen müde. Also, der Code sieht in etwa so aus:

Code: Alles auswählen

import ctypes
from ctypes import *

def py_get_vsrc_data_cb(voltage, time, name, id, rtptr):
	
	voltage.contents = c_double(1.0)
	print "py_get_vsrc_data: name= "+name+", time= "+str(time)+", value= "+str(voltage.contents)
	return 0
	
ngdll = CDLL("libngspice.so")
	
GETVSRCDATA = CFUNCTYPE(None, POINTER(c_double), c_double, c_char_p, c_int,c_void_p)
getvsrcdata = GETVSRCDATA(py_get_vsrc_data_cb)

#....
# ngdll.ngSpice_Init_Sync(getvsrcdata,getisrcdata,0,0,None)

a = c_double(0.0)
pnt = pointer(a)
#pnt.content = c_double(0.0)
py_get_vsrc_data_cb(pnt,0,"test",0,None)
print pnt.contents
und die c-Funktion ist so deklariert:

Code: Alles auswählen

typedef int (GetVSRCData)(double*, double, char*, int, void*);
/*
   double*     return voltage value
   double      actual time
   char*       node name
   int         identification number of calling ngspice shared lib
   void*       return pointer received from caller
*/
Die CB-Funktion erhält den Wert aber scheinbar trotzdem nicht
BlackJack

@markus87: Wieso scheinbar? Woran erkennst Du das? Du prüfst das ja nirgends? Und meine Ausgabe sieht auch anders als in Deinem ersten Beitrag aus, denn die Rückruffunktion setzt den Wert auf 1.0 und darum gibt das Programm auch diesen Wert als letztes aus:

Code: Alles auswählen

$ python forum9.py 
py_get_vsrc_data: name= test, time= 0, value= c_double(1.0)
c_double(1.0)
markus87
User
Beiträge: 5
Registriert: Montag 12. Mai 2014, 01:49

BlackJack hat geschrieben:@markus87: Wieso scheinbar? Woran erkennst Du das? Du prüfst das ja nirgends? Und meine Ausgabe sieht auch anders als in Deinem ersten Beitrag aus, denn die Rückruffunktion setzt den Wert auf 1.0 und darum gibt das Programm auch diesen Wert als letztes aus:

Code: Alles auswählen

$ python forum9.py 
py_get_vsrc_data: name= test, time= 0, value= c_double(1.0)
c_double(1.0)
Die Ausgabe sieht bei mir mittlerweile auch so aus. libngspice.so ist ein Simulationsprogramm und liefert mir immer falsche Ergebnisse. Ich habe das Python-Programm auch in C geschrieben und da funktioniert alles wie es soll. Ich hoffe, dass es nicht zu unübersichtlich wird, aber ich poste mal die beiden vollständigen Programme:

Code: Alles auswählen

import ctypes
from ctypes import *
import re

### structs###

class ngcomplex(Structure):
	_fields_ = [("cx_real", c_double), ("cx_imag", c_double)]

class vector_info(Structure):
	_fields_ = [	
		("v_name", c_char_p),	
		("v_type", c_int),
		("v_flags", c_short),
		("v_realdata", POINTER(c_double)),
		("v_compdata", ngcomplex),
		("v_length", c_int)
	]
				
class vecvalues(Structure):
	_fields_ = [
		("name", c_char_p),
		("creal", c_double),
		("cimag", c_double),
		("is_scale", c_bool),
		("is_complex", c_bool)
	]
				
class vecvaluesall(Structure):
	_fields_ = [
		("veccount", c_int),
		("vecindex", c_int),
		("vecsa", POINTER(POINTER(vecvalues)))
	]
		
class vecinfo(Structure):
	_fields_ = [
		("number", c_int),
		("vecname", c_char_p),
		("is_real", c_bool),
		("pdvec", c_void_p),
		("pdvecscale", c_void_p)
	]
				
class vecinfoall(Structure):
	_fields_ = [
		("name", c_char_p),
		("title", c_char_p),
		("date", c_char_p),
		("type", c_char_p),
		("veccount", c_int),
		("vecs", POINTER(POINTER(vecinfo)))
	]
				
### callback functions ###	

def py_send_char_cb(str, id, rptr):
	print "py_send_char_cb: "+str
	return 0			

def py_send_stat_cb(str, id, rtptr):
	print "py_send_stat_cb: "+str
	return 0
	
def py_controlled_exit_cb(status, unload, reason, id, rtptr):
	ngdll = None
	return 0
	
def py_send_data_cb(vecvalall, num, id, rtptr):
	data = vecvalall.contents
	
	for i in range (0,data.veccount):
		vec = data.vecsa[i].contents
		print "py_send_data_cb: name: "+vec.name+", is_scale: "+str(vec.is_scale)+", is_complex: "+str(vec.is_complex)+", creal: "+str(vec.creal)+", cimag: "+str(vec.cimag)
		
	return 0
	
def py_send_init_data_cb(vecvalall, id, rtptr):	
	data = vecvalall.contents

	for i in range(0,data.veccount):
		vec = data.vecs[i].contents
		print "py_send_init_data: name = "+vec.vecname
		
	return 0
	
def py_bg_thread_running_cb(running, id, rtptr):
	print "py_bg_thread_running"
	return 0
	
def py_get_vsrc_data_cb(voltage, time, name, id, rtptr):
	
	voltage.contents = c_double(1.0)
	print "py_get_vsrc_data: name= "+name+", time= "+str(time)+", value= "+str(voltage.contents)
	return 0
	
def py_get_isrc_data_cb(current, time, name, id, rtptr):
	current.contents = c_double(0.0)
	print "py_get_isrc_data"
	return 0
	
def py_get_sync_data_cb(time, delta, old_delta, redostep, id, loc, rtptr):
	print "py_get_sync_data"
	return 0
	
### load library ###

ngdll = CDLL("libngspice.so")

### define functions ###

SENDCHAR = CFUNCTYPE(None, c_char_p, c_int, c_void_p)
sendchar = SENDCHAR(py_send_char_cb)

SENDSTAT = CFUNCTYPE(None, c_char_p, c_int, c_void_p)
sendstat = SENDSTAT(py_send_stat_cb)

CTRLEXIT = CFUNCTYPE(None, c_int, c_bool, c_bool, c_int, c_void_p)
ctrlexit = CTRLEXIT(py_controlled_exit_cb)

SENDDATA = CFUNCTYPE(None, POINTER(vecvaluesall), c_int, c_int, c_void_p)
senddata = SENDDATA(py_send_data_cb)

SENDINITDATA = CFUNCTYPE(None, POINTER(vecinfoall), c_int, c_void_p)
sendinitdata = SENDINITDATA(py_send_init_data_cb)

BGTHREADRUNNING = CFUNCTYPE(None, c_bool, c_int, c_void_p)
bgthreadrunning = BGTHREADRUNNING(py_bg_thread_running_cb)

GETVSRCDATA = CFUNCTYPE(None, POINTER(c_double), c_double, c_char_p, c_int,c_void_p)
getvsrcdata = GETVSRCDATA(py_get_vsrc_data_cb)

GETISRCDATA = CFUNCTYPE(None, POINTER(c_double), c_double, c_char_p, c_int,c_void_p)
getisrcdata = GETISRCDATA(py_get_isrc_data_cb)

GETSYNCDATA = CFUNCTYPE(None, c_double, POINTER(c_double), c_double, c_int, c_int, c_int, c_void_p)
getsyncdata = GETSYNCDATA(py_get_sync_data_cb)

### init functions ###
ngdll.ngSpice_Init(sendchar,sendstat,ctrlexit,senddata,sendinitdata,bgthreadrunning,None)
### init synchronizing functions ###
ngdll.ngSpice_Init_Sync(getvsrcdata,getisrcdata,0,0,None)

### assign functions ###

ngspice_command = ngdll.ngSpice_Command
ngspice_get_vec_info = ngdll.ngGet_Vec_Info
ngspice_circ = ngdll.ngSpice_Circ
ngspice_cur_plot = ngdll.ngSpice_CurPlot
ngspice_all_plots = ngdll.ngSpice_AllPlots
ngspice_all_vecs = ngdll.ngSpice_AllVecs
ngspice_running = ngdll.ngSpice_running
ngspice_set_bkpt = ngdll.ngSpice_SetBkpt

### test circuit ###

testbench = []
testbench.append("test circuit")
testbench.append(".temp 20")
testbench.append("v1 a 0 0 external")
testbench.append("r1 a 0 1k")
testbench.append(".tran 10p 10n")
testbench.append(".end")
testbench.append(None)

ct_testbench = (ctypes.c_char_p * len(testbench))()
ct_testbench[:] = testbench

ngspice_circ(ct_testbench)

ngspice_command(b"run")
ngspice_command(b"meas tran dyn_power rms i(v1) from=0ns to=10ns")

ngspice_command(b"quit")

Code: Alles auswählen

#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <ctype.h>
#include <stdbool.h>

#define NGSPICE_INIT	(int * (*)(SendChar*, SendStat*, ControlledExit*, SendData*, SendInitData*, BGThreadRunning*, void*))

typedef struct 
{
    double cx_real;
    double cx_imag;
} ngcomplex_t;

typedef struct vector_info
{
    char *v_name;
    int v_type;	
    short v_flags;
    double *v_realdata;
    ngcomplex_t *v_compdata;
    int v_length;
} vector_info, *pvector_info;

typedef struct vecvalues 
{
    char* name;
    double creal;
    double cimag;
    bool is_scale;
    bool is_complex;
} vecvalues, *pvecvalues;

typedef struct vecvaluesall 
{
    int veccount;
    int vecindex;
    pvecvalues *vecsa;
} vecvaluesall, *pvecvaluesall;

typedef struct vecinfo
{
    int number;
    char *vecname;
    bool is_real;
    void *pdvec;
    void *pdvecscale;
} vecinfo, *pvecinfo;

typedef struct vecinfoall
{
    char *name;
    char *title;
    char *date;
    char *type;
    int veccount;

    pvecinfo *vecs; 

} vecinfoall, *pvecinfoall;

typedef void* funptr_t;

typedef int (SendChar)(char*, int, void*);
typedef int (SendStat)(char*, int, void*);
typedef int (ControlledExit)(int, bool, bool, int, void*);
typedef int (SendData)(pvecvaluesall, int, int, void*);
typedef int (SendInitData)(pvecinfoall, int, void*);
typedef int (BGThreadRunning)(bool, int, void*);
typedef int (GetVSRCData)(double*, double, char*, int, void*);
typedef int (GetISRCData)(double*, double, char*, int, void*);
typedef int (GetSyncData)(double, double*, double, int, int, int, void*);

funptr_t ngSpice_Init = NULL;
funptr_t ngSpice_Init_Sync = NULL;

funptr_t ngSpice_Command = NULL;
funptr_t ngSpice_GVI = NULL;
funptr_t ngSpice_Circ = NULL;
funptr_t ngSpice_CurPlot = NULL;
funptr_t ngSpice_AllPlots = NULL;
funptr_t ngSpice_AllVecs = NULL;

funptr_t ngSpice_running = NULL;
funptr_t ngSpice_SetBkpt = NULL;

int c_send_char_cb(char* str, int id, void* rtptr)
{
	printf("c_send_char_cb: %s\n",str);
	return 0;
}

int c_send_stat_cb(char* str, int id, void* rtptr)
{
	printf("c_send_stat_cb: %s\n",str);
	return 0;
}

int c_controlled_exit_cb(int status, bool unload, bool reason, int id, void* rtptr)
{
	printf("c_controlled_exit_cb\n");
	return 0;
}

int c_send_data_cb(pvecvaluesall vecvalall, int num, int id, void* rtptr)
{
	printf("c_send_data_cb\n");
	return 0;
}

int c_send_init_data_cb(pvecinfoall vecinfoall, int id, void* rtptr)
{
	printf("c_send_init_data_cb");
	return 0;
}

int c_bg_thread_running_cb(bool running, int id, void* rtptr)
{
	printf("c_bg_thread_running_cb");
	return 0;
}

int c_get_vsrc_data_cb(double* voltage, double time, char* name, int id, void* rtptr)
{
	*voltage = 1.0;
	printf("c_get_vsrc_data_cb");
	return 0;
}

int c_get_isrc_data_cb(double* current, double time, char* name, int id, void* rtptr)
{
	printf("c_get_isrc_data_cb");
	return 0;
}

int c_get_sync_data_cb(double time, double* delta, double old_delta, int redostep, int id, int loc, void* rtptr)
{
	printf("c_get_sync_data_cb(");
	return 0;
}

int main()
{
	void* ngdllh = NULL;
	
	const char* testbench[7];
	
	testbench[0] = "test circuit";
	testbench[1] = ".temp 20";
	testbench[2] = "v1 a 0 0 external";
	testbench[3] = "r1 a 0 1k";
	testbench[4] = ".tran 10p 10n";
	testbench[5] = ".end";
	testbench[6] = NULL;
	
	ngdllh = dlopen("libngspice.so",RTLD_NOW);
	if (ngdllh==NULL)
	{
		printf("error\n");
	}
	
	ngSpice_Init = dlsym(ngdllh,"ngSpice_Init");
    ngSpice_Init_Sync = dlsym(ngdllh,"ngSpice_Init_Sync");
    
	ngSpice_Command = dlsym(ngdllh,"ngSpice_Command");
	ngSpice_GVI = dlsym(ngdllh,"ngGet_Vec_Info");
	ngSpice_Circ = dlsym(ngdllh,"ngSpice_Circ");
	ngSpice_CurPlot = dlsym(ngdllh,"ngSpice_CurPlot");
	ngSpice_AllPlots = dlsym(ngdllh,"ngSpice_AllPlots");
	ngSpice_AllVecs = dlsym(ngdllh,"ngSpice_AllVecs");
	
	ngSpice_running = dlsym(ngdllh,"ngSpice_running");
	ngSpice_SetBkpt = dlsym(ngdllh,"ngSpice_SetBkpt");
	
	((int * (*)(SendChar*, SendStat*, ControlledExit*, SendData*, SendInitData*, BGThreadRunning*, void*)) ngSpice_Init)(c_send_char_cb,c_send_stat_cb,c_controlled_exit_cb,c_send_data_cb,c_send_init_data_cb,c_bg_thread_running_cb,NULL);
	((int * (*)(GetVSRCData*, GetISRCData*,GetSyncData*,int*,void*)) ngSpice_Init_Sync)(c_get_vsrc_data_cb,c_get_isrc_data_cb,NULL,NULL,NULL);
                      
                      
	((int * (*)(char**)) ngSpice_Circ)((char**)testbench);
	((int * (*)(char*)) ngSpice_Command)("run");
	((int * (*)(char*)) ngSpice_Command)("meas tran dyn_power rms i(v1) from=0ns to=10ns");
	((int * (*)(char*)) ngSpice_Command)("quit");
	
	
	dlclose(ngdllh);
	return 0;
}
BlackJack

@markus87: So vom schnellen drüberschauen scheinen die Rückruffunktionen alle keinen Rückgabetyp ”deklariert” zu haben (`None`), die Funktionen geben aber alle eine Zahl zurück und im C-Programm werden die Rückruffunktionen auch mit dem Rückgabetyp ``int`` deklariert.

Edit: Ich würde auch die Argumente und Rückgabetypen der anderen Funktionen ”deklarieren”.
markus87
User
Beiträge: 5
Registriert: Montag 12. Mai 2014, 01:49

BlackJack hat geschrieben:@markus87: So vom schnellen drüberschauen scheinen die Rückruffunktionen alle keinen Rückgabetyp ”deklariert” zu haben (`None`), die Funktionen geben aber alle eine Zahl zurück und im C-Programm werden die Rückruffunktionen auch mit dem Rückgabetyp ``int`` deklariert.
Habe jetzt die Rückgabewerte für alle Funktionen auf c_int geändert. Hat leider nichts geändert, ist aber natürlich nicht korrekt gewesen.
BlackJack hat geschrieben: Edit: Ich würde auch die Argumente und Rückgabetypen der anderen Funktionen ”deklarieren”.
Welche andere Funktionen meinst du?

Um das Testen zu erleichtern habe ich die shared library "nachprogrammiert", also eine lib die genau die selben funktionen deklariert. Beim Aufruf vom C-Programm aus ändert sich der voltage Wert, beim Aufruf von Python nicht.

Das ist die Funktion aus MEINER libngspice.so (ngSPice_Circ verwende ich nur weil die Funktion nur einmal ziemlich am Schluss aufgerufen wird und da die Init-Funktion schon aufgerufen wurde):

Code: Alles auswählen

int ngSpice_Circ(char** circarray)
{
	double test = 123.0;
	double* pnt = &test;
	
	printf("starting test: *pnt = %f\n",*pnt);
	printf("calling c_get_vsrc_data_cb()\n");
	c_get_vsrc_data_cb(pnt,0.0,"test",0,NULL);
	printf("evaluating test: *pnt = %f\n",*pnt);
	
//	printf("ngSpice_Circ\n");
	return 0;
}
Beim Aufruf aus Python liefert das:
ngSpice_Init
ngSpice_Init_Sync
starting test: *pnt = 123.000000
calling c_get_vsrc_data_cb()
voltage = c_double(123.0)
py_get_vsrc_data: name= test, time= 0.0, value= c_double(1.0)
evaluating test: *pnt = 123.000000
ngSpice_Command
ngSpice_Command
ngSpice_Command
und beim Aufruf aus C:
ngSpice_Init
ngSpice_Init_Sync
starting test: *pnt = 123.000000
calling c_get_vsrc_data_cb()
c_get_vsrc_data_cbevaluating test: *pnt = 1.000000
ngSpice_Command
ngSpice_Command
ngSpice_Command
BlackJack

@markus87: Die Init-Funktionen und was Du da noch so von der Bibliothek als Attribute abfragst. Das ist zwar nicht zwingend nötig zum Aufrufen (ausser eventuell bei Funktionen die keinen Rückgabewert haben), aber dann kann `ctypes` beim Aufruf prüfen ob man das richtige übergibt.
BlackJack

@markus87: Argh, ich bin blind: `contents` ändert den Inhalt des Datentyps, also *den Zeiger*. Du willst aber das ersetzen *worauf* er zeigt:

Code: Alles auswählen

In [130]: a = ct.c_int(42)  # int deklarieren und initialisieren.

In [131]: p = ct.pointer(a)  # Zeiger auf den int.

In [132]: p.contents = ct.c_int(23)  # Ändert den *Zeiger* auf eine andere Speicherstelle...

In [133]: a  # ...hat also keine Auswirkung auf `a`.
Out[133]: c_long(42)

In [134]: p = ct.pointer(a)  # Wieder einen Zeiger auf den Wert von `a`.

In [135]: p[0] = 23  # Den Wert von `a` ändern.

In [136]: a
Out[136]: c_long(23)
markus87
User
Beiträge: 5
Registriert: Montag 12. Mai 2014, 01:49

BlackJack hat geschrieben:@markus87: Argh, ich bin blind: `contents` ändert den Inhalt des Datentyps, also *den Zeiger*. Du willst aber das ersetzen *worauf* er zeigt:

Code: Alles auswählen

In [130]: a = ct.c_int(42)  # int deklarieren und initialisieren.

In [131]: p = ct.pointer(a)  # Zeiger auf den int.

In [132]: p.contents = ct.c_int(23)  # Ändert den *Zeiger* auf eine andere Speicherstelle...

In [133]: a  # ...hat also keine Auswirkung auf `a`.
Out[133]: c_long(42)

In [134]: p = ct.pointer(a)  # Wieder einen Zeiger auf den Wert von `a`.

In [135]: p[0] = 23  # Den Wert von `a` ändern.

In [136]: a
Out[136]: c_long(23)
1000 Dank! Es funktioniert :) Ich habe noch nicht so viel Erfahrung mit Python, das wäre mir nie aufgefallen!

Gibt es noch eine andere Möglichkeit auf den Inhalt der Variable zuzugreifen, auf die der Pointer zeigt, außer p[0] = ...?
Antworten