Python Forum
win32com: How to pass a reference object into a COM server class
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
win32com: How to pass a reference object into a COM server class
#1
Exclamation 
I'm trying to implement a cross-platform python interface for a third-party API (Antidote), so that it can communicate with a PyQt application.

Firstly, for Linux here's the working D-Bus interface; https://gitlab.com/-/snippets/2151173
Simply put, we create a DBus server (class AdaptateurAntidote) which relay all calls to another class (ImplementationAntidote), which in turn communicates with the GUI widgets.

Then, below is a tentative for the COM interface (notice that the method differ between the two API);
#!/usr/bin/python3
import pythoncom
import sys
import winreg
import win32com.client
import win32com.server.register
import win32com.server.util
from win32com.server import localserver
from pathlib import Path
from PyQt5 import QtCore, QtWidgets


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, app):
        super().__init__()
        self.body = QtWidgets.QTextEdit("Texte à corrigée.")
        self.setCentralWidget(self.body)
        self.antidote = ImplementationAntidote(self, self.body)

    def closeEvent(self, event):
        super().closeEvent(event)
        sys.exit(0)  # Kills the hanging server in QThread


class ImplementationAntidote(QtCore.QObject):
    def __init__(self, parent: QtCore.QObject, body: QtWidgets.QTextEdit):
        super().__init__()
        self.parent = parent
        self.body = body
        # self._register()

        self._launch()
        self.worker = ServerThread()
        self.serverThread = QtCore.QThread()
        self.serverThread.started.connect(self.worker.run)
        self.worker.moveToThread(self.serverThread)
        self.serverThread.start()

        # this should renew a timer until local com server and antidote are both ready  # ##
        QtCore.QTimer.singleShot(1000, self._connect)

    @staticmethod
    def readRegistry(path: str, name: str):
        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_READ)
        value, regtype = winreg.QueryValueEx(key, name)
        winreg.CloseKey(key)
        return value

    def _connect(self):
        version = self.readRegistry(r"Software\Druide informatique inc.\Antidote", "VersionAPI")
        version = min([2.0, float(version)])
        self.server = win32com.client.Dispatch("Correcteur.Antidote")
        self.antidote = win32com.client.Dispatch("Antidote.ApiOle")
        self.antidote.LanceOutilDispatch2(self.server, "C", "", str(version))  # Server, outil, langue, version API

    def _launch(self):
        antidoteFolder = self.readRegistry(r"Software\Druide informatique inc.\Antidote", "DossierAntidote")
        antidotePath = Path(antidoteFolder) / "Antidote.exe"
        QtCore.QProcess.startDetached(str(antidotePath), ["-activex"])

    def _register(self):
        win32com.server.register.UseCommandLine(AdapteurAntidote)


class EventHandler:
    pass


class ServerThread(QtCore.QObject):
    def __init__(self):
        super().__init__()

    def run(self):
        pythoncom.CoInitialize()
        localserver.serve(["{D390AE78-D6A2-47CF-B462-E4F2DC9C70F5}"])  # ## Hangs on quit
        pythoncom.CoUninitialize()  # ## Never called


class AdapteurAntidote:
    _reg_clsctx_ = pythoncom.CLSCTX_LOCAL_SERVER
    _reg_clsid_ = "{D390AE78-D6A2-47CF-B462-E4F2DC9C70F5}"  # pythoncom.CreateGuid()
    _reg_progid_ = "Correcteur.Antidote"
    _reg_verprogid_ = "Correcteur.Antidote.1"
    _reg_class_spec_ = "AntidoteCOM.AdapteurAntidote"
    _public_methods_ = ["ActiveApplication", "ActiveDocument", "DonneDebutSelection", "DonneFinSelection",
                        "DonneIdDocumentCourant", "DonneIdZoneDeTexte", "DonneIdZoneDeTexteCourante", "DonneIntervalle",
                        "DonneLongueurZoneDeTexte", "DonneNbZonesDeTexte", "DonnePolice", "DonneTitreDocCourant",
                        "RemplaceIntervalle", "SelectionneIntervalle"]

    def DonneIdDocumentCourant(self):
        print("DonneIdDocumentCourant")

    def DonneIdZoneDeTexte(self):
        print("DonneIdZoneDeTexte")

    def DonneIdZoneDeTexteCourante(self):
        print("DonneIdZoneDeTexteCourante")

    def DonneIntervalle(self):
        print("DonneIntervalle")

    def DonneLongueurZoneDeTexte(self):
        print("DonneLongueurZoneDeTexte")

    def DonneNbZonesDeTexte(self):
        print("DonneNbZonesDeTexte")

    def DonnePolice(self):
        print("DonnePolice")

    def DonneTitreDocCourant(self):
        print("DonneTitreDocCourant")

    def RemplaceIntervalle(self):
        print("RemplaceIntervalle")

    def ActiveDocument(self):
        print("ActiveDocument")
        # self.parent.activeDocument()

    def ActiveApplication(self):
        print("ActiveApplication")
        # self.parent.activeDocument()

    def DonneDebutSelection(self) -> int:
        print("DonneDebutSelection")
        # return self.parent.donneDebutSelection()

    def DonneFinSelection(self) -> int:
        print("DonneFinSelection")
        # return self.parent.donneFinSelection()

    def SelectionneIntervalle(self, debut: int, fin: int):
        print("SelectionneIntervalle")
        # self.parent.selectionneIntervalle(debut, fin)


if __name__ == '__main__':
    app = QtWidgets.QApplication([])
    gui = MainWindow(app)
    gui.show()
    app.exec()
Similarly a COM server is created (AdaptateurAntidote), which receives calls from Antidote's API. However, due to the nature of COM objects, the adapter class is never instantiated, and won't accept the ImplementationAntidote as its parent attribute.

For instance, if "parent" is declared in _public_attrs_, this;
self.server.parent = self
Results in;
Error:
TypeError: must be real number, not ImplementationAntidote.
If "parent" is not declared;
Error:
AttributeError: 'Correcteur.Antidote.parent' can not be set.
Settings a global variable or setting a "parent" attribute into AdaptateurAntidote also does not work, as it always return its default value (None).

Finally, instead of using Dispatch(), I've tried;
        self.server = win32com.client.DispatchWithEvents("Correcteur.Antidote", EventHandler)
So I could split the COM objects properties in a class, and handle the events in another. However, this gives;
Error:
TypeError: This COM object can not automate the makepy process - please run makepy manually for this object
Documentation is scarce and I'm stuck !
Reply
#2
Always show complete unaltered error messages (tracebacks), they contain extremely valuable information on what leads up to an error.
Reply
#3
"Parent" declared in _public_attrs_
Error:
Traceback (most recent call last): File "Z:\.antidote_\AntidoteCOM.py", line 53, in _connect self.server.parent = self # This File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\win32com\client\dynamic.py", line 559, in __setattr__ self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value) TypeError: must be real number, not ImplementationAntidote
"Parent" undeclared in _public_attrs_
Error:
Traceback (most recent call last): File "Z:\.antidote_\AntidoteCOM.py", line 53, in _connect self.server.parent = self # This File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\win32com\client\dynamic.py", line 565, in __setattr__ raise AttributeError("Property '%s.%s' can not be set." % (self._username_,attr)) AttributeError: Property 'Correcteur.Antidote.parent' can not be set.
Replacing Dispatch() with DispatchWithEvents
Error:
Traceback (most recent call last): File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\win32com\client\__init__.py", line 256, in DispatchWithEvents ti = disp._oleobj_.GetTypeInfo() pywintypes.com_error: (-2147352567, 'Exception occurred.', None, None) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "Z:\.antidote_\AntidoteCOM.py", line 52, in _connect self.server = win32com.client.DispatchWithEvents("Correcteur.Antidote", EventHandler) File "C:\Users\user\AppData\Local\Programs\Python\Python38\lib\site-packages\win32com\client\__init__.py", line 264, in DispatchWithEvents raise TypeError("This COM object can not automate the makepy process - please run makepy manually for this object") TypeError: This COM object can not automate the makepy process - please run makepy manually for this object
Reply
#4
From the documentation, it really seems that DispatchWithEvents() is the way to go in order to relays the events to a regular Python class. However, as shown in the error above it fails on this line (win32com.client::DispatchWithEvents);

ti = disp._oleobj_.GetTypeInfo()
I noticed that when running localserve.serve() in a separate console, the server returns as DispatchWithEvents() is called. No errors are thrown on the server side, but I assume there is something wrong or missing in the class definition of AdaptateurAntidote. I tried changing its policy to dynamic, but that did not help.

If someone could provide a minimal example of a custom server working along with DispatchWithEvents(), that would be very helpful. All the examples I could find use third-party servers such as those provided by Microsoft.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Printing out incidence values for Class Object SquderDragon 3 293 Apr-01-2024, 07:52 AM
Last Post: SquderDragon
  error in class: TypeError: 'str' object is not callable akbarza 2 514 Dec-30-2023, 04:35 PM
Last Post: deanhystad
  How to pass encrypted pass to pyodbc script tester_V 0 858 Jul-27-2023, 12:40 AM
Last Post: tester_V
  how to return a reference to an object? Skaperen 8 1,194 Jun-07-2023, 05:30 PM
Last Post: Skaperen
  can Inner Class reference the Outer Class's static variable? raykuan 6 5,912 Jul-01-2022, 06:34 AM
Last Post: SharonDutton
  How to pass variables from one class to another hobbyist 18 10,730 Oct-01-2021, 05:54 PM
Last Post: deanhystad
  win32com — How to resolve “AttributeError: xlUp” for Excel files? JaneTan 2 4,238 Aug-18-2021, 05:27 AM
Last Post: snippsat
  How to take the tar backup files form remote server to local server sivareddy 0 1,905 Jul-14-2021, 01:32 PM
Last Post: sivareddy
  Python with win32com and EXIF renaming files. Amrcodes 4 3,671 Apr-03-2021, 08:51 PM
Last Post: DeaD_EyE
  AttributeError class object has no attribute list object scttfnch 5 3,452 Feb-24-2021, 10:03 PM
Last Post: scttfnch

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020