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
CoCreateInstancerefcount = 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