Wednesday, July 27, 2005

Taking Advantage of COM with Python

Microsoft's Common Object Model (COM) implements a cross-language object model.COM is the basis for most of Microsoft's recent technologies, such as WMI and Microsoft Office. COM allows you to write code in one language, like C++, and use it in another, such as Python or VB. Python is capable of both using COM objects and creating COM objects. It's been around, in one form or another, for a number of years.

Python has excellent support for COM. In fact, Python's support for COM is so good that COM can be used as a legitimate way to extend Python. If you need to write a Python extension that will be running exclusively on Windows, strongly consider using COM. COM objects can be reused, providing flexibility and reusability, as well.

To use COM in Python, you must install the Python Win32 extensions, which can be downloaded from http://sourceforge.net/projects/pywin32/. The pywin32 package comes in a nice installer package, so compiling isn't necessary.

Using a COM component

The steps for using a COM object is the same for all languages that support COM. First you must instantiate the object. The instantiation can be directly incorporated in the language like Jscript's ActiveXObject function or it can be supported via a library, like Pythons Dispatch function. The only difference between the two approaches is that with library based support you have to import the library code first. Once the object is instantiated, you can use the properties, fields and methods just like they were part of normal Python objects.

To start, run the makepy utility on the component that you intend to use. This isn't a mandatory step, but it has some nice advantages. Specifically, named constants defined in the typelib will be available to you when you import the constants class, and if you're using the PyWin IDE, you'll get intellisense for the properties and methods of the component.

The easiest way to run makepy is through the PyWin IDE. Launch PyWin, and under the tools menu, choose makepy. This opens a simple little dialog that contains a list of all the registed COM components on the system.

You can use a COM component without running the makepy utility, but you'll then have to use numeric values in place of the named constants that you normally use for parameters and such.

Using a COM component in Python is nearly as simple as using the component in VB.First you need to import the COM support module:

import win32com.client as w32c

If you ran the makepy utility on the component, then the constants class has all of the named constants defined in the typelib:

from win32com.client import constants

Once the module is imported, you can instantiate the COM component simply:

Obj = w32c.Dispatch(r"Object.name")

To instantiate a COM component, you need to know the class name of the component. You can find it in the documentation for the component or by looking in the registry for the object, if you know the GUID. However, If you run the makepy utility on the component then both the name of the component and its GUID will be listed in the generated python file.

Once you've instantiated a component, you have access to all the properties and methods of that component. For example the following code calls the load and transform methods of the MSXML control:

xmldoc = w32c.Dispatch(r"Msxml2.DOMDocument")

rval = xmldoc.load(xml)

xmldoc.transformNode(xsldoc)

The file transform.py that accompanies this article shows a complete example that demonstrates how to use the MSXML COM component. To use that example, you need to have the MSXML components installed. (MSXML is installed along with Internet Explorer, so it is usually present on most systems.)

Using COM, you can drive other applications such as Microsoft Office. For instance, Python can be combined with MS Word to create document management systems. Python COM integration is extremely powerful in this regard. On most Windows systems there are numerous COM objects installed. You can browse through them using the makepy utility. Anything that shows up in the makepy utility can be used in Python.

Creating a COM component

Let's look now at what may be the coolest part of Python's COM integration.

Python comes with a large library of modules that do not have equivalents in other languages. By using Python to create COM objects, it is easy to expose some of these modules to other languages.

Creating COM objects in other languages, such as C++, requires a lot of code and knowledge. It also requires that you be familiar with IDL, which is a seperate language. So, to create a COM object in C++, you actually must know two languages.

Contrast that to creating a COM object in Python.

class MD5Obj:

    _public_methods_ = ["Update", "Hash", "Hex"]

    _reg_progid_ = "Python.MD5"

    _reg_clsid_ = "{895E2BD0-0DA7-11D9-9669-0800200C9A66}"

    MD5 = None

    def __init__ (self):

        self.MD5 = md5.new()

        return

    def Update (self, val):

        self.MD5.update(val)

        return

    def Hash (self):

        return self.MD5.digest()

    def Hex (self):

         return self.MD5.hexdigest()

Notice, that there are no non-Python constructs, no IDL, and no funny preprocessor macros —just plain old Python. The code wraps the MD5 module and exposes the methods that it needs. Something else to notice is that this class does not inherit from another class some magical COM class. What makes this a COM class is the three fields, _public_methods_, _reg_progid_, and _reg_clsid_. _public_methods_ informs informs pyWin which methods you want publicly exposed. Usually you want only the methods that are part of you public interface listed in this field. _reg_progid_ is just a string that gives the object a semi-unique name, that can be used to instantiate the object. The _reg_clsid_ field is a GUID (Globally Unique ID) that is the actual unique name of the object. This is also called the class ID. The class ID can be used to instantiate an object but there are various reasons not to do this. It's better to use the program ID for this. There are a number of utilities to create GUID's available on the Internet as well as part of various development environments.

To have the COM object work in a development environment, you have to register it. The easiest way to do this is just create a little code that register's the object when the script file is run:

if __name__=='__main__':

    import win32com.server.register

    win32com.server.register.UseCommandLine(MD5Obj)

That's it. Just run the script and the object is registered. No seperate utility, like regsrv32, to worry about. You can't get much simpler than that.

To use the object from another language, like JScript, is just like using any other COM object.

var md5 = new ActiveXObject("Python.md5");

md5.Update("Th\is is a test");

WScript.Echo(md5.Hash());

Wscript.Echo(md5.Hex());

The full example is available in the MD5Obj.py script.

Wrapping Up

Even though this was a simple example, everything shown here holds true for complex object hiarchies. You have full access to COM's facilities. You can use attributes, read only attributes, methods, and so on. You've got access to it all as well as the full power of Python. It's all easy as (wait for it...) pie.

Consider using Python to prototype your objects before jumping into C++. This gives you the chance to evaluate your object interfaces without having to go through the build and register step each time you change something.

Who knows, you may find that there is no reason to implement the objects in C++ after all.



SIDEBAR:

Using Pyscript

Windows Common Object Model (COM) isn't the only way to integrate Python with Windows. You can also use PyScript.

PyScript integrates Python with the Windows Scripting Host (WSH), Windows' official scripting engine. WSH is interesting because it's scripting language-agnostic, and because it provides an API for adding new scripting languages to the system.

Using WSH, Python has full access many of the core Windows features, such as such as mapping drives, controlling network printers, and remote scripting. WSH also allows Python to be used in login scripts. And since WSH has poor support for creating graphical user interfaces (GUIs), Python is a nice complement. By using pyscript you get everything that the WSH provides as well as all the capabilities and just plain fun that Python provides.

Python's Win32 library has WSH integration, but it's not enabled by default. To enable pyscript you need to run the pyscript.py file. It's default location is C:\Python23\Lib\site-packages\win32comext\axscript\client\pyscript.py. Running this script simply registers Python with WSH. To create a pyscript file, use the pys extension intead of the py or pyw extensions.

(Because of the many exploits involving vbscript and WSH, the pyscript.py registration script does not enable explorer associations. Considering that many network admins are actively disabling explorer associations with .vbs and .js files, this is a smart default. If you would like to have explorer integration, I have included a reg file that will add the explorer integration for you. If you have an odd setup or use an older version of Python you will need to edit the reg file.)

The example script, example.pys, first creates an MD5 object using Python's MD5 module.

import md5

m = md5.new()

m.update("%systemroot%\\system32\\wscript.exe")

Next, it creates a link, on the desktop to the wscript.exe application. The MD5 hash is appended to the end of the link description.

WshShell = WScript.CreateObject("WScript.Shell")

Desktop = WshShell.SpecialFolders("Desktop")

link = WshShell.CreateShortcut(Desktop + "\\Wscript.lnk")

link.Description = "Link to the Windows Scripting Host" + " " + m.hexdigest()

link.IconLocation = "wscript.exe,1"

link.TargetPath = "%systemroot%\\system32\\wscript.exe"

link.Save()

2 comments:

Rave Hanker said...

Hey, I tried doing the same thing and it's not working! I'm using IE 7 beta and implemented the following class

----Helloworld.py--
import pythoncom

class helloworld:
_public_methods_ = ["say"]
_reg_clsid_ = pythoncom.CreateGuid()
_reg_progid_ = "helloworld"
_reg_catids_ = ["{7DD95801-9882-11CF-9FA9-00AA006C42C4}"]

def say(self):
return "Hello World"


if __name__=='__main__':
import win32com.server.register
win32com.server.register.UseCommandLine(helloworld)

--------
I ran this script and called it as a COM object from Python and it worked. BUt when i tried doing the same from IE with the following code, it said "Object doesn't support this property or method"

I've removed the angle brackets in tags since blogger won't allow me to have them
----usecom.html-----
head
title Untitled Page /title

script language="jscript"
function load() {
var obj = new ActiveXObject("helloworld");
obj.say()
}
/script

/head
body
input id="Button1" type="button" value="button" onclick="return load()"
style="z-index: 100; left: 525px; position: absolute; top: 240px" /


/body
/html


-------------
Am i doing something wrong here? -->

Mike said...

IE7 has additional restriction on COM controls. This is likely the problem. I haven't tried IE7 yet so I can't tell you for sure.