Python, C++, and COM

Python a helpful cousin to C++ COM development


Ever want to write your pseudo-code for C++ win32 or COM programming and be able to have it run? In many cases python offers just that.Python offers easy, fast development, yet is still close enough to C++ to be recognizable. For example, compare python's class mechanism and exception handling. With the perspective of being a light overview, we'll look at some details of development of a raw C++ COM component and compare that to python development. What is interesting to focus on, is not how their different, but how they are similar, which is useful to leverage. For example, here's what abstract base classes (an idea used for COM interfaces) look like in python and C++.

Example

Abstract Base classes:

class IFlintstone

{

public:

  virtual TellWilma(long when)=0;

};



class IRubble

{

public:

  virtual AskBetty(long when)=0;

};



#Abtract Base classes for python

#not as formal as C++'s

class IFlintstone:

  def TellWilma(self,when=0):

    raise RuntimeError, 'virtual function'

class IRubble:

  def AskBetty(self,when=0):

    raise RuntimeError, 'virtual function'

Also, in the Win32 world, python and C++ are very similar. You'll notice that if you look at some of the other win32 examples in overviews. Python is capable of some sophisticated win32 programming allowing you to get the problem worked out before you have to write out the C++. For COM, like C++, python is multi-threaded and can live in any apartment. You even have at your disposal PyIUnknown.QueryInterface, pythoncom.CoInitializeEx, etc. much like in C++.. But don't have to worry about casting, reference counts, and such. Python has a translucent rather than black-box approach to COM.


Thus, in COM programming, you can quickly prototype a program, and then if necessary, work on a C++ solution. As already mentioned, a typical means in C++ to develop COM objects is with multiple inheritance. You create class that inherits whatever interfaces (abstract base classes), you want to expose. The only disadvantage to this is that it means that the interfaces can't have the same function names. These interfaces need to be defined using IDL to allow for proxies, resove any ambiguities C++ may have, and generate type libraries. A very simple IDL interface wouuld look like:



IDL interface:

import "unknwn.idl"

[object, uuid(11111111-2222-3333-4444-555555555555)]

interface IFlintstone : IUnknown

{

  HRESULT TellWilma([in] int when);

}

interface IRubble : IUnknown

{

  HRESULT AskBetty([in] int when);

}

After running it through the MIDL compiler, you include the header file created by MIDL and then inherit this just as with the class examples above. Then you define every method for each interface which includes IUnknown, as well as IFlintstone and IRubble. At this point, I'm ommitting some details like the metaclass which implements IClassFactory that creates Bedrock and errorhandeling , but from this class you can see the basic functions you'll have to write.



C++ definition:

#include "file_from_midl.h"

class BedRock : public IFlintstone, public IRubble {

    LONG m_references;



public:

   BedRock() : m_references(0){ } // constructor

   ~BedRock(void) { } //destructor



   // IUnknown Methods not shown in their entirety

   STDMETHODIMP QueryInterface(REFIID riid, void **ppv)

   STDMETHODIMP_(ULONG) AddRef(void)

   STDMETHODIMP_(ULONG) Release(void)



   // IBedRock Methods

   STDMETHODIMP TellWilma(long when)

   {

      write_to_cartoon(when,"Wilma!");

      return S_OK;

   }

   STDMETHODIMP AskBetty(long when)

   {

      write_to_cartoon(when,"Hey uh Betty!");

      return S_OK;

   }

};

How does python manage this? First of all, You do not bother to write IUnknown and IClassFactory (python, being a dynamic language, uses IDispatch to expose the methods you're interested in). Which also means there is no MIDL step you have to go through, if you change what interfaces are going to be used. Python's approach is for you to add attributes to your python class, defining the necessary COM specifics. For example, instead of defining the GUIDs in MIDL, you simply use the _reg_clsid_ attribute to define the class's GUID.


For the other methods, the basic idea is to create a python class and write class attributes that will expose what methods you want for COM. For interfaces which have native support you can include the list of interfaces in a _com_interfaces_ attribute for your class. And, for all interfaces, you simply add the interface's method calls to the list of in the _public_methods_ class attribute. In this case, neither IFlintstone nor IRubble have native support, so we don't bother with _com_interfaces_.


You'll notice the python COM class object is reasonably simple, but similar in spirit to the above.



Basic python COM code

##Abtract Base classes for python

class IFlintstone:

  def TellWilma(self,when=0):

    raise RuntimeError, 'virtual function'

class IRubble:

  def AskBetty(self,when=0):

    raise RuntimeError, 'virtual function'



class BedRock(IFlintstone, IRubble): #no need to use MIDL for these

    _public_methods_ = ['TellWilma', 'AskBetty']

    _reg_progid_ = "Python.Bedrock"

    _reg_clsid_ = "{12345678-1234-5678-1234-567812345678}"

    def __init__(self): #constructur

	pass #not doing anthing w/it

	     #no need to keep track of reference counts

    def __del__(self): #destructor

	pass #not much going on here either

    def TellWilma(self,when=0):

	write_to_cartoon(when,"Wilma!")

    def AskBetty(self,when=0):

	write_to_cartoon(when,"Hey uh Betty")



if __name__=='__main__':

    UseCommandLine(BedRock)

Have a great time with programming with python!

John Nielsen   nielsenjf@my-deja.com