A Python Path Through Windows COM
Dropbox’s Python code runs in unusual places. One of the least-known corners is comtypes, a module built on ctypes that talks to low-level Windows APIs using the Component Object Model (COM). The desktop client’s camera upload feature, for example, leans on comtypes to work with Windows Autoplay. COM itself dates back to 1993, yet many Windows APIs still depend on it—meaning a standards body from two decades ago must serve Windows XP alongside the then-unreleased Windows 8.
What COM Actually Is
COM is both a specification and a Windows service. The spec defines how software components interact without knowing each other’s implementations, regardless of language, process boundaries, or even machine boundaries. COM’s interface definitions are compiled into a language-neutral binary format via the Microsoft Interface Definition Language (MIDL), producing a type library that lives inside a DLL or EXE. At runtime, components query each other for supported interfaces—much like two travelers asking, "Do you speak IHardwareEventHandler version 2? No? Well, parlez-vous version 1?" COM also handles reference counting, inter-process marshaling, and thread management, saving developers from manually cleaning up objects across process boundaries or serializing arguments for cross-component calls.
That convenience comes wrapped in genuine complexity. Writing a COM client—code that consumes a component—is tricky; implementing a COM server that others can use is far worse. COM demands incantations, GUIDs for every class and interface (which are descriptive in neither form nor function), and a slew of configuration decisions. Threads must run in Single-Threaded Apartments or Multithreaded Apartments; the ThreadingModel must be set to Both or Free. Each choice requires deep COM knowledge, and most explanations sprawl across pages of charts and code. Even an experienced developer is usually an advanced novice at best.
Comtypes as the Witch Doctor
Writing pure Python that speaks COM would mean generating and parsing binary type libraries, handling the registry choreography, tracking reference counts, and reproducing all that syntactic boilerplate. Comtypes abstracts nearly all of it. For simple cases, point comtypes at the appropriate DLL or EXE and write relatively plain code:
device_obj = CreateObject("PortableDevice.PortableDevice", IPortableDevice)
contents = device_obj.Content()
for item in contents:
print item
The Python object here, deviceobj, wraps a real COM object that lives elsewhere on the system—in this example, it represents an attachable camera. Under the hood, comtypes performs the necessary COM creation, interface querying, and conversion of ctypes primitives into Python wrappers. Yet the abstraction can leak when you need something intricate—then you’re left wondering what rituals were performed, with the demon potentially showing up at your door.
Automatic Code Generation via GetModule
The real magic starts in comtypes.client. The GetModule helper accepts a binary like a .tlb or .exe, extracts the type data, and generates Python source specifying every interface, method, and struct needed for that COM object. This matters because Windows APIs often drag you into structs-within-structs—a FORMATETC struct, for drag-and-drop, begins with two mystery types followed by three 32-bit ints; digging reveals an enum and another struct with its own dependencies. Python can’t import hundreds of Windows headers, so someone must break everything down to primitive types. GetModule does that automatically, but the generated code hides the wrapper classes that actually proxy to the underlying COM objects. So it’s worth a peek underneath.
Inside the IUnknown metaclass
At the root of comtypes is the IUnknown class, the base for every COM interface comtypes can talk to. Declaring a usable interface—say, IPortableDevice—means subclassing IUnknown and describing each method in a _methods_ tuple: the function name, argument types, argument names, and return type.
// QueryInterface returns a pointer to the interface you are querying for,
// or an error if the object does not implement it
int QueryInterface(InterfaceID refiid, void** ppObjectOut);
// These methods are used for reference counting
int AddRef();
int Release();
The real machinery lives in the metaclass, _cominterface_meta. A metaclass, for anyone who hasn't needed one before, is the thing that constructs classes; here it examines those _methods_ declarations and generates ordinary Python methods on the interface class. Each generated wrapper method validates argument count and types before proxying the call into the live COM object, converting return values into Python types and COM error codes into Python exceptions. That layer of type checking is essential: it bridges untyped Python with statically typed compiled C++.
class IPortableDevice(IUnknown):
_iid_ = GUID('{625e2df8-6392-4cf0-9ad1-3cfa5f17775c}')
_methods_ = [
COMMETHOD([], HRESULT, 'Open',
( ['in'], LPWSTR, 'pszPnpDeviceID' ),
( ['in'], POINTER(IPortableDeviceValues), 'pClientInfo')),
COMMETHOD([], HRESULT, 'Content',
( ['out'], POINTER(POINTER(IPortableDeviceContent)), 'ppContent'))
]
The wrapper layer occasionally misbehaves in ways that are hard to diagnose. One such bug stalled development for two days. The symptom was an intermittent crash, with no exception, after reading a batch of images from a camera. The culprit was reference-counting semantics inside comtypes.
COM objects are born with a single reference held by the creator, and the consumer is expected to call Release exactly once when finished. If a consumer copies a reference, it must call AddRef to keep the object alive. The comtypes object model mirrors this: constructing a comtypes object does not call AddRef, but destroying it does call Release. That asymmetry is fine until comtypes creates—and destroys—objects the programmer never explicitly asks for.
Consider indexing into a COM array:
# The following is equivalent to the C code
# IDeviceItem* idevice_item_array = IDeviceItem[10];
# device->GetItems(10, &idevice_item_array);
idevice_item_array = (pointer(IDeviceItem) * 10)()
device_obj.GetItems(10, idevice_item_array)
device_item = idevice_item_array[0]
Intuitively, device_item should be a pointer to an IDeviceItem, and reaching the object itself would require dereferencing that pointer:
# In ctypes, pointer.contents refers to the target of the pointer
device_item = idevice_item_array[0].contents
device_item.do_something()
But comtypes, trying to be friendly, silently converts the array element from pointer(IDeviceItem) to IDeviceItem, which means the index operation constructs a brand-new comtypes object:
# idevice_item_array[0] is already of type IDeviceItem??
idevice_item_array[0].do_something()
Constructing that temporary object is harmless. The problem is its destruction: every one of those synthesized IDeviceItem objects triggers a Release call when garbage-collected. Loop over a hundred array elements:
for i in range(100):
print idevice_item_array[0]
Each iteration builds and then destroys an IDeviceItem, sending a hundred Release calls at a COM object that only ever had one reference. The first release is legitimate; subsequent releases hit an object the COM runtime considers already deleted. When the garbage collector reaches those stale items, the process crashes.
The workaround is to keep a single Python reference alive instead of repeated indexing. Storing the array element once, then reusing that object for the loop body, yields exactly one Release when the Python variable goes out of scope:
device_item = idevice_item_array[0]
for i in xrange(100):
print device_item
This incident happened early in the author's time with the Windows camera uploads project—during which they learned metaclasses before fully mastering list slicing. A parallel struggle on the Mac side, where an OS X library tried to run Dropbox code as PowerPC assembly on x86 hardware, at least had the virtue of perspective. After two days tracing bad reference counts and incorrect vtable pointers, comtypes still came out ahead: writing bespoke Python bindings over COM by hand would have cost considerably more effort.
Since then, those hard-won lessons have paid off directly. When camera uploads required another COM component a short time later, building a working client was straightforward—a comfortable outcome for an API layer that rarely grants comfort without a fight.



