pythoncom Spezialitäten im Vergleich zu COM in c#

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
derdigge
User
Beiträge: 5
Registriert: Montag 24. Juli 2017, 17:44

Hallo Liebes Forum.

Ich stehe vor folgender Problematik. Mir fehlt die Erfahrung im Umgang mit pythoncom. Im c# habe ich die Möglichkeit com Objekte folgendermaßen
aufzurunfen.

Code: Alles auswählen

m_tpr = new TaiPanRealtime();    // startet eine com Instanz

m_datastream = m_tpr.DataStream as DataStream;    // dockt an die Datastream Objekt

var datastreamEx = m_datastream as IDataStreamEx;    // diese "Magie" hier würde ich gern mit pythoncom nutzen

datastreamEx.EnableExEvent();    // diese Methode ändert den eventhandler auf ein singleevent welches als call den typ des events enthält. Das ist mein Ziel übrigens im python.

m_datastream.ExEvent += M_datastream_ExEvent;    // hier wird an den eventhandler angedockt.
Dies versuche ich in Python like:

Code: Alles auswählen

m_tpr = win32com.client.Dispatch("TaiPanRealtime.Application")        # startet eine COM instanz

stream = m_tpr.DataStream      # Andocken an das Stream Objekt

stream.EnableExEvent();       # kennt er nicht: object has no attribute 'EnableExEvent'

events = win32com.client.WithEvents( stream , EventHandler )
Lasse ich den teil von stream = com.DataStream weg kann ich Events empfangen und verarbeiten. Jedoch brauche ich für jede eventart eine funktion in der EventHandler Klasse. Das ist zum einen total umständlich und führt zum einfrieren bei richtig Traffic auf der Schnittstelle, wenn da events nicht abgedeckt sind.

Vielleicht hat jemand Erfahrungen mit pythoncom? Oder mit c# und versteht besser wie der Code oben sich verhält?

Danke fürs Lesen.

Viele Grüße
derdigge
Zuletzt geändert von Anonymous am Montag 24. Juli 2017, 18:55, insgesamt 1-mal geändert.
Grund: Quelltext in Codebox-Tags gesetzt.
BlackJack

@derdigge: Also wenn ich diesen Beitrag im Lenz+Partner-Forum richtig verstanden habe dann werden bei diesem `ExEvent` genau so viele Ereignisse erzeugt, bloss wird immer der selbe Handler aufgerufen und der Typ des Ereignisses wird als Argument mitübergeben. Ich sehe da jetzt ehrlich gesagt keinen Vorteil die Verteilung selbst zu machen. Die ist potentiell in `win32com` effizienter in C implementiert. Und falls es dort auch in Python-Implementiert ist, müsste man sich wenigstens die Arbeit nicht mehrfach machen.

Was passiert denn wenn man für ein Ereignis keine Methode auf der `EventHandler`-Klasse definiert hat? Was ist so umständlich daran die zu definieren?
derdigge
User
Beiträge: 5
Registriert: Montag 24. Juli 2017, 17:44

Hallo Blackjack.

Es ist so.(Schlussfolgerung aus meinen Beobachtungen) Das ein COM event einen Timeout hat. Es wird gefeuert und landet dann im Handler. Wenn es dort keine Verwendung nach Zeit x erfährt timeoutet es. Da ich aber nicht alle Fälle abdecke? habe ich bei mehreren 1000 Ereignissen pro Sekunde "Stau im Elbtunnel".

Um die Nachmittagszeit ist der meiste Traffic auf dem Handler. Da friert er mir ein und ich habe Zeitweise für 30-40 Minuten gar keine Events mehr.
Ich versuche es zu verstehen daher habe ich mit einer Funktion die mir die Methoden etc von com Objekten ausgibt. Es ist ein Paste aus dem Internetz, verbesserungsvorschläge - verry welcome. [Pastebin]https://pastebin.com/2N8fidNu[/Pastebin]
Aus dieser konnte ich verstehen, das events(events = win32com.client.WithEvents( stream , EventHandler )) folgende Ereignisse feuert.

# {1: 'OnBezahlt', 2: 'OnBrief', 3: 'OnGeld', 4: 'OnBriefMT', 5: 'OnGeldMT', 6: 'OnStatus', 7: 'OnAktuellMeldung', 8: 'OnOpenInterest', 9: 'OnKorrekturOpen', 10: 'OnKorrekturHigh', 11: 'OnKorrekturLow', 12: 'OnKorrekturVolume', 13: 'OnIndiKurs', 14: 'OnNews', 15: 'OnReload', 16: 'OnExEvent'}

Wobei OnReload und OnExEvent nicht dokumentiert sind. Daraus habe ich halt jetzt folgenden Handler geknetet:

[codebox=pys60 file=Unbenannt.txt]
class EventHandler:

# {1: 'OnBezahlt', 2: 'OnBrief', 3: 'OnGeld', 4: 'OnBriefMT', 5: 'OnGeldMT', 6: 'OnStatus', 7: 'OnAktuellMeldung', 8: 'OnOpenInterest',
# 9: 'OnKorrekturOpen', 10: 'OnKorrekturHigh', 11: 'OnKorrekturLow', 12: 'OnKorrekturVolume', 13: 'OnIndiKurs', 14: 'OnNews', 15: 'OnReload', 16: 'OnExEvent'}

def OnBezahlt(self, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis tritt ein (wird gefeuert), wenn ein neuer Bezahltkurs für ein Symbol aus der Überwachungsliste eintrifft.
set_value(self, SymbolNr, Kurs, Volume, Zeit)
#logger("OnBezahlt")
return

def OnBrief(self, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis tritt ein (wird gefeuert), wenn ein neuer Briefkurs für ein Symbol aus der Überwachungsliste eintrifft.
return

def OnGeld(self, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis tritt ein (wird gefeuert), wenn ein neuer Gledkurs für ein Symbol aus der Überwachungsliste eintrifft.
return

def OnBriefMT(self, Position, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis tritt ein (wird gefeuert), wenn ein neuer Briefkurs (Markttiefe) für ein Symbol aus der Überwachungsliste eintrifft.
return

def OnGeldMT(self, Position, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis tritt ein (wird gefeuert), wenn ein neuer Gledkurs (Markttiefe) für ein Symbol aus der Überwachungsliste eintrifft.
return

def OnStatus(self, code):
# Dieses Ereignis tritt ein (wird gefeuert), wenn eine Statusveränderung bzgl. der Verbindung von Tai-Pan Realtime eintrifft.
print(code)

def OnAktuellMeldung(self, url):
# Dieses Ereignis wird gefeuert, wenn eine neue Aktuell-Meldung zu Tai-Pan Realtime verfügbar ist.
return

def OnOpenInterest(self, SymbolNr, OpenInterest, Zeit):
# Dieses Ereignis wird gefeuert, wenn ein neuer Wert für das Open Interest geliefert wird.
return

def OnKorrekturOpen(self, SymbolNr, Kurs, Volume, Zeit):
# Diese Ereignisse werden gefeuert, wenn eine Korrektur zu einem Symbol geliefert wird. Die Art der Korrektur wird durch das Event definiert.
return

def OnKorrekturHigh(self, SymbolNr, Kurs, Volume, Zeit):
# Diese Ereignisse werden gefeuert, wenn eine Korrektur zu einem Symbol geliefert wird. Die Art der Korrektur wird durch das Event definiert.
return

def OnKorrekturLow(self, SymbolNr, Kurs, Volume, Zeit):
# Diese Ereignisse werden gefeuert, wenn eine Korrektur zu einem Symbol geliefert wird. Die Art der Korrektur wird durch das Event definiert.
return

def OnKorrekturVolume(self, SymbolNr, Kurs, Volume, Zeit):
# Diese Ereignisse werden gefeuert, wenn eine Korrektur zu einem Symbol geliefert wird. Die Art der Korrektur wird durch das Event definiert.
return

def OnIndiKurs(self, SymbolNr, Kurs, Volume, Zeit):
# Dieses Ereignis wird gefeuert, wenn ein neuer Indikativer Kurs geliefert wird. Dies ist z.B. bei Open-, Intraday- oder Schluss-Auktionen der Fall.
return

def OnNews(self, Zeit, sWPKs):
# Dieses Ereignis wird gefeurt, wenn eine neue Newsmeldung geliefert wird. Geliefert wird die neuste News mit der Spalte "Wertpapiere" des Newsfensters und des Zeitstempels.
return

def OnReload(self):
# not in documentation
return

def OnExEvent(self, nEventID, SymbolNr, Kurs, Volume, Zeit, LongValue):
# not in documentation
logger( "exevent" + nEventID )
return
[/code]

Hier nochmal als Pastebin
[Pastebin]https://pastebin.com/82C9afVd[/Pastebin]

Heute Abend hat er erstmal funktioniert™. Es ist aber auch nichts los. Ich arbeite mit sublimetext und python in der cmd. Da ich nicht so der "Windowstyp" bin, kenne ich keine andere Möglichkeit. Spyder habe ich unter Windows nicht ans laufen gekriegt. Daher wüsste ich nun nicht, wie ich es besser machen könnte zu sehen wo ein Event "Kleben bleibt". bzw. wie man eine solche COM vernünftig reverse engineered. Nach dem Source Code zum selber stöbern, brauche ich wohl nicht zu fragen bei Lenz und Partner. ;)

Meine Frage hier zielt halt in die Richtung, wie die Finessen beim Aufruf von comobjekten mit pythoncom sind. Es muss ja möglich sein?


Danke und Grüße
derdigge
BlackJack

@derdigge: Zum Code der die Rückrufmethoden ermittelt:

Wozu ist das ``try``/``except`` da? Welcher Attributname taucht denn in `dir()` auf, lässt sich dann aber mit `getattr()` nicht abfragen? Und welche Ausnahme wird da erwartet/ausgelöst? Nackte ``except:`` ohne Konkrete Ausnahme, die dazu einfach *gar nichts* machen, führen gerne mal zu laaaaangen Fehlersuchen wenn da irgendwas auftaucht was man gar nicht erwartet hat.

Die Zeichenkettendarstellung von einem Typ zu vergleichen ist keine gute Idee, denn so wirklich spezifiziert ist das Ergebnis nicht. Im `types`-Modul gibt es `types.InstanceType` das man für Vergleiche heranziehen kann.

Was Du `method` nennst muss keine Methode sein. Das ist erst einmal irgendein Attribut. Und auch `sub_method` ist keine Methode sondern eine Zeichenkette/ein Attributname. Da muss auch keine Methode dahinter stehen.

Ich würde bei den `dir()`-Aufrufen noch ein `sorted()` verwenden um die Ausgabe ein wenig übersichtlicher zu haben. Ungetestet:

Code: Alles auswählen

from types import InstanceType


def show_comkeys(comobj):
    for name in sorted(dir(comobj)):
        attribute = getattr(comobj, name)
        if isinstance(attribute, InstanceType):
            print(name)
            for sub_name in sorted(dir(attribute)):
                if (
                    not sub_name.startswith('_')
                    and 'clsid' not in sub_name.lower()
                ):
                    print('\t' + sub_name)
        else:
            print('\t', attribute)
Mit dem `EventHandler` hast Du Dir ja die Arbeit jetzt schon gemacht. Die Kommentare würden wohl ganz gute Docstrings abgeben.

Wenn in einer Funktion/Methode nichts steht würde ich eher ein ``pass`` als ein ``return`` ohne Rückgabewert verwenden. Und das in Funktionen/Methoden in denen etwas steht dann auch entfernen.

Für Methoden die man nicht wirklich implementieren möchte kann man auch einmal eine Methode schreiben die beliebige Argumente entgegen nimmt, nichts tut, und dann die Namen der zu ignorierenden Methoden auch alle an diese eine Methode binden.

Je nach dem wie win32com die Methoden ermittelt könnte man vielleicht auch einfach mit `__getattribute__()` dafür sorgen das bei jedem Attribut das es nicht auf dem Objekt gibt, eine solche Dummy-Methode geliefert wird.
derdigge
User
Beiträge: 5
Registriert: Montag 24. Juli 2017, 17:44

Moin BlackJack!

Wie gesagt es ist ein Copy and Paste aus dem Internetz. Mein Types hat kein InstanceType :)

[codebox=pys60 file=Unbenannt.txt](Pdb) from types import *
(Pdb) types.InstanceType
*** AttributeError: module 'types' has no attribute 'InstanceType'[/code]

bzw.

[codebox=pys60 file=Unbenannt.txt](Pdb) from types import InstanceType
*** ImportError: cannot import name 'InstanceType'[/code]

Anyway. Das geht glaube ich etwas in die falsche Richtung. Es muss doch, ganz allgemein nicht auf das derzeitige Problem beschränkt, Objekte gezielt(er) anzufragen? Als Beispiel mal an einer Watchliste von Taipan:

im C#:
[codebox=csharp file=Unbenannt.cs]

// liefert ein COM Objekt mit Methoden : Count | Item | Name | Nr
TPRTListen = (IListen)TPRTDataBase.WatchListen;
TPRTWatchListe = (IWatchListe)TPRTListen[1];

// liefert eine COM Objekt mit anderen Methoden: Add | Remove | RemoveAll
TPRTListen = (IListen)TPRTDataBase.WatchListen;
TPRTWatchliste2 = (IWatchListe2)TPRTListen[1];

[/code]

Diese Praktik ist in c# nichts ungewöhnliches und beschränkt sich nicht auf die COM der TaiPan.
Mir ist durchaus bewusst, das mein Anliegen etwas pikant/speziell ist.

Gruß
derdigge
BlackJack

@derdigge: Dann verwendest Du Python 3. Dann wundert mich aber das Du ”old style classes”-Objekte hast, denn die haben diesen Typ `types.InstanceType` in Python 2 und dieser Typ wird als ``"<type 'instance'>"`` als `str()` dargestellt. In Python 3 gibt es diesen Typ nicht mehr und deshalb auch `InstanceType` nicht mehr im `types`-Modul.

Die Objekte haben in C# ja alle Methoden. Die Deklarationen beziehungsweise das casten mit Interfaces erlauben ja letztendlich nicht den Zugriff, sondern schränken ihn auf das Interface ein.

``TPRTDataBase.WatchListen[1]`` hat in Deinem Beispiel die Methoden Add, Count, Item, Name, Nr, Remove, und RemoveAll. Und vielleicht auch noch andere. In Python könnte man einfach auf alle zugreifen ganz ohne casten zu müssen, denn es gibt die statische Typisierung und damit die Beschränkungen durch Interfaces nicht. Nur hast Du da in Python ja kein Python-Objekt sondern ein Proxyobjekt das irgendwie wissen muss wie es Zugriffe von Python aus an das COM-Objekt weiterleiten muss.

Man muss sich dazu wohl näher mit der COM-Schnittstelle und deren Typsystem beschäftigen.

Das die pywin32-Dokumentation als CHM-Datei vorliegt hilft nicht gerade wenn man von einem Linux-System aus helfen möchte und sich das nicht runterladen mag. :-)

Es gibt aber wohl eine `CastTo()`-Funktion in `win32com.client`. Schau mal ob und wie man die verwenden kann.
derdigge
User
Beiträge: 5
Registriert: Montag 24. Juli 2017, 17:44

``TPRTDataBase.WatchListen[1]`` hat in Deinem Beispiel die Methoden Add, Count, Item, Name, Nr, Remove, und RemoveAll. Und vielleicht auch noch andere. In Python könnte man einfach auf alle zugreifen ganz ohne casten zu müssen, denn es gibt die statische Typisierung und damit die Beschränkungen durch Interfaces nicht. Nur hast Du da in Python ja kein Python-Objekt sondern ein Proxyobjekt das irgendwie wissen muss wie es Zugriffe von Python aus an das COM-Objekt weiterleiten muss.
!= True

Das is ja genau der Punkt es sind zwei unterschiedliche Objekte. Wie unten geschrieben kommt es drauf an ( in c# ) wie es gecastet wird.

Es gibt aber wohl eine `CastTo()`-Funktion in `win32com.client`. Schau mal ob und wie man die verwenden kann.
Dit isset! Du hast et voll druff.

https://mail.python.org/pipermail/pytho ... 01728.html
In der Mailing List steht es beschrieben. For example:

C#:
[codebox=csharp file=Unbenannt.cs]
// Allgemeine DeklarationTaiPanRealtime TPRTObjekt;
TPRTObjekt = new TaiPanRealtime();
DataBase TPRTDataBase;
TPRTDataBase = (DataBase)TPRTObjekt.DataBase;

TPRTListen = (IListen)TPRTDataBase.WatchListen;
TPRTWatchliste2 = (IWatchListe2)TPRTListen[1];
[/code]

ist in Python:
[codebox=pys60 file=Unbenannt.txt]from win32com.client import CastTo
import win32com.client

l_com = win32com.client.Dispatch("TaiPanRealtime.Application") # entspricht TPRTObjekt = new TaiPanRealtime();
wl1 = CastTo( l_com.DataBase.WatchListen , "IIListen" ) # entspricht TPRTListen = (IListen)TPRTDataBase.WatchListen;
wl2 = CastTo( wl1[0], "IWatchListe2" ) # entspricht TPRTWatchliste2 = (IWatchListe2)TPRTListen[1];[/code]

an wl2 habe ich nun alle methoden der IWatchliste2 und nichtmehr die der IWatchListe. Somit ist mein Verständnisproblem beseitigt. Danke vielmals. Dieses Verhaltenn kann nun natürlich auf die Ursprüngliche Problemstellung angewandt werden.

Danke und Gruß
BlackJack

@derdigge: Okay, das ist ein bisschen verwirrend — man kann in C# den Cast nicht überladen aber benutzerdefinierte Typkonvertierungen, was letztendlich dann doch nach überladenen Casts aussieht. Abgefahren. :-)
derdigge
User
Beiträge: 5
Registriert: Montag 24. Juli 2017, 17:44

Mich verwirrt es auch noch etwas :) Ich bin nach wie vor noch nicht am Ziel. Die Regentage sind dennoch überraschend produktiv :]
Antworten