| compatibility | Requires the .NET SDK and the Microsoft.Windows.CsWin32 source generator; Windows COM APIs across .NET 10 and .NET Framework. Cross-assembly extension-block examples require C# 14. |
| description | Guides struct-based COM interop using CsWin32 - AOT-compatible raw pointer calls without [ComImport] or built-in CLR marshalling; managed [ComImport] mirrors are limited to CCW bridges. Consult when working with ComScope, IID.Get, delegate* unmanaged vtables, CoCreateInstance or a class factory, IComIID across .NET 10 and .NET Framework, connection-point Advise/Unadvise ownership, caller-owned CCW references, manually defining COM interfaces absent from Win32 metadata, COM pointer lifetime in fields, cross-assembly CCWs, or mocking struct-based COM in tests. Not for ordinary RCW-based COM consumption with no raw struct pointers or CCW bridge. Paired with the cswin32-interop skill for the general P/Invoke layer and blittable-signature rules. |
| license | MIT |
| metadata | {"applicability":"dotnet","binding":"optional-overlay","github-path":"skills/cswin32-com","github-pinned":"v0.11.0","github-ref":"refs/tags/v0.11.0","github-repo":"https://github.com/JeremyKuhne/agent-skills","github-tree-sha":"16f0e262ed5b4a11f90a91b70cb4b9ab473a3d1a","maturity":"canary","portability":"portable","related":"security-review, il-copy-inspection","requires":"cswin32-interop","risk":"local-write"} |
| name | cswin32-com |
CsWin32 struct-based COM interop
If overlay.md exists beside this file, read it before acting; it contains
repository-specific bindings. This core remains usable without it.
Struct-based COM interop on top of CsWin32: raw delegate* vtable calls and no
built-in CLR marshalling on the native pointer surface, so it is AOT- and
trimming-friendly. A managed [ComImport] mirror is allowed only as a narrow
CCW bridge; runtime calls still use the generated struct. This skill covers the
COM-specific layer; the general P/Invoke layer and the blittable-signature rules
that vtable methods also follow live in the paired cswin32-interop skill.
Helper conventions
Examples use ComScope<T> for a disposable scope that owns one AddRef'd COM
reference and IID.Get<T>() for a support helper that returns a Guid* to
T's IComIID.Guid. CsWin32 generates IComIID, but these two helpers come
from a support library. The overlay names their concrete implementations. If a
repository does not provide them, use an equivalent owned-pointer scope and a
stable IID pointer; keep the same ownership and ABI contracts.
AcquireOwnedCcwPointer<T>() in examples is pseudocode for a repository helper
that returns one AddRef'd, caller-owned interface pointer for a managed object.
Its implementation determines compatibility: classic
Marshal.GetComInterfaceForObject uses built-in COM interop and is not a
NativeAOT path, while a ComWrappers implementation can be AOT-compatible.
Workflow
- In Win32 metadata? If yes, add the interface name to
NativeMethods.txt
and CsWin32 generates it. If not (for example, Setup Configuration or a
private / third-party interface), define a manual struct in its own file - see
manual-structs.md.
- Lifetime:
using ComScope<T> scope = new(); for every transient owned
COM reference, and free every owned COM BSTR out-param (SysFreeString /
FreeBSTR, or a repo-provided scoped BSTR wrapper). A correct
try / finally also releases on early return, but an owned-pointer scope
centralizes initialization, release, and ownership transfer. Never release a
borrowed pointer unless the caller first acquires a reference. See
lifetime.md.
- Activate via a class-factory helper or
CoCreateInstance with
the exact interface IID; prefer IID.Get<T>() when the support helper is
available. See Activation.
- Call via
scope.Pointer->Method(...). Pass ComScope<T> directly where
the API expects T** / void** when the concrete scope provides those
conversions.
- Match the caller's error contract. If the top-level consumer treats COM
failure as a documented "absent" result, helpers may return
default /
false; otherwise call .ThrowOnFailure(). Do not change a shared helper's
contract merely because one caller catches COMException. See the parity
table in
migration-and-testing.md.
- Gate .NET 10-only COM features with
#if NET. ComWrappers-based CCW
support is unavailable on .NET Framework. Generated IComIID works on both
supported runtime families with CsWin32 0.3.287 or later; see
comiid-and-cls.md.
- Compose across packages at the owner boundary. The owner implements
generated partial hooks and publishes shared COM structs; extenders add
behavior with extension blocks or uniquely named CCW providers. See
ccw-composition.md.
Activation
Guid clsid = SomeCoClass.CLSID;
using ComScope<ISomething> instance = new();
PInvoke.CoCreateInstance(
&clsid, null, CLSCTX.CLSCTX_INPROC_SERVER, IID.Get<ISomething>(), instance)
.ThrowOnFailure();
instance.Pointer->DoThing(...);
- Prefer
IID.Get<T>() when available; it returns a Guid* to the type's
IComIID.Guid without a per-call copy. Otherwise pass a pointer to a stable
Guid containing the exact IID. A local Guid is valid for the duration of
the native call; the problem is an incorrect or ad hoc IID, not local storage.
- Initialize
ComScope<T> with new(); it implicitly converts to the T** /
void** output parameter when the concrete support type supplies those
conversions.
- A repo may also ship a class-factory helper (activate via
CoGetClassObject
then CreateInstance), the AOT-friendly path when a CLSID is registered - see
the overlay.
Sibling pages
- lifetime.md -
ComScope<T>, BSTR, owned and borrowed
references, field-lifetime strategies, and ownership transfer out of a helper.
- manual-structs.md - defining an interface not in Win32
metadata:
delegate* unmanaged[Stdcall] vtables, exact slot indices, the
dual-target IComIID member, and strongly-typed handle/token wrappers.
- comiid-and-cls.md -
IComIID across .NET 10 and
.NET Framework (both-family auto-emit versus the older Framework per-struct
polyfill) and CLS compliance.
- migration-and-testing.md - the error-handling
parity table when migrating off
[ComImport], and mocking struct-based COM in
tests via a CCW bridge.
- ccw-composition.md - owner-side
IUnknown vtable
initialization, extending imported COM structs across assemblies, and local
CCW provider types.