Colour Skin
Phantom
Matrix
Hellfire
Abyss
Reactor
Caveman
Win 95
Aero
COMplexity
COMplexity — Windows COM

COM Object in Memory — click a vptr row to light up its vtable

Object Layout (CShape @ 0x0020A000)
+0x00 vptr → 0x140001000 IShape vtable ptr
+0x08 vptr → 0x140001028 IDrawable vtable ptr
+0x10 m_color: DWORD e.g. 0x00FF0000
+0x14 m_x: float e.g. 100.0f
+0x18 m_y: float e.g. 200.0f
+0x1C m_refCount: LONG 1 initially
IShape vtable @ 0x140001000
QueryInterface 0x14000A1B0
AddRef 0x14000A220
Release 0x14000A260
GetArea 0x14000A300
GetPerimeter 0x14000A350
IDrawable vtable @ 0x140001028
QueryInterface 0x14000A1B0
AddRef 0x14000A220
Release 0x14000A260
Draw 0x14000A400
SetColour 0x14000A450
Click a vptr row in the object layout to see which vtable it points to.
Key Insights
  • Each interface a COM object implements adds one vptr to the object’s memory layout.
  • The vptr is the very first field for each interface — the pointer you pass around IS a pointer to that vptr slot.
  • IUnknown methods (QueryInterface, AddRef, Release) are always the first three entries in every vtable.
  • Both vtables here share the same QI/AddRef/Release implementation addresses — one C++ class, two interfaces.
  • Casting between interface pointers changes which vptr the callee dereferences, not the underlying object.

Reference Count Simulator

1
Object alive
Operation Log
CoCreateInstance — refcount = 1, IShape* returned
Rules
  • AddRef() must be called whenever an interface pointer is copied to a new owner.
  • Release() must be called when an owner no longer needs the pointer — set it to NULL immediately after.
  • QueryInterface() internally calls AddRef() on the returned pointer — so the caller must Release() it.
  • The refcount is advisory for debugging only; never branch on its return value for lifetime decisions.
  • When refcount reaches zero, the object must destroy itself (typically delete this).
  • COM smart pointers (ATL CComPtr, WRL ComPtr) manage AddRef/Release automatically.

Object Lifecycle Step-Through

COM Init
No
Object Exists
No
IShape*
NULL
IDrawable*
NULL
RefCount
0
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
CoCreateInstance(CLSID_Shape, NULL, CLSCTX_INPROC_SERVER, IID_IShape, &pShape);
pShape->QueryInterface(IID_IDrawable, &pDraw);
pShape->GetArea(); pDraw->Draw();
pDraw->Release(); pDraw = NULL;
pShape->Release(); pShape = NULL;
CoUninitialize();
Press Next to step through the COM object lifecycle.

HRESULT Decoder

Bit Layout
Severity (bit 31)
Reserved (bits 30–28)
Facility (bits 27–16)
Code (bits 15–0)
Severity
—
Facility
—
Code
—
Full Hex
—

Apartment Model Visualiser

Object resides in
STA
Thread 1
Direct
↔
vtable call
Caller lives in
STA
Thread 1
Select a scenario above to see how COM routes the call between apartments.
Apartment Rules
  • STA (Single-Threaded Apartment): one thread, one object. All calls serialised through the message loop.
  • MTA (Multi-Threaded Apartment): any MTA thread can call any MTA object directly — but the object must be thread-safe.
  • Calling across apartment boundaries always requires a proxy/stub pair and marshaling.
  • STA threads must pump their message loop, or cross-apartment calls will deadlock.
  • CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) creates an STA; COINIT_MULTITHREADED joins the MTA.
  • Interface pointers cannot be passed raw across apartment boundaries — use CoMarshalInterfaceInStream / IGlobalInterfaceTable.

COM Quiz

Question 1 of 15