Skip to content

Global COM object list keeps RCWs alive until explicit DisposeAllCOMProxies cleanup #481

@jozefizso

Description

@jozefizso

Summary

NetOffice stores wrappers in a global strong object list and removes them only during explicit disposal. If callers forget Dispose() or DisposeAllCOMProxies(), wrappers and their underlying RCWs stay alive indefinitely. There is no finalizer fallback.

Background

Core.AddObjectToList() stores each wrapper in _globalObjectList. Core.DisposeAllCOMProxies() walks that list and disposes wrappers. Individual wrappers call Factory.RemoveObjectFromList() only as part of Dispose().

Relevant code paths:

  • Source/NetOffice/Core.cs: _globalObjectList
  • Source/NetOffice/Core.cs: AddObjectToList()
  • Source/NetOffice/Core.cs: RemoveObjectFromList()
  • Source/NetOffice/Core.cs: DisposeAllCOMProxies()
  • Source/NetOffice/COMObject.cs: constructors and Dispose()
  • Source/NetOffice/COMDynamicObject.cs: constructors and Dispose()

Use Case

A consumer creates temporary Office child objects in loops or event handlers and misses a dispose call on one path. In normal .NET code, losing all managed references would allow the GC to collect the wrapper and eventually let the RCW be released. NetOffice's global strong list prevents that.

Example shape:

void InspectWorkbook(NetOffice.ExcelApi.Application excel)
{
    var workbook = excel.ActiveWorkbook;
    var sheet = workbook.ActiveSheet;
    // missed Dispose() on an exception or early return path
}

Problem

Because the global list keeps strong references, missed dispose calls become permanent lifetime leaks for the Core lifetime. This can keep Office processes alive, retain COM server resources, and make cleanup depend on caller discipline rather than runtime reachability.

Suggested Fix

  • Reconsider whether the global object list should hold strong references to every wrapper.
  • Consider weak references for monitoring plus explicit ownership for deterministic cleanup.
  • Add diagnostics for wrappers that remain undisposed at DisposeAllCOMProxies() or application shutdown.
  • Document that NetOffice requires explicit disposal for every wrapper or root-level cleanup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions