Problem mit dplotlib.dll (ctypes)

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

Hallo zusammen,

ich habe in der Vergangenheit unter Fortran ein freies Tool zum Plotten verwendet (DPlot Jr, http://www.dplot.com). DPlotlib.dll wurde dabei in Fortran eingebunden. Nach ersten Gehversuchen in Python möchte ich das Tool auch hier nutzen. Ich habe dazu ctypes verwendet:

Code: Alles auswählen

from ctypes import *
MAXC = 100

class DPLOT(Structure):
    _fields_ = [
                ("Version", c_uint), \
                ("hwnd", c_uint), \
                ("DataFormat", c_uint), \
                ("MaxCurves", c_uint), \
                ("MaxPoints", c_uint), \
                ("NumCurves", c_uint), \
                ("Scale", c_uint), \
                ("LegendX", c_float), \
                ("LegendY", c_float), \
                ("NP", c_uint*MAXC), \
                ("LineType", c_uint*MAXC), \
                ("SymbolType", c_uint*MAXC), \
                ("SizeofExtraInfo", c_uint), \
                ("Legend", c_char_p*(MAXC+1)), \
                ("Label", c_char_p*MAXC), \
                ("Title", c_char_p*3), \
                ("XAxis", c_char_p), \
                ("YAxis", c_char_p)
                ]

def XYExample():
    NP = 1001
    xarray = c_float*c_int(NP).value
    x = xarray()
    yarray = c_float*(2*c_int(NP).value)
    y = yarray()
    for i in range(NP):
        x[i] = (4.*i)/(NP-1)
        y[i] = sin(pi*x[i])
        y[i+NP] = cos(pi*x[i])

    DPlot = DPLOT()
    DPlot.Version = DPLOT_DDE_VERSION
    DPlot.hwnd = 0
    DPlot.DataFormat = DATA_XYYY
    DPlot.MaxCurves = 2
    DPlot.MaxPoints = NP
    DPlot.NumCurves = 2
    DPlot.Scale = SCALE_LINEARX_LINEARY
    DPlot.LegendX = 0.05
    DPlot.LegendY = 0.05
    DPlot.NP[0] = NP
    DPlot.NP[1] = NP
    DPlot.LineType[0] = LINESTYLE_SOLID
    DPlot.LineType[1] = LINESTYLE_LONGDASH
    DPlot.Legend[1] = "sin({\sp}x)" #{\s is DPlot-speak for "use symbol font"
    DPlot.Legend[2] = "cos({\sp}x)"
    DPlot.Title[0] = "Data sent to DPlot via DPLOTLIB.DLL"
    DPlot.XAxis = "x"
    DPlot.YAxis = "y"

    command = c_char_p( "[ManualScale(0,-1.25,4,1.25)]"+ \
                        "[TickInterval(1,0.5,0.25)]"+ \
                        "[Caption(\"DPLOTLIB XY Test\")]"+ \
                        "[DocMaximize()]"+ \
                        "[ClearEditFlag()]" )
    ret = f.DPlot_Plot(byref(DPlot), byref(x), byref(y), command)
DPLOT_DDE_VERSION etc. sind definiert.
DPlot wird zwar geladen, leider erhalte ich jedoch nicht die gewünschte Grafik: manchmal fehlt der Kurvenverlauf, mal fehlt derText.
Was mache ich falsch?

Danke für jeden Beitrag!
Christoph
CM
User
Beiträge: 2464
Registriert: Sonntag 29. August 2004, 19:47
Kontaktdaten:

Hoi und willkommen im Forum,

hast Du schon mal im DPlot-Forum nachgefragt, ob dort Erfahrungen bestehen? In der Pythonwelt habe ich noch nichts von DPlot gehört, sondern nur über C/C++ - was aber nicht heißt, daß es nicht gehen wird.

Ansonsten kann ich Dir matplotlib empfehlen, eine sehr leistungsstarke Library fürs Plotten in 2D. Willst Du 3D-features bietet Dir die entsprechende scipy Seite einige Alternativen.

Tut mir leid, daß es keine Antwort auf Deine Frage ist - vielleicht weiß ja sonst jemand Bescheid.

Gruß,
Christian
BlackJack

Erstmal zum Quelltext selbst: Die '\' am Zeilenende brauchst Du nicht wenn es noch offene Klammern gibt. Erst wenn alle Klammern, ob nun rund, geschweift oder eckig, geschlossen sind, braucht man ein '\' um Zeilen fortzusetzen.

Und bei der `command` Zeichenkette kannst Du zusätzlich noch die ``+``-Operatoren weglassen. Zeichenketten die nur durch "whitespace" getrennt sind, werden beim Übersetzen in Bytecode zusammengefügt, genau wie in C/C++.

Bei der Legende hast Du ein '\s' in der Zeichenkette stehen. Das ist keine gültige Escape-Sequenz, also kommt da auch wirklich ein rückwärtsgerichteter Schrägstrich und ein kleines 's' bei heraus, aber auch da sollte man besser '\\s' schreiben. Wenn man sich das nicht angewöhnt, fällt man früher oder später darüber.

Nun zum Problem: Läuft die aufgerufene Funktion aus der DLL asynchron, oder kehrt die erst zurück wenn sie ihre Arbeit erledigt hat?

Gibt es bei den selben Werten immer die selben Fehler, oder ist das eher "zufällig"?
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

Danke für eure Antworten!

CM:
Ich habe in DPlot-Forum nachgefragt; es gibt dort keine Erfahrungen mit Python.

BlackJack:
'\' und '+' werde ich löschen.
'\s' wird geändert
Die Funktion läuft asynchron; auch nach Beenden der Python-Anwendung läuft DPlot weiter. Die auftetenden Fehler sind "zufällig"; es treten nicht immer die selben Fehler auf.
Ich bin der Meinung, dass sowohl "x" als auch "y" und "command" richtig übergeben werden. Meiner Meinung nach liegt das Problem in der structure.

Gruß
Christoph
BlackJack

capsule5 hat geschrieben:Die Funktion läuft asynchron; auch nach Beenden der Python-Anwendung läuft DPlot weiter. Die auftetenden Fehler sind "zufällig"; es treten nicht immer die selben Fehler auf.
Dann vermute ich mal dass ist das Problem: Du rufst die DPlot-Funktion auf, diese kehrt sofort zurück, die Python-Funktion endet, folglich verschwinden die lokalen Namen. Und damit schlägt der Garbage Collector zu und gibt den Speicher von den Daten, auf die Du Pointer an DPlot übergeben hast, zur Wiederverwendung frei. Während DPlot noch drauf zugreift, kann es dann passieren, dass andere Objekte diesen Speicher belegen und ihn verändern und DPlot verarbeitet "Müll".
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

Der Aufruf von DPlot ist in einer Schleife:

Code: Alles auswählen

while plotNo: # 0 == False
    plotNo = choosePlot()
    if plotNo == 1:
        XYExample()
    elif plotNo == 2:
        ContourPlot1()
In "choosePlot()" wird nach einer Eingabe gefragt; damit wird das entsprechende DPlot-Beispiel geladen. Wie kann ich verhindern, dass der Speicher freigegeben wird?
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

hier ist die struc-Definition in C:

Code: Alles auswählen

typedef struct tagDPLOT
{
    DWORD Version;           // Caller must set this to DPLOT_DDE_VERSION
    DWORD hwnd;              // handle of client application window
                             // (Use DWORD rather than HWND)
    DWORD DataFormat;        // XY pairs, DX and Y, etc.
    DWORD MaxCurves;         // maximum number of curves (must be <= 20)
                             // = NX for DataFormat = DATA_3D
                             //   ignore for DataFormat = DATA_3DR
    DWORD MaxPoints;         // maximum number of points/curve
                             // = NY for DataFormat = DATA_3D
                             // = 3 * number of points for DATA_3DR
    DWORD NumCurves;         // actual number of curves, always 1 for
                             //   DATA_3D or DATA_3DR
    DWORD Scale;             // scaling code (Linear, Log, etc.)
    float LegendX;           // left coord of legend, expressed as a ratio
                             //    of plot size (0->1)
    float LegendY;           // top coord of legend
    DWORD NP[MAXC];          // actual number of points in each curve;
                             //   cannot exceed MaxPoints.
	                         // For DATA_3DR files, return number of nodes in NP[0]
    DWORD LineType[MAXC];    // line types (see codes below)
    DWORD SymbolType[MAXC];  // symbol types (see codes below)
    DWORD SizeofExtraInfo;   // Extra information following X,Y data
    char  Legend[MAXC+1][40];// Legend[0] is the caption for the legend.
                             // Legend[n] is the legend for the n'th curve.
    char  Label[MAXC][5];    // Strings displayed beside the last data point
                             //   in a curve.
    char  Title[3][80];      // Three title lines.
    char  XAxis[80];         // X Axis label.
    char  YAxis[80];         // Y Axis label.
} DPLOT;
Habe ich die char-fields richtig übertragen? Ich bin mir da nicht sicher ...
BlackJack

capsule5 hat geschrieben:hier ist die struc-Definition in C:

Code: Alles auswählen

    char  Legend[MAXC+1][40];// Legend[0] is the caption for the legend.
                             // Legend[n] is the legend for the n'th curve.
    char  Label[MAXC][5];    // Strings displayed beside the last data point
                             //   in a curve.
    char  Title[3][80];      // Three title lines.
    char  XAxis[80];         // X Axis label.
    char  YAxis[80];         // Y Axis label.
} DPLOT;
Habe ich die char-fields richtig übertragen? Ich bin mir da nicht sicher ...
Wie's aussiehst hast Du recht, Dir nicht sicher zu sein. ;-)

Du hast Pointer definiert, in der Struktur stehen aber die Daten direkt drin.

Zum Problem mit dem Garbage Collector: Du musst Referenzen auf die Argumente im Python-Programm behalten. Am einfachsten liesse sich das durch kapseln der Plot-Funktion in einem Objekt erreichen, das sich alle jemals verwendeten Argumente merkt (ungetestet):

Code: Alles auswählen

class Plotter(object):
    def __init__(self):
        self.arguments = list()
    
    def __call__(self, dplot_ref, x_values_ref, y_values_ref, command):
        self.arguments.append((dplot_ref, x_values_ref, y_values_ref, command))
        f.DPlot_Plot(dplot_ref, x_values_ref, y_values_ref, command)

dplot = Plotter()
Wenn Das Programm länger läuft, könnte man noch eine Methode hinzufügen die das "Gedächtnis" löscht.

Etwas schöner wäre es natürlich, die DPlot-Funktion so in Objekte zu verpacken, dass ein Benutzer möglichst wenig von `ctypes` mitbekommt.
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

Danke, das sieht schon besser aus :) :

Code: Alles auswählen

class DPLOT(Structure):
    _fields_ = [
                ("Version", c_uint),
                ("hwnd", c_uint), \
                ("DataFormat", c_uint),
                ("MaxCurves", c_uint),
                ("MaxPoints", c_uint),
                ("NumCurves", c_uint),
                ("Scale", c_uint),
                ("LegendX", c_float),
                ("LegendY", c_float),
                ("NP", c_uint*MAXC),
                ("LineType", c_uint*MAXC),
                ("SymbolType", c_uint*MAXC),
                ("SizeofExtraInfo", c_uint),
                ("Legend", (c_char*80)*(MAXC+1)),
                ("Label", (c_char*40)*MAXC),
                ("Title", (c_char*80)*3),
                ("XAxis", c_char*80),
                ("YAxis", c_char*80)
                ]
damit geht auch

Code: Alles auswählen

    DPlot.XAxis = "x"
    DPlot.YAxis = "y"
wie kann ich aber z.B. das

Code: Alles auswählen

DPlot.Title[0] = "Data sent to DPlot via DPLOTLIB.DLL"
in Python umsetzen?
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

so, das wars wohl!

Mit

Code: Alles auswählen

def strCopy(str_out, str_in):
    str_out[:len(str_in)] = str_in

strCopy(DPlot.Title[0], "Data sent to DPlot via DPLOTLIB.DLL")
ist auch das letzte Problem gelöst. :D

Danke, und Gruß
Christoph
BlackJack

Es gibt `ctypes.create_string_buffer()`, damit sollte es sich auch lösen lassen:

Code: Alles auswählen

In [35]: title_array = ctypes.c_char * 80 * 3

In [36]: a = title_array()

In [37]: title = 'Data sent to DPlot via DPLOTLIB.DLL'

In [38]: a[0] = ctypes.create_string_buffer(title, 80)

In [39]: a[0].value
Out[39]: 'Data sent to DPlot via DPLOTLIB.DLL'
capsule5
User
Beiträge: 28
Registriert: Sonntag 10. Dezember 2006, 18:49

Super, damit geht's auch! :D Danke!

Falls Interesse besteht, kann ich den Code hier zur Verfügung stellen. Gibt es eine Möglichkeit, Anhänge zu posten?

Gruß
Christoph
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

capsule5 hat geschrieben:Gibt es eine Möglichkeit, Anhänge zu posten?
Nein, aber es gibt LodgeIt.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Antworten