Skip to content

Commit

Permalink
Mention that it is unnecessary to Release created or copied objects…
Browse files Browse the repository at this point in the history
… in documentation. (enthought#728)
  • Loading branch information
junkmd authored Dec 27, 2024
1 parent 797dc9f commit 2b24220
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 40 deletions.
61 changes: 37 additions & 24 deletions docs/source/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,39 +13,42 @@ Creating and accessing COM objects
``comtypes.client`` exposes three functions that allow to create or
access COM objects.

``CreateObject(progid, clsctx=None, machine=None, interface=None, dynamic=False, pServerInfo=None)``
.. py:function:: CreateObject(progid, clsctx=None, machine=None, interface=None, dynamic=False, pServerInfo=None)
Create a COM object and return an interface pointer to it.

``progid`` specifies which object to create. It can be a string
*progid* specifies which object to create. It can be a string
like ``"InternetExplorer.Application"`` or
``"{2F7860A2-1473-4D75-827D-6C4E27600CAC}"``, a ``comtypes.GUID``
instance, or any object with a ``_clsid_`` attribute that must be
a ``comtypes.GUID`` instance or a GUID string.

``clsctx`` specifies how to create the object, any combination of
*clsctx* specifies how to create the object, any combination of
the ``comtypes.CLSCTX_...`` constants can be used. If nothing is
passed, ``comtypes.CLSCTX_SERVER`` is used.

``machine`` allows to specify that the object should be created on
*machine* allows to specify that the object should be created on
a different machine, it must be a string specifying the computer
name or IP address. DCOM must be enabled for this to work.

``interface`` specifies the interface class that should be
*interface* specifies the interface class that should be
returned, if not specified |comtypes| will determine a useful
interface itself and return a pointer to that.

``dynamic`` specifies that the generated interface should use
*dynamic* specifies that the generated interface should use
dynamic dispatch. This is only available for automation interfaces
and does not generate typelib wrapper.

``pServerInfo`` that allows you to specify more information about
the remote machine than the ``machine`` parameter. It is a pointer
to a ``COSERVERINFO``. ``machine`` and ``pServerInfo`` may not be
*pServerInfo* that allows you to specify more information about
the remote machine than the *machine* parameter. It is a pointer
to a ``COSERVERINFO``. *machine* and *pServerInfo* may not be
simultaneously supplied. DCOM must be enabled for this to work.

``CoGetObject(displayname, interface=None)``

.. py:function:: CoGetObject(displayname, interface=None)
Create a named COM object and returns an interface pointer to it.
For the interpretation of ``displayname`` consult the Microsoft
For the interpretation of *displayname* consult the Microsoft
documentation for the Windows ``CoGetObject`` function.
``"winmgmts:"``, for example, is the displayname for
`WMI monikers <https://learn.microsoft.com/ja-jp/windows/win32/wmisdk/constructing-a-moniker-string>`_:
Expand All @@ -57,15 +60,18 @@ access COM objects.
``interface`` and ``dynamic`` have the same meaning as in the
``CreateObject`` function.

``GetActiveObject(progid, interface=None)``
Returns a pointer to an already running object. ``progid``

.. py:function:: GetActiveObject(progid, interface=None)
Returns a pointer to an already running object. *progid*
specifies the active object from the OLE registration database.

The ``GetActiveObject`` function succeeds when the COM object is
already running, and has registered itself in the COM running
object table. Not all COM objects do this. The arguments are as
described under ``CreateObject``.


All the three functions mentioned above will create the typelib
wrapper automatically if the object provides type information. If the
type library is not exposed by the object itself, the wrapper can be
Expand Down Expand Up @@ -346,26 +352,29 @@ based on so-called *connection points*.
handlers you should read the :doc:`server` document.


``GetEvents(source, sink, interface=None)``
.. py:function:: GetEvents(source, sink, interface=None)
This functions connects an event sink to the COM object
``source``.
*source*.

Events will call methods on the ``sink`` object; the methods must
Events will call methods on the *sink* object; the methods must
be named ``interfacename_methodname`` or ``methodname``. The
methods will be called with a ``this`` parameter, plus any
parameters that the event has.

``interface`` is the outgoing interface of the ``source`` object;
*interface* is the outgoing interface of the *source* object;
it must be supplied when |comtypes| cannot determine the
outgoing interface of ``source``.
outgoing interface of *source*.

``GetEvents`` returns the advise connection; you should keep the
connection alive as long as you want to receive events. To break
the advise connection simply delete it.

``ShowEvents(source, interface=None)``

.. py:function:: ShowEvents(source, interface=None)
This function contructs an event sink and connects it to the
``source`` object for debugging. The event sink will first print
*source* object for debugging. The event sink will first print
out all event names that are found in the outgoing interface, and
will later print out the events with their arguments as they occur.
``ShowEvents`` returns a connection object which must be kept
Expand All @@ -375,11 +384,13 @@ based on so-called *connection points*.
To actually receive events you may have to call the ``PumpEvents``
function so that COM works correctly.

``PumpEvents(timeout)``

.. py:function:: PumpEvents(timeout)
This functions runs for a certain time in a way that is required
for COM to work correctly. In a single-theaded apartment it runs
a windows message loop, in a multithreaded apparment it simply
waits. The ``timeout`` argument may be a floating point number to
waits. The *timeout* argument may be a floating point number to
indicate a time of less than a second.

Pressing Control-C raises a KeyboardError exception and terminates
Expand Down Expand Up @@ -503,7 +514,8 @@ fortunately |comtypes| includes a code generator that does create
modules containing the Python interface class (and more) automatically
from COM typelibraries.

``GetModule(tlib)``
.. py:function:: GetModule(tlib)
This function generates Python wrappers for a COM typelibrary.
When a COM object exposes its own typeinfo, this function is
called automatically when the object is created.
Expand Down Expand Up @@ -588,7 +600,8 @@ from COM typelibraries.
+ from comtypes.gen.friendlymodule import __wrapper_module__ as mod
c_int_alias = mod.TheName

``gen_dir``
.. py:attribute:: gen_dir
This variable determines the directory where the typelib wrappers
are written to. If it is ``None``, modules are only generated in
memory.
Expand Down
98 changes: 83 additions & 15 deletions docs/source/com_interfaces.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,97 @@ 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:
``IUnknown``.

``_iid_``

The ``IUnknown`` as a Python class
++++++++++++++++++++++++++++++++++

.. py:class:: comtypes.IUnknown
In this package, ``IUnknown`` is defined as a pure Python class,
with all its high-level wrapper methods.

.. py:method:: QueryInterface(interface, iid=None)
This high-level method wraps the low-level `IUnknown::QueryInterface <https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-queryinterface(refiid_void)>`_
foreign function calls. This enables a Pythonic way of usage,
without worrying about pointers or passing by reference.

*interface* is a ``IUnknown`` Python class or one of
subclasses. If the COM object implements the interface,
then it returns a pointer instance to that interface after
incrementing the reference count on it.

*iid* is the optional interface identifier (IID). In most
cases, the *interface* class attribute is used to identify
the interface. However, passing a ``comtypes.GUID`` instance
can be useful in certain low-level processing scenarios.

The return value is not a ``HRESULT`` value but a pointer to
the interface. If the COM object does **not** implement the
interface, a ``ctypes.COMError`` is raised with an ``hresult``
of ``-2147467262`` (``E_NOINTERFACE``, ``'0x80004002'`` in
signed-32bit hex)

.. py:method:: Add()
This wraps the `IUnknown::AddRef <https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-addref>`_.
Increments the reference count for an interface pointer to a
COM object and returns the new reference count.

.. py:method:: Release()
This wraps the `IUnknown::Release <https://learn.microsoft.com/en-us/windows/win32/api/unknwn/nf-unknwn-iunknown-release>`_.
Decrements the reference count for an interface on a COM
object and returns the new reference count.

In other COM technologies, it is necessary to explicitly
release COM pointers that have been created or copied by
calling ``Release``. However, in |comtypes|, explicit release
is not required because ``Release`` is automatically invoked
via ``atexit`` hooks or metaclasses when the Python
interpreter exits or when the Python instance is about to be
destroyed.

In fact, explicitly releasing the pointer can cause issues;
if ``Release`` is called at the aforementioned timing, it may
raise an ``OSError``.

.. sourcecode:: pycon

>>> from comtypes.client import CreateObject, GetModule
>>> GetModule('UIAutomationCore.dll') # doctest: +ELLIPSIS
<module 'comtypes.gen.UIAutomationClient' from ...>
>>> from comtypes.gen.UIAutomationClient import CUIAutomation
>>> iuia = CreateObject(CUIAutomation)
>>> iuia # doctest: +ELLIPSIS
<POINTER(IUIAutomation) ptr=... at ...>
>>> iuia.Release()
0
>>> del iuia # doctest: +ELLIPSIS
Exception ignored in: <function _compointer_base.__del__ at ...>
Traceback (most recent call last):
...
OSError: exception: access violation writing ...

The interface class must define the following class attributes:

.. py:attribute:: _iid_
a ``comtypes.GUID`` instance containing the
*interface identifier* of the interface

``_idlflags_``
.. py:attribute:: _idlflags_
(optional) a sequence containing IDL flags for the interface

``_case_insensitive_``
.. py:attribute:: _case_insensitive_
(optional) If set to ``True``, this interface supports case
insensitive attribute access.

``_methods_``
.. py:attribute:: _methods_
a sequence describing the methods of this interface. COM
methods of the superclass must not be listed, they are
Expand All @@ -61,22 +134,17 @@ attributes:
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:

.. sourcecode:: python

class ISomeInterface(IUnknown):
_iid_ = GUID(...)

ISomeInterface._methods_ = [...,]

.. 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
Expand All @@ -86,7 +154,7 @@ 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=())``
.. py:function:: comtypes.STDMETHOD(restype, methodname, argtypes=())
Calling ``STDMETHOD`` allows to specify the type of the COM method
return value. Usually *restype* is a ``HRESULT``, but other return
Expand All @@ -95,7 +163,7 @@ information.
method expects.


``comtypes.COMMETHOD(idlflags, restype, methodname, *argspec)``
.. py:function:: 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
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
# html_theme_options = {}

# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme_path = []

# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
Expand Down

0 comments on commit 2b24220

Please sign in to comment.