pythoncom & _midlSAFEARRAY

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Dienstag 24. Oktober 2017, 21:45

Hallo und guten Abend im Forum,

ich versuche mich mit pythoncom an einer Automation und möchte aus einem numpy-Array einen POINTER(_midlSAFEARRAY(c_double)) machen. Eine Funktion benötigt als Übergabe diesen Pointer.

Kann mir dabei jemand helfen?

Alternativ überlege ich daran mit Lazarus eine DLL zu erstellen die mir die Funktionalitäten die ich für die "Kommunikation" brauche bereitstellt. Mit Lazarus funktioniert die Automation sehr gut. Wie müsste so eine DLL aufgebaut sein?

Vielen Dank

Zarathustra
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Mittwoch 25. Oktober 2017, 20:58

Hallo __deets__,

vielen Dank für die beiden Seiten. Die Fehlermeldungen verringern sich ab, funktionieren tut es noch nicht.

Das hier ist mein numpy-Array:

Code: Alles auswählen

ArrayData = np.array([0., 0., 0., 10., 0., 0., 5., 8.6667, 0.], np.double)
Daraus baue ich ein _midlSAFEARRAY:

Code: Alles auswählen

midlArrayData = _midlSAFEARRAY(c_double).create(ArrayData)
Dann kommt der Automatisierungsbefehl:

Code: Alles auswählen

DcPts.SetCommandPoints(c_short(3), ctypes.POINTER(midlArrayData))
und dann die Fehlermeldung: TypeError: must be a ctypes type

Das _midlSAFEARRAY besteht doch aus einem c_double, also einem ctypes Datentyp. Was mache ich hier falsch?

Vielen Dank und schöne Grüße

Zarathustra
__deets__
User
Beiträge: 3477
Registriert: Mittwoch 14. Oktober 2015, 14:29

Mittwoch 25. Oktober 2017, 23:37

POINTER statt pointer. Ersteres ist für Typdeklarationen, zweiteres um einen Pointer auf etwas zu bekommen. byref geht wahrscheinlich auch, ich habe gerade die Details nicht im Kopf.
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Donnerstag 26. Oktober 2017, 21:58

Guten Abend __deets__, hallo Forum,

leider kein Erfolg. Weder mit pointer noch mit byref. Beidesmal kommt diese Fehlermeldung:

DcPts.SetCommandPoints(c_short(3), ctypes.byref(midlArrayData))
File "C:\Anaconda2\lib\site-packages\comtypes\client\dynamic.py", line 42, in __call__
return self._obj._comobj.Invoke(self._id, *args)
File "C:\Anaconda2\lib\site-packages\comtypes\automation.py", line 766, in Invoke
array.value = a
File "C:\Anaconda2\lib\site-packages\comtypes\automation.py", line 358, in _set_value
self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
KeyError: <class 'comtypes.safearray.LP_SAFEARRAY_c_double'>


Hhmm, mit der Fehlermeldung kann ich noch weniger anfangen. Diese COM-Automatisierung unter Python ist scheint weitaus schwieriger als mit Lazarus. Woran liegt das?

Jetzt geht's aber in die Falle...

Viele Grüße und vielen Dank für Antworten

Zarathustra
__deets__
User
Beiträge: 3477
Registriert: Mittwoch 14. Oktober 2015, 14:29

Donnerstag 26. Oktober 2017, 23:49

Man kann sich ruhig mal die Inder Fehlermeldung angegebenen Dateien anschauen und probieren das zu verstehen. Dann sieht man, das die Wandlung zu safearry implizit geschieht. Du musst das nicht tun. Es reicht sogar einfach eine Python-Liste mit Doubles reinzugeben.

Das es mit Lazarus einfacher geht verwundert nicht - das ist statisch typisiert, da hilft der Compiler mehr mit. Wieso benutzt du es dann nicht einfach?
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Freitag 27. Oktober 2017, 06:35

Guten Morgen __deets__

Lazarus bietet zwar die für mich angenehmere COM-Integration, hat aber nichts vergleichbares zu numpy und matplotlib aufzuweisen. Wenn es die Armadillo-Bibliothek für Lazarus gäbe würde ich das nehmen. Ich bin so auf der Suche nach der Eier legenden Woll-Milch-Sa.

Es soll "einfach" in der Anwendung sein, Berechnung, Plotting, GUI und COM mitbringen. Wenn COM funktioniert ist python das Richtige. GUI decke ich mit wxPython ab. Rechnen mit numpy und Plotten mit Matplotlib.

Eine Python-Liste mit Doubles führt dazu, dass das Programm beendet ohne die Punkte und die zugehörige Kurve zu zeichnen. Ich kann dann mit der Maus auf die Zeichenobefläche klicken und Punkte setzen durch die dann der Spline gezeichnet wird. Die Punkte sollten aber durch das SetCommandPoints-Kommando vorgegeben werden.

Viele Grüße und einen schönen Tag

Zarathustra
Sirius3
User
Beiträge: 8411
Registriert: Sonntag 21. Oktober 2012, 17:20

Freitag 27. Oktober 2017, 07:16

@zarathustra: eigentlich sollte sich COM um die richtige Datentypen selbst kümmern, solange sie irgendwie konvertierbar sind. Man sollte also einfach schreiben können:

Code: Alles auswählen

ArrayData = np.array([0., 0., 0., 10., 0., 0., 5., 8.6667, 0.])
midlArrayData = _midlSAFEARRAY(c_double).create(ArrayData)
DcPts.SetCommandPoints(3, midlArrayData)
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Freitag 27. Oktober 2017, 16:17

Hallo Sirius3, hallo __deets__,

gebe ich

Code: Alles auswählen

DcPts.SetCommandPoints(3, ArrayData)
vor, so wird es seitens der Funktion SetCommandPoints zwar "geschluckt" und keine Fehlermeldung ausgegeben, aber es passiert auf der Zeichenfläche auch nichts. Das Programm wartet darauf, dass ich mit der Maus einige Punke klicke und mit Doppelklick den Kurvenbefehl beende. Dann endet auch das Programm. Das Zeichnen der Kurve ist dann so, als hätte ich den Befehl im Zeichenprogramm mit der Maus gestartet.

Richtig Kurios finde ich dass das Setzen eines einzelnen Punktes mit

Code: Alles auswählen

DcPts.SetCommandPoint(x,y,z)
funktioniert.
Also dreimal hintereinander:

Code: Alles auswählen

DcPts.SetCommandPoint(0., 0., 0.)

Code: Alles auswählen

DcPts.SetCommandPoint(0., 10., 0.)

Code: Alles auswählen

DcPts.SetCommandPoint(5., 8.6667., 0.)
dann:

Code: Alles auswählen

DcCurve.Draw
und der Spline wird gezeichnet. Nur mit dem "Sammelbefehl" für die Punkte funzt es nicht.

Ich hatte ja bei Eröffnung des Themas auch die Frage gestellt, wie denn eine DLL "aussehen" müsste, die mir die von mir benötigten COM-Funktionalitäten sozusagen nach Python liefert. Eine einfache DLL mit Lazarus, die zwei Zahlen addiert ist kein Problem. Das hatte ich hinbekommen. Nach gleichem Muster was mit "COM" gebastelt, die flog mir um die Ohren...

Was wäre dabei zu beachten?

Gruß

Zarathustra
__deets__
User
Beiträge: 3477
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 27. Oktober 2017, 17:10

Fliegt um die Ohren ist halt eine von diesen Dingen zu denen man nix sagen kann :K

Da braucht man schon konkrete Fehlermeldungen. Und natürlich muss Lazarus es unterstützen DLLs mit normaler C-ABI zu bauen. Ob es das tut kann ich nicht sagen, Pascal hatte ich das letzte mal in der Oberstufe.
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Freitag 27. Oktober 2017, 22:38

Hallo zusammen,

na dann mal ein Beispiel was funktioniert (Pascal-Quelltext):

Code: Alles auswählen

library PythonDLL;

{$mode objfpc}{$H+}

function zwei(a, b: Integer): Integer; cdecl;
begin
  zwei := a + b;
end;

exports
    zwei;
end.
Der Aufruf in Python (TestDLL.py zum Aufruf liegt im Ordner der DLL):

Code: Alles auswählen

import ctypes
meinedll = ctypes.cdll.LoadLibrary("PythonDLL.dll")
ausgabe = meinedll.zwei(7,5)
print "Ergebnis = " + str(ausgabe)
Es kommt Ergebnis = 12

Jetzt der Umbau zur "COM-DLL" (Pascal-Quelltext):

Code: Alles auswählen

library PythonDLL;

{$mode objfpc}{$H+}

uses Classes, SysUtils, activex, lazactivex, DesignCAD_23_0_TLB,
ComObj, Types, SysConst, Forms, Interfaces;

var
  DcApp: DesignCAD_23_0_TLB.Application;

function DCSTART(): Integer; cdecl;
begin
   DcApp := GetActiveOleObject('DesignCAD.Application.23') as IDcadApp;
   DcApp.Visible := True;
   DcApp.BringToTop;

   Result := 0;
end;

exports
    DCSTART;
end.
Aufruf in Python:

Code: Alles auswählen

import ctypes
meinedll = ctypes.cdll.LoadLibrary("PythonDLL.dll")
ausgabe = meinedll.DCSTART()
print "Ergebnis = " + str(ausgabe)
Fehlermeldung: WindowsError: [Error -532262845] Windows Error 0xE0465043

Verwende ich zum Aufruf meinedll = ctypes.windll.LoadLibrary("PythonDLL.dll") kommt die gleiche Fehlermedung.

Dass das beim ersten Mal nicht so einfach klappt, dachte ich mir. Hat jemand sowas schon mal probiert?

Viele Grüße

Zarathustra
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Samstag 28. Oktober 2017, 10:03

Guten Tag zusammen,

hier mal der Quelltext meines Problemes in Python mit comtypes:
Das hier funktioniert:

Code: Alles auswählen

from comtypes.client import GetActiveObject as gao
from comtypes.gen import DesignCAD as DC23

DcApp = gao(DC23.Application, None, DC23.IDcadApp)
DcApp.Visible = True
DcApp.BringToTop()

DcDoc = gao(DC23.Document, None, DC23.IDcadDoc)

DcPts = DcDoc.CmdPoints
DcCurve = DcDoc.Curve

DcCurve.DrawType = 0

#Punkte = ([0., 0., 0., 10., 0., 0., 5., 8.6667, 0.])
#DcPts.SetCommandPoints(3, Punkte)

DcPts.SetCommandPoint(0., 0., 0.)
DcPts.SetCommandPoint(0., 10., 0.)
DcPts.SetCommandPoint(5., 8.6667, 0.)

DcCurve.Draw
und modifiziert eben nicht:

Code: Alles auswählen

from comtypes.client import GetActiveObject as gao
from comtypes.gen import DesignCAD as DC23

DcApp = gao(DC23.Application, None, DC23.IDcadApp)
DcApp.Visible = True
DcApp.BringToTop()

DcDoc = gao(DC23.Document, None, DC23.IDcadDoc)

DcPts = DcDoc.CmdPoints
DcCurve = DcDoc.Curve

DcCurve.DrawType = 0

Punkte = ([0., 0., 0., 10., 0., 0., 5., 8.6667, 0.])
DcPts.SetCommandPoints(3, Punkte)

"""DcPts.SetCommandPoint(0., 0., 0.)
DcPts.SetCommandPoint(0., 10., 0.)
DcPts.SetCommandPoint(5., 8.6667, 0.)"""

DcCurve.Draw
Das Programm stürzt nicht mit Fehlermeldung ab, sondern DcCurve.Draw stellt den Spline nicht dar.

Ich habe dann in DesignCAD den Cursur zur Verfügung und kann Punkte für einen Spline setzen. Wenn ich das manuelle Punkte setzen beende wird ein Spline dargestellt und das Pythonscript endet ordnungsgemäß, ohne irgendeinen Fehler. Ich anstelle einiger willkürlicher Punkte zu setzen auch einfach "Escape" drücken. Dann wird halt nix dargestellt und das Pythonscript ebdet ohne Fehlermeldung.

Das gibt mir zu Denken und deshalb frage ich hier im Forum. Es könnte ja sein, dass jemand Anderes so ein ähnliches Problem auch mal hatte und ggf. helfen kann.

Vielen Dank

Gruß

Zarathustra
__deets__
User
Beiträge: 3477
Registriert: Mittwoch 14. Oktober 2015, 14:29

Samstag 28. Oktober 2017, 12:45

Ich würde da mit pdb durchsteppen und mir anschauen, was wo wie passiert. So durch pures draufgucken wird man das nicht lösen. Dann hab ich das hier noch gefunden: http://www.rohitab.com/apimonitor

Damit kannst du versuchen Unterschieden im Methodenaufruf auf die Schliche zu kommen (mit Lazarus & mit Python laufen lassen & schauen wo unterscheide sind)
zarathustra
User
Beiträge: 27
Registriert: Samstag 17. April 2010, 23:02

Donnerstag 16. November 2017, 20:24

Hallo und guten Abend,

vielen Dank nochmal für Eure Tipps. Die Sache mit dem DLL-Überwachen habe ich zunächst hinten angestellt, da es mir etwas zu kompliziert erscheint. Dafür habe ich meine Vorgehensweise bei der Zuweisung der Variablen etwas geändert, und schon ändern sich die Fehlermeldungen.

Code: Alles auswählen

from comtypes.client import GetActiveObject as gao
from comtypes.gen import DesignCAD as DC23
import ctypes
from ctypes import c_int
import numpy as np


DcApp = gao(DC23.Application, None, DC23.IDcadApp)
#DcApp = gao("DesignCAD.Application.23")
DcApp.Visible = True
DcApp.BringToTop()

#DcDoc = gao(DC23.Document, None, DC23.IDcadDoc)
DcDoc = DcApp.ActiveDocument

DcCurve = DC23.IDcadCurve #Anstelle DcApp.Curve
DcPts = DC23.IDcadCmdPoints #Anstelle DcApp.CmdPoints

Punkte = np.array([0., 0., 0., 10., 0., 0., 5., 8.6667, 0.])

DcCurve.DrawType = 0

DcPts.SetCommandPoints(3, Punkte)

DcCurve.Draw
führt zur Fehlermeldung:
Traceback (most recent call last):
File "<string>", line 420, in run_nodebug
File "C:\Users\volkerschafer\Documents\DesignCADTests\dcTest_3.py", line 26, in <module>
DcPts.SetCommandPoints(3, Punkte)
TypeError: Expected a COM this pointer as first argument


Da konnte ich mir behelfen, indem ich die DcPts.SetCommandPoints-Zeile wie folgt änderte:

Code: Alles auswählen

DcPts.SetCommandPoints(c_int(3), Punkte)
führt zur Meldung:
Traceback (most recent call last):
File "<string>", line 73, in execInThread
File "<string>", line 44, in __call__
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 196, in __call__
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 71, in syncreq
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 431, in sync_request
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 379, in serve
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 337, in _recv
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\channel.py", line 50, in recv
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\stream.py", line 166, in read
EOFError: [WinError 10054] Eine vorhandene Verbindung wurde vom Remotehost geschlossen


Starte ich aus der Eingabeaufforderung, dann stürzt python einfach ab. Python funktioniert nicht mehr. Das Programm wird aufgrund eines Problems nicht richtig ausgeführt...
Ich nutze momentan Python aktuelles 3.6.3, allerdings 64 bit.
Habe ich das mit dem c_int(3) als COM-Pointer richtig gemacht? Wer ist der Remotehost? Muß ich da in der Firewall/Windows Defender Freigaben für Python setzen? Wenn ja, welche?

Kann es sein, dass COM-Automatisierung mit Python bei einigen zu automatisierenden Programmen auch mal nicht richtig funktioniert?

Mit Excel-VBA funzt es tadellos, sogar noch angenehmer wie mit Lazarus. Andererseits zeigt "xlwings", dass COM und Excel gut funktionieren.

Für Hilfe, Tipps und Anregungen bin ich dankbar.

Viele Grüße

Zarathustra
__deets__
User
Beiträge: 3477
Registriert: Mittwoch 14. Oktober 2015, 14:29

Freitag 17. November 2017, 01:19

Das ist ohne es genau nachzuvollziehen zu können jetzt geraten, aber es sieht so aus als ob du statt Instanzen jetzt halt Interfaces benutzt. Und das geht nicht, weil du eben mehr als nur die Schnittstelle brauchst, du brauchst schon wen, der die implementiert. Alles was dann passiert sind irgendwie Folgefehler.

Ich habe dir schon gesagt, was ich tun würde. Wenn du das nicht tun magst - neue Ideen habe ich nicht.

Ggf findet du eine comtypes-spezifische Mailingliste oder ähnliches.

Das VBA besser mit COM kann ist kein Wunder. VBA IST COM. Kannst ja mal von VBA Python ansprechen, dann bekommst du auch das fluchen...
Antworten