Summary
NetOffice currently releases RCWs with Marshal.ReleaseComObject() from its wrapper lifetime layer. This is only safe if NetOffice exclusively owns every RCW it wraps. Public constructors and UnderlyingObject make it easy to share the same RCW with raw interop code, dynamic code, or another wrapper stack.
Background
The CLR creates and maintains Runtime Callable Wrappers (RCWs) for COM objects. The runtime keeps one RCW per COM object per process/apartment context, and Marshal.ReleaseComObject() decrements the RCW reference count. Microsoft documents that improper use of ReleaseComObject() can break other managed users of the same RCW and can cause hard-to-debug failures if release happens during active calls.
NetOffice adds COMProxyShare as a NetOffice-level reference counter over the RCW and releases the proxy at count zero.
Relevant code paths:
Source/NetOffice/COMProxyShare.cs: MarshalReleaseComObject()
Source/NetOffice/COMObject.cs: constructors accepting object comProxy
Source/NetOffice/COMDynamicObject.cs: constructors accepting object comProxy
Source/NetOffice/Interfaces/ICOMObjectProxy.cs: UnderlyingObject
Use Case
Applications often mix Office automation libraries, raw COM interop, and helper utilities. A raw RCW may be passed into NetOffice for convenience while the original caller still keeps and uses that RCW.
Example shape:
object rawWorkbook = excelInterop.ActiveWorkbook;
var noWorkbook = new NetOffice.ExcelApi.Workbook(parent, rawWorkbook);
noWorkbook.Dispose();
// rawWorkbook may now be disconnected although this code did not intend
// to transfer exclusive ownership to NetOffice.
Problem
NetOffice cannot know whether an incoming RCW is exclusively owned by NetOffice. Releasing the RCW from NetOffice can invalidate other managed references that still represent the same COM object. This is a mismatch between NetOffice's ownership layer and the CLR RCW architecture.
Suggested Fix
- Document explicit ownership semantics for all APIs that accept raw COM proxies.
- Consider separate "borrowed RCW" wrapping mode that does not call
Marshal.ReleaseComObject().
- Make ownership transfer explicit in constructor/factory names or options.
- Add tests or samples showing safe and unsafe interop sharing patterns.
Summary
NetOffice currently releases RCWs with
Marshal.ReleaseComObject()from its wrapper lifetime layer. This is only safe if NetOffice exclusively owns every RCW it wraps. Public constructors andUnderlyingObjectmake it easy to share the same RCW with raw interop code, dynamic code, or another wrapper stack.Background
The CLR creates and maintains Runtime Callable Wrappers (RCWs) for COM objects. The runtime keeps one RCW per COM object per process/apartment context, and
Marshal.ReleaseComObject()decrements the RCW reference count. Microsoft documents that improper use ofReleaseComObject()can break other managed users of the same RCW and can cause hard-to-debug failures if release happens during active calls.NetOffice adds
COMProxyShareas a NetOffice-level reference counter over the RCW and releases the proxy at count zero.Relevant code paths:
Source/NetOffice/COMProxyShare.cs:MarshalReleaseComObject()Source/NetOffice/COMObject.cs: constructors acceptingobject comProxySource/NetOffice/COMDynamicObject.cs: constructors acceptingobject comProxySource/NetOffice/Interfaces/ICOMObjectProxy.cs:UnderlyingObjectUse Case
Applications often mix Office automation libraries, raw COM interop, and helper utilities. A raw RCW may be passed into NetOffice for convenience while the original caller still keeps and uses that RCW.
Example shape:
Problem
NetOffice cannot know whether an incoming RCW is exclusively owned by NetOffice. Releasing the RCW from NetOffice can invalidate other managed references that still represent the same COM object. This is a mismatch between NetOffice's ownership layer and the CLR RCW architecture.
Suggested Fix
Marshal.ReleaseComObject().