Python and COM - Blowing the others away

Using Python and COM

This document describes how to use the PythonCOM framework if you are a Python programmer. Before reading this, you may like to become familiar with the win32com package structure. For details on how to extend the core COM functionality, or for a description of how it all works, please see Python COM Implementation documentation.

Described here you will find

Using Client Side COM

Client side COM support is implemented in the win32com.client package. This contains various modules to assist in working with client side objects.

dynamic.py

Dynamic.py is a module which assists in using "dynamic" dispatch objects. Dynamic in this sense means the support is for objects created "on the fly", as opposed to objects that have a dedicated Python module supporting them (as would be created by the makepy module).

Dynamic.py defines two useful functions - Dispatch and DumbDispatch. These are used to create objects on the fly, and provide a nice, natural interface to the underlying object. However, restrictions do apply. Python and COM clash in a number of areas, and although all efforts are made, some gotchyas apply. These will be described in detail later.

Example - Talking to Microsoft Excel

OK - lets jump straight in, and see how we use all this wonderful stuff to do something useful.

First we need to import the "ni" module, and the win32com.client.dynamic module.

>>> import ni, win32com.client.dynamic

Next, we create a COM object for the program identified by "Excel.Application" - ie, Excel itself.

>>> xl = win32com.client.dynamic.Dispatch("Excel.Application")

"xl" is now an instance of "Dispatch", which has an underlying COM object wrapped up inside it.

Excel itself is created invisible - to make Excel show itself, we need to set a property of the Excel application object. First, lets have a look at the property:

>>> xl.Visible

0

OK - not visible - lets set the property to True.

>>> xl.Visible = 1 # Excel instantly shows itself.

Notice that Excel is opened with no Worksheets opened. We will create a new workbook object

>>> xl.Workbooks().Add()
<COMObject <unknown>>

Notice that the Add() method returns a new workbook object. We don't need this object for this demonstration. Lets stick some text in the first cell of the new workbook.

>>> xl.Cells(1,1).Value = "Hello"

And we will see the word "Hello" appear in the top cell.

Note that the usage of this object is quite transparent. All properties work as you expect, as do method calls. Not too many surprises here. Unfortunately, this is because Microsoft Excel exposes type information for almost all of its objects. Python uses this type information to determine what are properties, what are methods, etc.

Some other objects are not as kind. More details on this to follow.

Client Side Hacks

Below are listed some hacks that can be used for client side COM. By definition, these items are all implementation specific, and officially not supported. Use at your own risk, and expect them to change over time.

_print_details_() method
If dynamic.Dispatch is used, the objects have a _print_details_() method available, which prints all relevant knowledge about an object. In the example of Excel above, then all methods and properties would be listed. For objects that do not expose type information, _print_details_ may not list anything.

Makepy.py

makepy.py is a tool which takes a "Type Information" file, and generates a Python source file. This Python source file is then used to interface to the COM object.

Using Server Side COM

Server Side COM is the ability to create objects that expose an interface to other applications. The most common usage if this is to create IDispatch based interfaces - also known as "Automation Objects". Before you can use a server, it must be registered with COM.

Before jumping in to a sample, we need to describe how it all works.

Server Policy

Whenever a Python Server needs to be created, the C++ framework first instantiates a "policy" object. This "policy" object is the gate-keeper for the COM Server - it is responsible for creating the underlying Python object that is the server (ie, your object), and also for translating the underlying COM requests for the object.

All of the underlying COM functionality is handled by this policy object. For example, COM requires all methods and properties to have unique numeric ID's associated with them. The policy object manages the creation of these ID's for the underlying Python methods and attributes. Similarly, when the client whishes to call a method with ID 123, the policy object translates this back to the actual method, and makes the call.

This means there are very few requirements imposed on a Python object that wishes to be a COM server - almost all the details are handled by the policy object on its behalf. The addition of one single line of code to the class is often all that is needed.

It should be noted that the operation of the "policy" object can be dictated by the Python object - the policy object has many defaults, but its operation can always be dictated by the actual Python class.

Minimal Example

Just to demonstrate this, below is a minimal COM server:

class MyTestServer:

_public_methods_ = ['Say'] # This is the extra line needed to enable COM

def Say(self, message)

print message # This probably wont go anywhere useful!

The _public_methods_ attribute identifies the object as a valid COM server to the policy object. It uses this attribute to determine which methods to expose. The only other thing needed is registration of this server in the registry, and it will work.

Policy attributes

The policy object has a few special attributes that define who the object is exposed to COM. The example above shows the _public_methods attribute, but this section describes all such attributes in detail.

_public_methods_

Required list of strings, containing the names of all methods to be exposed to COM. It is possible this will be enhanced in the future (eg, possibly '*' will be recognised to say all methods, or some other ideas…)

_public_attrs_

Optional list of strings containing all attribute names to be exposed, both for reading and writing. The attribute names must be valid instance variables.

_readonly_attrs_

Optional list of strings defining the name of attributes exposed read-only.

_com_interfaces_

Optional list of IIDs exposed by this object. If this attribute is missing, IID_IDispatch is assumed (ie, if not supplied, the COM object will be created as a normal Automation object.

and actual instance attributes:

_dynamic_ : optional method

_value_ : optional attribute

_query_interface_ : optional method

_NewEnum : optional method

_Evaluate : optional method

Exception Handling

Servers need to be able to provide exception information to their client. In some cases, it may be a simple return code (such as E_NOTIMPLEMENTED), but often it can contain much richer information, describing the error on detail, and even a help file and topic where more information can be found.

A scheme has been devised to allow Python Servers to support this functionality in a natural way. The Python server can take advantage of the fairly new Python feature of being able to raise a class instance as an exception. The COM framework will examine this exception, and look for certain known attributes. These attributes will be copied across to the COM exception, and passed back to the client.

To make working with exceptions easier, there is a helper module "exception.py", which defines a single class. An example of its usage would be:

raise Exception(desc="Must be a string",scode=winerror.E_INVALIDARG,helpfile="myhelp.hlp",...)