Python Programming on Win32: Chapter 12 Advanced Python and COM
Total Page:16
File Type:pdf, Size:1020Kb
Python Programming on Win32: Chapter 12 Advanced Python and COM http://www.oreilly.com/catalog/pythonwin32/chapter/ch12.html Python Programming on Win32 Help for Windows Programmers By Mark Hammond & Andy Robinson 1st Edition January 2000 1-56592-621-8, Order Number: 6218 672 pages, $34.95 Chapter 12 Advanced Python and COM In Chapter 5, Introduction to COM , we presented some basic material about Python and COM. If you have never used Python and COM together or are unfamiliar with basic COM concepts, you should review that chapter before continuing here. In this chapter we take a more technical look at COM and using Python from COM. We initially provide a discussion of COM itself and how it works; an understanding of which is necessary if you need to use advanced features of COM from Python. We then look at using COM objects from Python in more detail and finish with an in-depth discussion of implementing COM objects using Python. Advanced COM In order to fully understand Python and COM, it is necessary to understand COM itself. Although Python hides many of the implementation details, understanding these details makes working with Python and COM much easier. If you want to see how to use Python to control COM objects such as Microsoft Word or Excel, you can jump directly to the section "Using Automation Objects from Python ." Interfaces and Objects COM makes a clear distinction between interfaces and objects . An interface describes certain functionality, while an object implements that functionality (that is, implements the interface). An interface describes how an object is to behave, while the object itself implements the behavior. For example, COM defines an IStream interface, a generic interface for reading and writing, in a manner similar to a file. Although COM defines the IStream interface, it's the responsibility of objects to implement the interface; thus, you may have an object that implements the IStream interface writing to and from files or an object implementing the IStream interface using sockets, and so forth. This is a huge advantage to users of these interfaces, because you can code to the IStream interface, and your code works regardless of whether your data goes to a file or out over a socket. Each COM interface has a unique 128-bit GUID known as an interface ID (IID). An interface defines a series of methods: interfaces can't have properties. An interface is defined in terms of a C++ vtable . Highly experienced C++ programmers will know that a vtable implements virtual methods in C++. 1 of 33 23/8/2005 11:33 Python Programming on Win32: Chapter 12 Advanced Python and COM http://www.oreilly.com/catalog/pythonwin32/chapter/ch12.html Just as with C++, COM allows one interface to derive from, or extend, another interface; in fact, COM explicitly requires it. COM defines an interface known as IUnknown , which is the root (or base) of all COM interfaces; that is, all COM interfaces explicitly support the IUnknown interface. IUnknown is a simple interface defining only three methods: AddRef() , Release() , and QueryInterface() . AddRef() and Release() manage object lifetimes; a reference counting technique is used so a particular object knows when it is no longer needed. The Python COM framework manages this behind the scenes for you, so these will not be discussed further. QueryInterface() allows an object to return a specific interface, given that interface's unique IID. Thus, regardless of the object you have, you can always call its QueryInterface() method to obtain a new interface, such as IStream . COM also defines a standard technique for identifying and creating objects themselves. Each object class is identified by a class ID (CLSID, also a GUID) that exposes interfaces, each identified by an IID. Thus, there are a number of identifiers associated with every COM object: the CLSID identifying the class that provides the object, and a series of IIDs for each interface the object supports. Each object supports at least two interfaces, the IUnknown interface as described previously, and some useful interface (such as IStream ) that allows the object to perform its task. Objects may also register a program ID, or ProgID as well as a CLSID. A ProgID is a string describing the object, suitable for use by humans. When you need to create a particular object, it's usually more convenient to use the ProgID rather than the CLSID. There is no guarantee that ProgIDs will be unique on a given system; you should choose the names of your objects carefully to avoid conflicts with other objects. For example, the Microsoft Excel object has a ProgID of Excel.Application . The IDispatch Interface The COM architecture works well for languages such as C++, where the methods you need to use are known beforehand (i.e., at compile time). You create an object using the standard COM techniques, then perform a QueryInterface() on the object for a particular interface. Once you have the interface, you can make calls on its methods. This architecture does have some drawbacks, notably: There is support for methods, but no support for properties. In many cases, properties would simplify the object model you are attempting to publish. It doesn't work as well when using higher-level languages than C++. There may be no compile-time step involved at all. The language in use may not support using the .IDL or .H files necessary to obtain the definition of these interfaces. COM defines the IDispatch interface specifically to meet the requirements of these higher-level languages. The IDispatch interface allows an object to expose an object model (complete with methods and properties) and allows the user of the object to determine the methods and properties available at runtime. This means the methods or properties you need to call can be determined when you need to call them, rather than requiring them to be predefined. You should note that the object model exposed using IDispatch is quite distinct from the IDispatch interface itself; IDispatch is a COM interface that allows an arbitrary object model to be exposed. In other words, IDispatch is not the object model but is the mechanism that allows an object model to be exposed. There are two methods IDispatch defines for this purpose. The first is GetIDsOfNames() ; it allows you to ask an object "do you have a method/property named foo ?" If the object does have such an attribute, it returns an integer ID for the method or property. The method Invoke() performs the actual operation on the object--that is, either calling the method foo , or getting or setting a property named foo . The Invoke() method is passed the integer ID obtained 2 of 33 23/8/2005 11:33 Python Programming on Win32: Chapter 12 Advanced Python and COM http://www.oreilly.com/catalog/pythonwin32/chapter/ch12.html from GetIDsOfNames() , as well as any parameters for the function or property. In almost all languages, you don't need to use the IDispatch interface; your language uses IDispatch behind the scenes to present a natural model. For example, we'll see later that when you execute code in VB, Python, Delphi, and so forth similar to: workbook = excel.Workbooks.Add() behind the scenes, there is pseudo-code similar to: propertyId = excel->GetIDsOfNames("Workbook") newObject = excel->Invoke(propertyId, DISPATCH_PROPERTYGET) methodId = newObject->GetIDsOfNames("Add") result = newObject->Invoke(methodId, DISPATCH_METHOD) The final piece of this puzzle relates to how the arguments and results are passed around. For this purpose, COM defines a VARIANT data structure. A VARIANT is defined as a self-describing C++ union and allows a wide variety of common data-types to be passed. To create a VARIANT , indicate the type of data you wish to pass and set the value. When you need to use a VARIANT passed by someone else, first query the type of data it holds and obtain the data. If the type of the data doesn't work for you, you can either attempt a conversion or reject the call returning the appropriate error code. This implies that type checking of the parameters and results can happen only at runtime (although many tools can take advantage of type information provided by the object to flag such errors at compile-time). As with the IDispatch interface itself, most high-level languages hide the details of the VARIANT and use them invisibly behind the scenes. Objects that expose an IDispatch interface to support method calls and property references are also known as automation objects . Late- Versus Early-Bound IDispatch The process described for IDispatch has one obvious flaw: it seems highly inefficient, and it is! In many cases, the inefficiency isn't important; the objects you need to call will often take longer to do their thing than it took to make the call. Programs or languages that use IDispatch in the manner described are known as late-bound , because the binding of objects to methods or properties is done at the last possible moment, as the call or property reference is made. There is, however, a technique automation objects use to publish their object model in a type library. Type libraries define a set of interfaces a program can use to determine both the methods and properties themselves, and other useful information, such as the type of the parameters or return values. Languages or environments may be capable of using this information at compile-time to provide a better interface to the objects. The key benefits of knowing this information before it's used are: The GetIDsOfNames() step described previously can be removed, as the type information includes the integer ID of each method or property.