Python és COM, II. rész

dokumentum verziók:

Az előző cikkemet a kezdő lépések megtételéhez szántam segédanyagnak. Most igyekszem a témában egy kicsit mélyebbre ásni és a Python-COM páros kapcsolatának megértéséhez adok újabb információkat:

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

Hogyan lesz egy python objektumból COM objektum? Mi történik a háttérben?

Tömören megfogalmazva a mögöttes infrastruktúra a python objektumból egy IDispatch objektumot csinál.

Mielőtt vázlatos lépésekben ismertetném, hogy a framework hogyan állítja elő ezt az IDispatch objektumot, meg kell ismerkednünk a policy-kkal. A policy gyakorlatilag egy közvetítő a python objektum és a COM objektum között, meghatározza hogy milyen metódusok és attributumok érhetőek el COM-on keresztül, milyen dispatch ID-kat rendel ezekhez stb. Az előző cikkben ismertett példákban azért nem találkoztunk policy-kkal, mert van egy alapértelmezett, a DesignatedWrapPolicy. A DesignatedWrapPolicy-nek kellenek az előző cikk forrásaiban szereplő _public_methods_, _public_attrs_ stb. attributumok. Az előregyártott policy-k közül ebben a cikkben a DynamicPolicy-vel kerülünk közelebbi ismeretségbe. A policy-k python-ban készültek, azaz mi is írhatunk egyet amennyiben a meglévők egyike sem megfelelő számunkra.

Nézzük mit csinál a framework pl. egy VB-ből kiadott CreateObject hatására:

  1. Létrehoz egy policy példányt abból a policy-ből, ami specifikálva lett a python class-unkban(_reg_policy_spec_) vagy használja az alapértelmezett DesignatedWrapPolicy-t.
  2. A policy példány létrehozza a python objektumot.
  3. A policy objektumot becsomagolja egy IDispatch objektumba és ezt küldi el a kliensnek

Python-ban írt COM objektumok nyomkövetése

Van mégegy beékelődő python objektum a dispatcher, az előző folyamat-leírásból azért maradt ki mert fogalmam sincs hogy hová ékelődik be pontosan. Elsősorban debugg-olásra használjuk. Az alapértelmezett DispatcherWin32trace - mint nevéből is látszik - a win32trace modult használja, azaz Pythonwin-ben a a Tools menüből elindítva a "Trace Collector Debugging tool"-t, a megjelenő ablakban nyomon követhetjük, hogy milyen hívásokat kapott objektumunk ill. a nyomkövetési céllal kiadott print utasításaink eredménye is itt jelenik meg. Ha nyomkövetésre van szükségünk, akkor azt COM komponensünk regisztrációjakor kell megadni ill. ha nem megfelelő az alapértelmezett DispatcherWin32trace, akkor a "_reg_debug_dispatcher_spec_"-et beállítva kérhetünk más dispatcher-t. Az előző cikk példáiban is használt UseCommandLine a szkriptünk futtatásakor megadott parancssori paraméterek alapján dönti el, hogy mi a teendője:

Miért fontos az előző folyamat ismerete? Mindaddig amíg egy regisztrált class-unk van és annak metódusai nem akarnak COM objektumot visszadni, igazából nem túl érdekes az egész. Baj akkor van, ha van egy python objektumunk, amit szeretnénk úgy visszadni a kliensnek, hogy az egy kezelhető IDispatch interfészes COM objektumot lásson. Erre való a win32com.server.util.wrap, aki megcsinálja a fenti becsomagoló eljárást. FIGYELEM: minden olyan python class-nak, amit csomagolni szeretnénk rendelkeznie kell azokkal az attributumokkal, amit a használt policy(wrap függvény usePolicy argumentuma) megkíván, de nem kellenek a regisztrációs paraméterek ha nem akarjuk regisztrálni(pl. csak egy másik objektum metódus hívásain keresztül érhető el). A win32com.server.util.wrap függvény párja a win32com.server.util.unwrap, amivel a klienstől visszakapott COM objektumokból kicsomagolhatjuk a python objektumot. Ha olyan COM objektumot próbálunk meg kicsomagolni, aki nem tartalmaz python objektumot(mert nem Python-ban írták) akkor hibaüzenetet kapunk.

DynamicPolicy használata és egyszerű példák

A DynamicPolicy a DesignatedWrapPolicy-vel ellentéttem - mint nevéből is sejthető - dinamikusan futásközben határozza meg, hogy milyen metódusok és attributumok láthatóak a COM-on keresztül, azaz nincs a class-unknak _public_methods_, _public_attrs_ stb. attributuma. Ez a dinamikusság annyira igaz, hogy futás közben gyárthatunk és megszüntethetünk metódusokat, tulajdonságokat. A DynamicPolicy van annyira rendes hozzánk, hogy elintézi az IDispatch interface Invoke és GetIDsOfNames hívásainak kezelését, nekünk már csak nevekkel kell foglalkozni. Class-unknak a szokásos regisztrációs attributumain kívül csak egyetlen kötelező metódusra van a _dynamic_, amiben le kell kezelni a metódusok hívását, az attributumok lekérdezését ill. értékadását vagy adott esetben "le kell tagadnunk azok létét".

Python kliens esetében metódusra hivatkozás is először tulajdonság lekérdezésként jelenik meg (_dynamic_ metódus wFlags argumentuma) és ilyenkor pythoncom.DISP_E_MEMBERNOTFOUND exception-t generálva lehet rávenni, hogy metódusként is próbálja meg. Ez azért van, mert a Python világban a metódus is attributum. VB kliens esetén pedig metódus híváskor a _dynamic_ metódus wFlags argumentuma pythoncom.DISPATCH_PROPERTYGET | pythoncom.DISPATCH_METHOD is lehet, mert a VB bizonyos helyzetekben képtelen eldönteni melyikről van szó. A pythoncom.DISPATCH_METHOD vizsgálatot előre véve és kiegészítve annak viszgálatával, hogy az attributum valóban metódus-e a kérdés megnyugtatóan kezelhető.

A lenti példákban igyekeztem mindent kisbetűsen használni a python forrásokban ill. a kapott paramétereket kisbetűsre konvertáltam, mert pl. a VB-nek mindegy hogy kisbetű vagy nagybetű, de a Python megkülönbözteti.

Az első példa nagyon egyszerű: COM objektumunknak nincs egyetlen metódusa sem, viszont futásidőben korlátlan számú új tulajdonságot hozhatunk létre és azok értékét lekérdezhetjük, módosíthatjuk, sőt tipusukat is megváltoztathatjuk.

A példa forrása és egy teszt kliens.

Második példánk egy buta víz objektum lesz, a H2O. Egy H2O instance allapot tulajdonsága "szilárd" vagy "folyékony" lehet(létrehozás után "folyékony"). Az allapot tulajdonság mindig lekérdezhető, de nem lehet direktben módosítani. A masolj metódus is mindig meghívható és a visszadott érték egy új H2O instance lesz az aktuális állapottal. A fagyjmeg és az olvadjmeg metódusok csak a megfelelő allapot érték esetén léteznek, azaz pl. "szilárd" allapot esetében fagyjmeg hívása "Member not found." hibaüzenetet generál.

A példa forrása és egy egyszerű kliens a teszteléshez.

COMToCorba: egy realisztikusabb példa a DynamicPolicy-ra

A DynamicPolicy használatára egy kicsit bonyolultabb, de realisztikusabb példa a COMToCorba scriptem, mellyel COM-ból lehet Corba objektumokat elérni. Ebben a programban a unicode kezelésére is láthattok példát. Egyrészt a COM belül unicode-os, ezért ami bemegy az unicode-ra konvertálódik és úgy is jön ki belőle. Másrészt a Python 1.52-es verziójában viszont még nem volt unicode kezelés, ezért figyelni kellett például a COM-ból jövő stringekre ha azokat Python-ban is használni akartuk(egy str() hívás megtette). A Python 2.0-ás verzója már kezeli a unicode-ot, így ez a gond megszűnt. Az omniORBpy viszont még nem kezel unicode-ot, tehát miatta(és minden nem unicode-os api használata miatt is) még mindig konvertálgatni kell.

Összegzés

Ebbe a cikkbe ennyi fért bele. Az online elérhető dokumentációkon és programforrásokon kívül ajánlom figyelmetekbe Mark Hammond és Andy Robinson könyvét: Python Programming on win32(néhány dolgot én is ebből szipkáztam:-)). Természetesen a lenti címemen is próbálkozhatsz ha végképp elakadtál.

Kiss Árpád