Python és COM

dokumentum verziók:

Ezzel a leírással azoknak akarok segíteni, akik kezdők a fenti témakörben és már megfogalmazódott bennük az alábbi kérdések valamelyike:

A cikkben szereplő mintaprogramok kipróbálásához szükséged lesz a következőkre:

Mi a csuda az a COM

Egyfajta bináris/hálózati szabvány szoftver komponensek használatára és együttműködésére. A COM ún. interfészekkel dolgozik, amiken keresztül hozzáférhetünk a komponensek funkcionalitásához. A COM maga is előre definiál egy rakat interfészt. Egy interfész valójában egy "virtual base class", már akinek ez a C++-os hablaty mond valamit. Nekünk jelenleg elég, ha egy interfész kapcsán egy olyan class-ra gondolunk, akinek mindeféle metódusai vannak, de a metódusok implementációját nem az interfész maga hanem egy másik class - a COM class - tartalmazza. Több interfész is tartozhat egy ilyen COM class-hoz, sőt a gyakorlatban legalább kettő interfésze mindig van.

Azt is szokták mondani, hogy egy interfész nem más mint egy kommunikációs szerződés a komponens és a kliens program között. Például egy bizonyos interfészt különböző komponensek teljesen eltérő módon is implementálhatnak, de az őket használó kliens program számára ez nem is lényeges. Gondoljatok az ActiveX kontrolokra: mindegyik másképp néz ki és más célt szolgál(textbox, label, listbox stb.), de mégis támogatnak olyan előre specifikált interfészeket, amitől ők ActiveX kontrolokká válnak (és pl. a VB fejlesztő környezetében egységes használatot/viselkedést eredményeznek).

Egy COM komponens fizikailag valamilyen modulban található, ami lehet:

Mivel COM komponenseket használhatunk úgy is, hogy a komponens, amit használunk fizikailag egy másik gépen fut, ebben az esetben egy dll is out of process lesz!

A COM bináris volta miatt nem nyelvfüggő megvalósítás. Természetesen a különböző programozás nyelvekben és fejlesztő környezetekben a COM támogatottsága eltérő. Például Visual Basic-ben API használat nélkül csak a default interfész érhető el és nem lehet "mászkálni" az interfészek között. A bináris volta miatt szükség van valamilyen nyelvfüggetlen eszközre, ami információt szolgáltat a komponensekről a különböző fejlesztő eszközöknek. Erre gyakorlatilag két megoldást kínál a Microsoft:

A COM a komponensek, interfészek és gyakorlatilag mindennek az azonosítására 128 bit hosszú számokat használ, amiről az állítja hogy igen nagy valószínűséggel nem fordul elő két azonos. Lehet ProgId-vel is hivatkozni, ami egy beszédes név, de mivel ez a fejlesztő találmánya ebből lehet azonos. A COM csak a 128 bites azonosítóknak hisz. Több fajta elnevezésük is van ezeknek az ID-knek, ami a felhasználási területükre utal:

ID előállítása lehetséges: API hívással, guidgen.exe-vel stb.

Néhány a COM által előre definiált interfész:

Nem volt célom, hogy 5 mondatban megvilágítsam a COM minden rejtelmét, csak annyit akartam a COM-ról írni, hogy az alapján a python-os anyag és programok már érthetőek legyen. Végül még egy rövid terminológiai szótár, ami további COM-os cikkek megértésében lehet segítségedre:

Python-ból hogyan érhetek el COM komponenseket

A Python lehetőséget ad arra, hogy a COM-ot azon a szinten kezeljük, amire mondjuk C++-ban van lehetőség, de támogatja a másik végletet is - amire jó példa Visual Basic - mikor a működési mechanizmus nagy része rejtve marad előlünk.

Legegyszerűbb eset: Dispatch használata

Ha nincs eseménykezelés és, akkor legegyszerűbb a win32com.client.Dispatch használata. A win32com.client.Dispatch maga dönti el, hogy dinamikusan használja a komponenst vagy statikusan. A makepy.py utility segítségével előre legenrálhatunk támogató class-okat a COM komponenshez, ami bizonyos esetekben stabilabb működést eredményez(tipus ellenőrzés stb.). A legutóbbi win32all (build 132) esetében már a támogató class-ok package-be vannak szervezve és csak azok a class-ok vannak ténylegesen legenerálva akiket használni akarunk. A makepy.py használata '-i' paraméterrel kiír az outputra néhány sort, amit scriptünkbe másolva biztosítani tudjuk, hogy a támogató classok mindenképpen létezzenek. A scriptek végén ajánlott meghívni a pythoncom.CoUninitalize függvényt. Minden thread-ben mielőtt COM-ot használnánk meg kell hívni a pythoncom.CoInitialize vagy a pythoncom.CoInitializeEx függvényeket. Ezekben a példákban azért nem hívjuk meg, mert a fő thread-ben az "import pythoncom" kiadásával automatikusan meghívásra kerül a pythoncom.CoInitialize. Figyelem: egy thread-ben többször is hívható a pythoncom.CoInitialize vagy a pythoncom.CoInitializeEx(azonos paraméterrel), de ugyan annyi pythoncom.CoUninitalize hívásra is szükség van. A példa program Excel automation-t mutat be.

Python forrás

Események kezelése, ha van type library: DispatchWithEvents használata

Eseménykezeléshez a kliens oldalon egy sink-re, egy minimális COM class-ra van szükség. A sink IUnknown interfészét adjuk át a megfelelő connection point -nak(Advise metódus). Python-ban ebből a legegyszerűbb esetben szinte semmit sem látunk csak egy class-t, akinek "On"+eseménynév nevű metódusai vannak. A DispatchWithEvents-nek paraméterként átadjuk ezt a class-t, a többit elintézi a mögöttes infrastruktúra. Ez a megoldás type library-t igényel. Ha még nincs python class generálva a type library-ból, akkor meg fog jelenni a makepy.py progress bar-ja is(másodszori futtatásnál már nem). A példában Word-öt indítunk el, majd kilépünk belőle és a kilépés tényét - mint eseményt - kapjuk el.

FIGYELEM: eddig kisbetű/nagybetű nem számított, de mivel támogató Python class-ok lettek generálva itt számít ez is!

A win32com\gen_py alá kerülnek a generált modulok, ahol az eseményeket kezelő metódusok prototípusait is megtaláljuk.

Python forrás

Események kezelése, ha nincs type library: DispatchWithEvents megkerülése

Type library hiányában az előző egyszerű módszer nem járható, kénytelenek vagyunk egy kicsit alacsonyabb szintre menni. A példa az ebben a cikkben ismertetett python szerver komponenst használja.

Python forrás

Nem lokális komponensek elérése: DispatchEx használata

Mi van, ha az első példát(Excel automation) úgy akarjuk megcsinálni, hogy az Excel egy másik gépen fusson? A python forrásban minimális változásra van szükség: Dispatch helyett DispatchEx-t kell használni, mert az utóbbinak azt is meg lehet mondani, hogy melyik gépen hozza létre a komponenst. A példa működéséhez szükséges a rendszer átkonfigurálása is! Csak Windows NT-re tudom, hogy mit kell csinálni mert csak ezt használunk.

A dcomcnfg.exe segítségével a távoli gépen:

  1. engedélyeztük a DCOM-ot
  2. magunknak adjunk jogot komponens indításra/manipulálásra(default launch/access permission).
  3. Az Excel esetében állítsuk be, hogy az identity az interactive user legyen(csak azért, hogy Task Manageren keresztül is piszkálható legyen) a security pedig a default beállításokkal működjön.

A dcomcnfg.exe segítségével a lokális gépen engedélyezzük a DCOM-ot. Ez kell mert alapértelmezés szerint nincs engedélyezve.

A példa szintén Excel-t, használ és megegyezik a korábbi példával csak most az Excel a távoli gépen fut.

Python forrás

Lehetséges-e COM komponensek készítése Python-ban

Természetesen a válasz igen. Python-ban nincs "virtual base class" fogalom mint C++-ban, ezért az interfészek nem önálló class-okként jelennek meg. Nagyon leegyszerűsítve a COM komponens gyártásának menete Python-ban:

  1. Létrehozunk egy class-t azokkal a metódusokkal, amelyek - egy része vagy mind - majd a a komponensünk egyetlen interfészének metódusai lesznek.
  2. A class-nak lesz néhány speciális tulajdonsága, amit a mögöttes infrastruktúra használ fel a komponensként publikálás során:
  3. A python modulunkhoz hozzápakolunk egy néhány soros regisztrációs kódot.
Ezzel kész is vagyunk. Lefuttava a regisztrációs kódot máris használatba vehetjük a frissen elkészült COM komponensünket.

A mintaként mellékelt példa egyetlen metódusa az echo, ami annyiszor visszhangozza a kapott paramétert, amennyi a publikus count tulajdonságban be van állítva.

Python forrás

Események generálásához még némi kiegészítésre van szükségünk. Valahogy IConnectionPoint, IConnectionPointContainer interfészekkel kell kiegészíteni a komponensünket:

  1. Azonosítókat kell generálnunk a connection point interfészeinknek.
  2. A class-unkat a "win32com.server.connect.ConnectableServer"-ből kell származtatni. Ez fogja biztosítani a fentebb említett interfészek kezelését a számunkra.
  3. A _connect_interfaces_ class változóban(lista) fel kell sorolni a connection point interfészek azonosítóit(hogy ConnectableServer tudja kikről van szó).
  4. Figyelni kell rá, hogy a _public_methods_ class változó a ConnectableServer public metódusait is tartalmazza.
  5. A class-unknak legyen __init__ metódusa, ahol hívjuk meg a ConnectableServer __init__ metódusát.
  6. Szükségünk van még egy metódusra, akit a ConnectableServer _BroadcastNotify metódusának adunk meg paraméterként, mikor eseményt generálunk.
Az előző példa módosítása: az echo metódus "IntVolt" eseményt generál, ha a paraméter int tipusú, azaz egy connection point interfészünk lesz egyetlen event-el(IntVolt).

FIGYELEM: Visual Basic-ből ezt az eseményt nem tudjuk kezelni, mert a "Dim WithEvents" nyelvi lehetőség type library-ra épül, az meg itt nincs!

Python forrás

Dióhélyban ennyi a COM komponensek készítése Python-ban.

Összegzés

Remélem a cikk elégséges információkat tartalmaz a Python-COM páros használatához. Egy későbbi cikben szándékozok visszatérni a mögöttes működési mechanizmus ismertetésére és az így megnyiló plussz lehetőségek bemutatására.

Kiss Árpád