comtypes COM interfaces ======================= .. contents:: Overview -------- To use or implement a COM interface in ``comtypes`` a Python class must be created. Normally is is not needed to write this class manually, ``comtypes.client.GetModule`` creates interfaces from type libraries automatically. However, an understanding of the generated code is certainly useful. If no type library but only an IDL file is available it is often the fastest way to make the interfaces available to Python by compiling the IDL file into a temporary type library, and generate a Python module for it; the type library can be deleted after that because it is not needed any more. It is possible to take the generated module, move it as a template into another location and customize the classes with hand written methods (this is how much of the interfaces in the comtypes package have been created). The COM interfaces in ``comtypes`` are abstract classes, they should never be instantiated. Defining COM interfaces ----------------------- A COM interface in ``comtypes`` is defined by creating a class. The class must derive from ``comtypes.IUnknown`` or a subclass of ``IUnknown``. The interface class must define the following class attributes: ``_iid_`` a ``comtypes.GUID`` instance containing the *interface identifier* of the interface ``_idlflags_`` (optional) a sequence containing IDL flags for the interface ``_case_insensitive_`` (optional) If set to ``True``, this interface supports case insensitive attribute access. ``_methods_`` a sequence describing the methods of this interface. COM methods of the superclass must not be listed, they are inherited automatically. If one or more of the COM methods reference the interface class itself, it is possible to assign the ``_methods_`` attribute *after* the class statement like this:: class ISomeInterface(IUnknown): _iid_ = GUID(...) ISomeInterface._methods_ = [.., POINTER(ISomeInterface), .] **Note:** All the other attributes ``_iid_``, ``_idlflags_``, ``_case_insensitive_`` must be defined when ``_methods_`` is set. The ``_methods_`` list ...................... Methods are described in a way that looks somewhat similar to an IDL definition of a COM interface. Methods must be listed in VTable order. There are two functions that create a method definition: ``STDMETHOD`` is the simple way, and ``COMMETHOD`` allows to specify more information. ``comtypes.STDMETHOD(restype, methodname, argtypes=())`` Calling ``STDMETHOD`` allows to specify the type of the COM method return value. Usually this is a ``comtypes.HRESULT``, but other return types are also possible. ``methodname`` is the name of the COM method. ``argtypes`` are the types of arguments that the COM method expects. ``comtypes.COMMETHOD(idlflags, restype, methodname, *argspec)`` ``idlflags`` is a list of IDL flags for the method. Possible values include ``dispid(aNumber)`` and ``helpstring(HelpText)``, as well as ``"propget"`` for a property getter method, or ``"proput"`` for a property setter method. ``restype`` and ``methodname`` are the same as above. ``argspec`` is a sequence of tuples, each item describing one argument for the COM method, and must contain several items: 1. a sequence of IDL flags: ``"in"``, ``"out"``, ``"retval"``, ``"lcid"``. 2. type of the argument. 3. argument name. .. 4. XXX Are there more??? Since the ``IUnknown`` metaclass automatically creates Python methods and properties that forward the call to the COM methods, there is typically no need to write any Python methods for the interface class (unless you want to override what the metaclass does). An Example ---------- These are two simple COM interfaces. ``IProvideClassInfo`` only contains one method ``GetClassInfo`` (in addition to the three methods inherited from ``IUnknown``). ``IProvideClassInfo2`` inherits from ``IProvideClassInfo`` and adds a ``GetGUID`` method. This is the IDL definition, slightly simplified (from Microsofts ``OCIDL.IDL``): :: [ object, uuid(B196B283-BAB4-101A-B69C-00AA00341D07), pointer_default(unique) ] interface IProvideClassInfo : IUnknown { HRESULT GetClassInfo( [out] ITypeInfo ** ppTI ); } [ object, uuid(A6BC3AC0-DBAA-11CE-9DE3-00AA004BB851), pointer_default(unique) ] interface IProvideClassInfo2 : IProvideClassInfo { HRESULT GetGUID( [in] DWORD dwGuidKind, [out] GUID * pGUID ); } ``comtypes`` interface classes: :: from ctypes import * from comtypes import IUnknown, GUID, COMMETHOD from comtypes.typeinfo import ITypeInfo class IProvideClassInfo(IUnknown): _iid_ = GUID("{B196B283-BAB4-101A-B69C-00AA00341D07}") _methods_ = [ COMMETHOD([], HRESULT, "GetClassInfo", ( ['out'], POINTER(POINTER(ITypeInfo)), "ppTI" ) ) ] class IProvideClassInfo2(IProvideClassInfo): _iid_ = GUID("{A6BC3AC0-DBAA-11CE-9DE3-00AA004BB851}") _methods_ = [ COMMETHOD([], HRESULT, "GetGUID", ( ['in'], DWORD, "dwGuidKind" ), ( ['out', 'retval'], POINTER(GUID), "pGUID" )) ] Using COM interfaces -------------------- As said above, comtypes interface classes are never instantiated, also they are never used directly. Instead, one uses instances of ``POINTER(ISomeInterface)`` to call the methods on a COM object. The ``IUnknown`` COM interface has ``AddRef()``, ``Release()``, and ``QueryInterface()`` methods that you can call. Since the COM internal reference count is handled automatically by comtypes, there is no need to call the first two methods. ``QueryInterface()``, however, is the call that you need to ask a COM object for other COM interfaces. Since IUnknown is the base class of **all** COM interfaces, it is available in every COM interface. So, assuming you have a ``POINTER(IUnknown)`` instance, you can ask for another interface by calling ``QueryInterface`` with the interface you want to use. For example:: # punk is a pointer to an IUnknown interface pci = punk.QueryInterface(IProvideClassInfo) This call will either succeed and return a ``POINTER(IProvideClassInfo)`` instance, or it will raise a ``comtypes.COMError`` if the interface is not supported. Assuming the call succeeded, you can get the type information of the object by calling:: ti = pci.GetClassInfo() Unless the call fails, it will return a ``POINTER(ITypeInfo)`` instance. Implementing COM interfaces --------------------------- While the ``IUnknown`` metaclass creates Python methods that you can call in client code directly, you have to write code yourself if you want to **implement** a COM interface. One important thing to keep in mind is that each COM method implementation with ``comtypes`` receives an additional special parameter per convention named ``this``, just after the ``self`` standard parameter. If you want to implement the ``IProvideClassInfo`` interface described above in a Python class you have to write an implementation of the ``GetClassInfo`` method:: from comtypes import COMObject from comtypes.persist import IProvideClassInfo class MyCOMObject(COMObject): _com_interfaces_ = [ .... IProvideClassInfo] Skipping some very important details that are out of context here, the interfaces that your COM object implements must be listed in the ``_com_interfaces_`` class variable. Then, of course, you should implement the methods of all the interfaces by writing a Python method for each of them. **Note**: The ``COMObject`` metaclass provides a default for methods that are **not** implemented in Python. This default method returns the standard COM error code ``E_NOTIMPL`` when it is called. To implement the COM method named ``MethodName`` for the interface ``ISomeInterface`` you write a Python method either named ``ISomeInterface_MethodName`` or simply ``MethodName``. This method must accept the following arguments: 1. the standard Python ``self`` parameter. 2. a special ``this`` parameter, that you can usually ignore. 3. All the parameters that are listed in the interface description. The latter parameters will be instances of types specified in the ``_methods_`` description. So, to implement the ``GetClassInfo`` method of the ``IProvideClassInfo`` interface, one could write this code:: from comtypes import COMObject from comtypes.persist import IProvideClassInfo class MyCOMObject(COMObject): _com_interfaces_ = [ .... IProvideClassInfo] def IProvideClassInfo_GetClassInfo(self, this, ppTI): # this method could also be named 'GetClassInfo'. ..... The ``ppTI`` parameter in this case is an instance of ``POINTER(POINTER(ITypeInfo))`` which you have to fill out. So, to write a method that actually returns a useful type info pointer for the object, you have to fill the contents of the ``ppTI`` pointer like this:: def IProvideClassInfo_GetClassInfo(self, this, ppTI): from comtypes.hresult import E_POINTER, S_OK # First, check for NULL pointer and return error if not ppTI: return E_POINTER ti = create_type_info(...) # get the type info somehow # poke it into the 'out' parameter ppTI[0] = ti # and return success return S_OK ``E_POINTER`` ìs an error code that you should return when you received an unexpected NULL pointer, ``S_OK`` is the usual success code for COM methods returning a ``HRESULT``. For details about the semantics that you have to implement for a COM interface method consult the MSDN documentation. Case sensitivity ---------------- In principle, COM is a case insensitive technology (probably because of Visual Basic). Type libraries generated from IDL files, however, do *not* always even preserve the case of identifiers; see for example http://support.microsoft.com/kb/220137. Python (and C/C++) are case sensitive languages, so ``comtypes`` is also case sensitive. This means that you have to call ``obj.QueryInterface(...)``, it will not work to write ``obj.queryinterface(...)``. To work around the problems that you get when the case of identifiers in the type library (and in the generated Python module for this library) is not the same as in the IDL file, ``comtypes`` allows to have case insensitive attribute access for methods and properties of COM interfaces. This behaviour is enabled by setting the ``_case_insensitive_`` attribute of a Python COM interface to ``True``. In case of derived COM interfaces, case sensitivity is enabled or disabled separately for each interface. The code generated by the ``GetModule`` function sets this attribute to ``True``. Case insensitive access has a small performance penalty, if you want to avoid this, you should edit the generated code and set ``_case_insensitive_`` to False. More about the metaclass ------------------------ The Python class ``IUnknown``, which is the base interface of *all* COM interfaces, uses a metaclass that automatically creates Python methods and properties for the COM methods described in the ``_methods_`` list. For a COM method described by a ``STDMETHOD`` only the types of the arguments and the return type of the method is known. In this case only trivial code is generated that checks the type of the arguments and returns whatever the COM method returns. For a COM method described by ``COMMETHOD``, much more information is available: the argument names, the direction of data transfer for each argument ["in"], ["out"], or ["in", "out"], and whether this method is a getter or setter of a property. In this case, code is generated that instantiates containers for "out" parameters inside the method call, passes and "in" and "out" parameters to the actual COM method of the object, retrives "out" parameters from their container(s) and returns them as the result. If the method has exactly one "out" parameter, this is returned. If the method has two or more "out" parameters, a tuple of their values is returned. **Note**: the native return value of the method, usually a ``HRESULT``, is *not* returned in the presence of "out" parameters. For the ``IProvideClassInfo`` and ``IProvideClassInfo`` COM interfaces mentioned above, the metaclass creates methods with these signatures automatically (``__call_com_method()`` is the ``ctypes`` code that calls the actual method slot of the COM object):: class IProvideClassInfo(IUnknown): ... # code for this method generated by the IUnknown metaclass at # runtime #def GetClassInfo(self): # param = POINTER(ITypeInfo)() # __call_com_method(byref(param)) # return param[0] class IProvideClassInfo2(IProvideClassInfo): ... # code for this method generated by the IUnknown metaclass at # runtime #def GetGUID(self, dwGuidKind): # param = GUID() # __call_com_method(dwGuidKind, byref(param)) # return param According to MSDN, the ``IProvideClassInfo2::GetGUID`` method *"returns a GUID corresponding to the specified dwGuidKind"*. However, currently only a single valid value for *dwGuidKind* is defined: ``GUIDKIND_DEFAULT_SOURCE_DISP_IID == 1`` which specifies the guid for the default outgoing interface. So, it would probably make sense to implement the GetGUID method with a default value of 1 for the *dwGuidKind* parameter. This can be done by manually implementing a ``GetGUID`` method for the ``IProvideClassInfo2`` interface class:: class IProvideClassInfo2(IProvideClassInfo): ... def GetGUID(self, dwGuidKind=1): return self._GetGUID(dwGuidKind) When the metaclass finds that the ``GetGUID`` method **already has** an implementation, it will not overwrite it. Instead, it creates an interface method with the name ``_GetGUID`` that you can use to get the raw functionality.