Python és COM
dokumentum verziók:
- 1.1 verzió(2000.07.25.):
- kimaradt az előző verzióból a CoInitialize és CoUnitialize függvények használata,
- a makepy.py használatához adtam némi kiegészítést,
- a forrásokat formáztam
- 1.0 verzió(2000.06.23.): ezt már használhatónak itéltem
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:
- Windows 9x, Windows NT vagy Windows 2000 operációs rendszer.
- Python telepítő készlet a fenti operációs rendszerekhez
- Mark Hammond win32all csomagja
- Windows 95 esetében talán COM frissítés is kell, de ebben nem vagyok biztos...
- Microsoft Office
- Magukra a minta programokra
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:
- egy dll. Ezt szokás inprocess szervernek is nevezni.
- egy exe. Ezt szokás out of process szervernek nevezni.
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:
- IDL(Interface Definition Language): ez egy Ascii fájl, ami leírja a
komponensek funkcionalitását.
- Type Library: ez az IDL fájl bináris formája, ami vagy külön .tlb fájlban
van vagy maga a modul tartalmazza.
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:
- GUID: Globally Unique ID
- UUID: Universally Unique ID
- CLSID: COM class azonosító
- IID: interfész azonosító
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:
- IUnknown: ebből származik minden más interfész, ezért három metódusa
minden interfészben megtalálható:
- QueryInterface: ezzel lehet egy IID-val azonosított másik interfészre átmenni
- AddRef: a belső refcount-ot növeli
- Release: a belső refcount-ot csökkenti és ha ez 0-vá vált, akkor kitörli a komponenst
- IDispatch: néha automation interfészként is hivatkoznak rá. Legfontosabb metódusa az
Invoke, ami paraméterként a meghívandó metódus nevét és annak paramétereit kapja meg.
Ez az interfész dinamikus metódus hívást/használatot tesz lehetővé.
- IClassFactory: ez az interfésze(IUnknown-on kívül) a másik COM class-nak,
ami minden modulban benne van. Ez a "COM class gyár" interfész,
szabályosan ennek használatával hozunk létre egy új instance-ot.
Direktben nem nagyon használjuk, de például CoCreateInstance COM API is ezt használja.
- IConnectionPointContainer, IConnectionPoint: ha vannak események is, akkor ezekre az interfészekre
van szükség ahhoz, hogy a komponensnek az eseménykezelőnket(sink) át tudjuk adni.
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:
- Modul: dll vagy exe, ami az egész motyót tartalmazza
- COM class: vagy component class, vagy COM object. Ő valósítja meg a funkcionalitást. Őt fogjuk elérni különböző interfészeken keresztül. Gyakorlatilag az interfészeknek – mint virtual base class-oknak – a leszármazottja.
- Interface: ez egy virtual base class, aminek a metódusait majd inplemetálnunk kell a COM class-ban. Az angol doksikban előszerettel emlegetik az interfészeket mit egyfajta szerződés a kliens és a szerver között(verzió követés).
- Class object: vagy class factory, vagy class factory object. Többnyire minden modulban van egy ilyen. Ki lehet hagyni, ha csak nagyon fapados API-kat használunk, de a célja az hogy magaszintű API-kat használva segítségével állítsuk elő az instance-okat. A „location transparency” az ami miatt fapados API-k használata nem javasolt és jobb ha szerverünkben implemetálunk egy class factory-t, mert akkor az infrastruktúra megkímél bennünket egy csomó rabszolgamunkától. Ez egy speciális COM class, akinek legalább egy interésze van az IClassFactory. Feladata instance-ok létrehozása a COM class-okból.
- Dual interface az az interface, aminek metódusait vtable-n keresztül és IDispatch::Invoke segítségével is el tudjuk érni.
- [OLE] Automation az a funkcionalitás, amit IDispatch –ből származtatott interface-en keresztül érhetünk el. COM előtt is létező technológia, ami mint egy interface épült be a COM-ba.
- Proxy: a TCP/IP-s proxy-hoz hasonló funkcionalitású. Proxy-ra van szükség pl. egy out of process komponens elérése során, ekkor a kliens a proxy-n keresztül kapcsolódik a szerverhez. A kliens számára ez láthatatlan az infrastruktúra automatikusan létrehozza, ha szükség van rá.
- Stub: a proxy párja a szerver oldalon, ő reprezentálja a klienst a szerver számára a szerver processben.
- Sink: a kliens oldalon létrehozott COM instance akinek az IUnknown interfészét adjuk át egy connection point-nak. A sink-ben történik az események kezelése.
- Aggregation és containment/delegation : mind a két esetben egy COM komponens más COM komponenseket használ a funkciók implemetálása során. Containment/delegation-ről beszélünk, ha a külső komponens felhasználja a belsők interfészeit a saját interfészek implemetálása során. Aggregation-ről beszélünk, ha a külső komponens egy vagy több interfészét nem implemetálja, hanem egy másik COM komponens interfészét használja, ami kívülről úgy látszik mintha a külsőben lenne az implementáció.
- Reference counting: ezzel követjük egy, hogy egy instance-ot használ-e még valaki, vagy már törölhető.
- Inprocess szerver: mikor a szerver komponens fizikai megjelenése dll és a kliens is a szerver is azonos gépen fut. Ekkor a kliens process-en belül létezik a szerver komponens.
- Out of process szerver: mikor a szerver exe vagy olyan dll, akit hálózaton keresztül egy másik gépen érünk el. Ha dll a szerver és egy másik gépen fut, akkor természetesen a COM biztosít egy process-t a számára, amin belül futni fog, azaz a fejlesztőnek nem kell feltétlenül extra erőfeszítéseket tennie azért hogy inprocess szerverét hálózaton keresztül távoli gépről futtassák.
- Type Library: lehet önálló fájl vagy tartalmazhatja a modul is. Az interfész definíciókat tartalmazza bináris formátumban(mint az IDL fájl, de az ASCII).
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:
- engedélyeztük a DCOM-ot
- magunknak adjunk jogot komponens indításra/manipulálásra(default launch/access permission).
- 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
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:
- 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.
- 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:
- _public_methods_: azokat a metódusokat kell egy listában felsorolni(a nevüket), akiket meg
akarunk jeleníteni a COM interfészünk metódusaként
- _public_attrs_: azokat a tulajdonságokat kell egy listában felsorolni(a nevüket), akiket meg
akarunk jeleníteni a COM interfészünk metódusaként
- _reg_desc_: valami duma a komponensünkről
- _reg_clsid_: class azonosító, amit valamilyen módon elő kell állítani(guidgen.exe, pythoncom.CreateGuid() stb.)
- _reg_progid_: ez lesz a ProgId, amit például Python-ból vagy
Visual Basic-ből használhatunk
- 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:
- Azonosítókat kell generálnunk a connection point interfészeinknek.
- 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.
- 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ó).
- Figyelni kell rá, hogy a _public_methods_ class változó a ConnectableServer public metódusait
is tartalmazza.
- A class-unknak legyen __init__ metódusa, ahol hívjuk meg a ConnectableServer __init__ metódusát.
- 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