Python and COM Greg Stein Mark Hammond
Tutorial Agenda • Introduction to COM • PythonCOM Framework • Using Python as a COM client • Using Python as a COM server • Advanced topics • Futures
COM/OLE and ActiveX • Component Object Model • Specification for implementing and defining objects • OLE is the old name, COM is the official name, ActiveX is the marketing name • All the same - the Microsoft Marketing Machine
What is a COM interface? • A technique for implementing objects • Uses a “vtable” to define methods • Does not have properties • Basically a C++ object with only public methods • But not C++ specific - just borrowed implementation technique • Allows implementation from other languages • All interfaces have a unique ID
IUnknown • Base class of all interfaces • Every COM object must implement • Defines object lifetimes • Reference counted using “AddRef” and “Release” methods • Defines techniques for querying the object for something useful • QueryInterface method • No other functionality - simply defines these 3 methods
Custom interfaces • Any interface derived from IUnknown • Therefore must follow IUnknown rules for lifetimes • Derived? • Means object can be used as its base. Simple implementation using vtables • All interfaces are custom interfaces • Not a standard part of COM per-se, just follow its rules
Objects vs. Interfaces • Interfaces simply define functionality • Objects, once instantiated, implement the interface • Object class also has unique ID • Objects always provide multiple interfaces • At least IUnknown and some other functional interface
CLSIDs, GUIDs, UUIDs, IIDs • COM defines 128 bit identifier, and API for creating them • “High degree of certainty” that these are globally unique • All use the same type and implementation, acronym reflects different usage • IID == Interface ID, GUID == Globally Unique Identifier, CLSID == Class ID, UUID == Universally Unique Identifier, etc • API for converting to and from Strings
Registering Objects • Objects register themselves in the Windows Registry • Register with their Unique CLSID • Also register a name for the object • COM provides API for translating, but names are not guaranteed unique • Many objects self register
Creating Objects • Standard COM API for creation • CoCreateInstance, passing: • CLSID identifying the object to create • IID identifying the initial interface requested
C++ Pseudo Code // Create an object, but only get back // IUnknown pointer, with new reference IUnknown *pUnk = CoCreateInstance(“MyObject”, ...) // Ask the object for a pointer to a useful implementation! pUseful = pUnk->QueryInterface( IID_IUseful, ...) pUnk->Release(); // finished with this. pUseful->DoSomethingUseful(); … pUseful->Release(); // Object now dies…
Custom Interface Example1 of 2 • Native Interfaces using Word • You would be unlikely to use Word this way • Demonstrative purposes only! • >>> import ni, win32com, pythoncom>>> o=pythoncom.CoCreateInstance \ ("Word.Application", None, pythoncom.CLSCTX_ALL, pythoncom.IID_IUnknown)>>> o<PyIUnknown at 0x834b04 with obj at 0x423474>
Custom Interface Example2 of 2 • >>> o.QueryInterface( \ pythoncom.IID_IPersist)<PyIPersist at 0x85dd54 with obj at 0x423464> • Almost identical to the pseudo code above • In fact, Python is far better than C++, as long as we support the required interfaces natively • No AddRef or Release required, or even exposed • Release() currently exposed, but not for long!
IDispatch - poor man’s COM1 of 2 • Also known as “Automation” • Derived from IUnknown • Defines vtable methods to determine “dispatch methods and properties” at runtime • Perfect for scripting languages which have no compile step, or which are not C++! • Optionally uses Type Libraries so optimizations can be made at compile time
IDispatch - poor man’s COM2 of 2 • What many people know as COM • Microsoft marketing machine • In reality, a small, but somewhat useful part of COM • Many useful COM interfaces do not support IDispatch • Native MAPI, Active Scripting/Debugging, ActiveX Controls, Speech Recognition, etc • Very hard from C/C++, very easy from VB
Core InterfacesIntroduction • COM tends to use interfaces for everything. Example: • Instead of using a file pointer/handle, a “Stream” interface is used, which provides file like semantics • Anyone free to implement the stream interface using any technique they choose • Such interfaces not necessarily part of COM per-se, but we consider them “core interfaces”
Core InterfacesEnumerators • Enumerators provide access into a list of values • Provides Next, Skip, Reset and Clone methods • Different enumerator interfaces for different types: • IEnumGUID - Enumerate list of GUID’s • IEnumFooBar - Enumerate list of FooBars!
Core InterfacesCollections • Alternative technique for accessing lists • Usually only used via IDispatch • Uses “tricks” only IDispatch has available, such as properties! • Therefore not a real interface • Used to provide array like semantics for VB, etc • Methods include Count() and Item(). Count often implied by len(), Item() often omitted.
Core InterfacesStreams and Storage • IStream provides file like semantics • IStorage provides file system like semantics • Programs can write to this specification without needing to know the destination of the data • COM provides implementations of these for “structured storage files”
Core InterfacesMonikers • Provide file name to object mapping semantics • Fundamental concept is to provide an indirection level to an underlying object, and a program neutral way of accessing the underlying object • File and URL Monikers do just that • Pointer monikers allow anyone to implement an abstract indirection to an object (e.g., into a message store, etc)
Core InterfacesConnectionPoints • Provides simple callback functionality • Client sets up connection point object • Object passed to Connection Point Container object • Container calls methods on the Connection Point when appropriate • Typically used as an event mechanism (e.g., ActiveX Controls). This is how VB finds the list of events for an object.
Core InterfacesAnd the Rest • Plenty of others not listed here • Anything in core PythonCOM is considered core • By us, anyway - YMMV :-) • Check out the sources, Help Files, or forthcoming documentation • Who was going to write that? • Extensions to PythonCOM - present and future
Error Handling • All methods use HRESULT return code • Multiple “success” codes, and many failure codes • ISupportErrorInfo interface for richer error information • IDispatch uses EXCEP_INFO structure • PythonCOM transparently maps these • More detail in Server section
PythonCOM Framework • Supports use of Python for both COM servers and COM clients • Easy for the Python programmer • Dispatch friendly with core support for most common vtable interfaces • Easily extended for new vtable interfaces
PythonCOM Extensions • Model allows for COM extension DLLs • Once loaded, looks like native support to the Python programmer • MAPI, ActiveX Scripting and Debugging all use this technique • Import them once, and PythonCOM will serve up their interfaces • Makes for stable core, with more frequent extension releases
Python COM ClientsThe Problem • Calling a COM object from Python • COM = vtable = C++ (not Python) • IDispatch removes vtable requirement • Imposes coding burden on client • IDispatch is still vtable based, so core problem remains
Python COM ClientsThe Answer • We need an intermediary between a Python object and COM’s vtables • These are called “interfaces” (c.f. “gateways” for the server side - poor choice of terminology, but this is what we use!)
PythonCOM Interfaces1 of 3 • Very similar to standard Python extension modules • Conceptually identical to wrapping any C++ object in Python • 1:1 mapping between the COM pointer and Python object • Pulls apart arguments using PyArg_ParseTuple • Makes call on underlying pointer • Handles errors, exceptions, and return values
v-table interface Python C++ PythonCOM Interfaces2 of 3 PythonCOM Interface Server Client Interface Interface
IDispatch interface Python C++ PythonCOM Interfaces3 of 3 PythonCOM Interface IDispatch Client Wrapper Server
IDispatch vs. vtable • IDispatch implemented in PythonCOM.dll like any other interface • No Dynamic logic implemented in DLL • Only GetIDsOfNames and Invoke exposed • win32com.client Python code implements all IDispatch logic • Calls the above 2 methods dynamically to obtain method and property information
IDispatch Implementation • 2 modes of IDispatch usage • Dynamic, where no information about an object is known at runtime • All determination of methods and properties made at runtime • Static, where full information about an object is known before hand • Information comes from a “Type Library” • Not all objects have Type Libraries (including Python objects!)
Dynamic IDispatch Implementation1 of 5 • Implemented by win32com.client.dynamic • Also makes use of win32com.client.build • Uses __getattr__ and __setattr__ methods in Python to implement its magic
Dynamic IDispatch Implementation2 of 5 • Not perfect solution as • __getattr__ has no idea if the attribute being requested is a property reference or a method reference • No idea if the result of a method call is required (i.e., is it a sub or a function) • Python must guess at the variable types • Big problem tends to be “byref” params - by default these are not handled
Dynamic IDispatch Implementation3 of 5 • win32com.client.Dispatch kicks it all off • Demo >>> import ni >>> from win32com.client import Dispatch >>> w=Dispatch(“Word.Application”) >>> w.Visible = 1 • Starts Winword, and makes it visible
Dynamic Dispatch Implementation4 of 5 • Pros • No setup steps - just works • Provides quick scripting access to components • Cons • Relatively slow • You need to know the object model of the target. Not self documenting. • Actually, Python can make many objects self documenting, but this is beyond the scope of this
Dynamic Dispatch Implementation5 of 5 • Smart Dispatch vs. Dumb Dispatch • To overcome some potential problems, Python attempts to use Type Info even for dynamic objects • Slows down considerably for certain objects • win32com.client.DumbDispatch provides alternative implementation which does not attempt to locate type information • For many servers, will provide excellent results and speed
Static Dispatch Implementation1 of 4 • Generates .py file from Type Information • win32com.client.makepy does this • Python code then imports this module • Python knows everything about the object • No confusion between methods and properties • Byref args handled correctly • No dynamic lookups - much faster
Static Dispatch Implementation2 of 4 • Demo C:\> cd “\Program Files\Microsoft Office\Office” C:\> \python\python \python\win32com\client\makepy.py msword8.olb > \python\msword8.py ... C:> start python >>> import msword8 # grind, grind :-) >>> w = msword8.Application() >>> w.Visible = 1
Static Dispatch Implementation3 of 4 • Pros • ByRef args handled correctly • Result becomes a tuple in that case • All types handled correctly • Python knows the type required, so doesnt have to guess. More scope to coerce • Significantly faster • Python source file documents methods and properties available
Static Dispatch Implementation4 of 4 • Cons • Need to hunt down type library • Need to enter cryptic command to generate code • No standard place to put generated code • Compiling code may take ages • Not real problem, as this is a once only step • Type library may not be available • Many Cons listed are not permanent - help would be appreciated!
Dispatch, VARIANTs and Python Types • VARIANT • COM concept for IDispatch interface • Just a C union, with a flag for the type, and an API for manipulating and converting • IDispatch always uses VARIANT objects • In reality, COM is not typeless - most servers assume a particular type in the variant • Most (only!) complex code in PythonCOM deals with VARIANT conversions
Dispatch, VARIANTs and Python Types • Python has 2 modes of conversion • Python type drives VARIANT type • Python knows no better • Creates VARIANT based on type of Python object • Known type drives VARIANT type • For static IDispatch, Python often known exactly type required • Attempt to coerce the Python object to VARIANT of this type
win32com.client Files1 of 2 • makepy.py, dynamic.py • Static and dynamic IDispatch implementations respectively • build.py • Utility code used by both modules above • CLSIDToClass.py • Manages dictionary of Python classes, mapped by CLSID. Code generated by makepy.py automatically populates this.
win32com.client Files2 of 2 • combrowse.py • Basic COM browser that requires Pythonwin. Simply double-click on it. • tlbrowse.py • Basic Type Library browser that requires Pythonwin • util.py • Utiility helpers • connect.py • Connection point client base class
Client Side Error Handling1 of 2 • Client interfaces raise pythoncom.com_error exception • Exception consists of: • HRESULT • 32 bit error code, defined by OLE • Error Message • Should be language independant • (cont.)
Client Side Error Handling2 of 2 • COM Exception Tuple • Tuple of (wcode, AppName, AppMessage, HelpFile, HelpContext, scode), all of which are application defined • Exception object itself, or any part of it, may be None • Arg Error • Integer containing the argument number that caused the error • Often None if error does not relate to specific argument