Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory management doc #899

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open

Conversation

manodasanW
Copy link
Member

Documenting what we learned when working on the fix for the memory management issues in C#/WinRT.

Fixes #810

Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


In the former, the object is implemented purely in C# and its lifetime is managed by the .NET
garbage collector. C#/WinRT only comes into play when this C# object is passed across the ABI to a
WinRT function. When this happens, C#/WinRT creates a CCW for it using the .NET 5 ComWrappers API
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/memorymanagement.md Outdated Show resolved Hide resolved
docs/memorymanagement.md Outdated Show resolved Hide resolved
docs/memorymanagement.md Outdated Show resolved Hide resolved
docs/memorymanagement.md Outdated Show resolved Hide resolved
docs/memorymanagement.md Outdated Show resolved Hide resolved
know about all the references to it from another reference tracking system like the .NET
garbage collector. This allows XAML to track scenarios where objects may have circular references
or only have references to it from objects that are pending clean up. Specifically, when a C# wrapper
is created for a XAML runtime tracked object (implements `IReferenceTracker`), the XAML runtime
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs/memorymanagement.md Outdated Show resolved Hide resolved
docs/memorymanagement.md Outdated Show resolved Hide resolved
serves the purpose of documenting how C#/WinRT interacts with all 3 systems to correctly manage the
lifetime of projected WinRT objects.

### COM reference tracking
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be easier to augment this with a series of enumerated steps in the process.

which C#/WinRT does behind the scenes when a C# class extends such a projected type. In COM aggregation,
there is 2 objects in play: the outer object which is the CCW for the C# object and the inner object
which is the WinRT object being extended. Both these objects are made to look like one object known as the

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected line break? Also some line breaks below this when viewing the preview non-markdown version of the doc


The above describes what typically happens for any natively implemented WinRT object that C#/WinRT
projects. There are some differences to this when the object is instead a C# implemented object that is
projected into WinRT via a COM callable wrapper (CCW). This is done via a C# class implementing a set of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe factor out these into 2 separate sections? E.g.

C# class implementing WinRT interface
...
C# class extending an unsealed WinRT type
...


### COM reference tracking

Each WinRT object that we project is based on COM and implements a set of interfaces.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we -> C#/WinRT


Each WinRT object that we project is based on COM and implements a set of interfaces.
As per the COM design, every COM interface implements `IUnknown` which has an `AddRef` and `Release`
function. C#/WinRT calls the `AddRef` function anytime it gets a new reference to a WinRT object
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mention CCWs below. Maybe introduce IObjectReference as the RCW here.

As per the COM design, every COM interface implements `IUnknown` which has an `AddRef` and `Release`
function. C#/WinRT calls the `AddRef` function anytime it gets a new reference to a WinRT object
which C#/WinRT holds onto using an `IObjectReference` instance. It also calls `AddRef` whenever it gives
out a reference to one of these objects across the ABI as an out parameter. C#/WinRT calls the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should introduce this acronym too: Application Binary Interface (ABI)

object alive and that is managed by the .NET runtime and `ComWrappers` implementation.


In the latter scenario, extending an unsealed WinRT type is typically done via COM Aggregation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"latter" here is referring back a bit - may help to provide some structure to the doc (section titles)


In the latter scenario, extending an unsealed WinRT type is typically done via COM Aggregation
which C#/WinRT does behind the scenes when a C# class extends such a projected type. In COM aggregation,
there is 2 objects in play: the outer object which is the CCW for the C# object and the inner object
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are two

composed object. To achieve that, the outer object delegates calls for any of the inner object

interfaces that aren't overridden to the inner object. Any calls for interfaces that are only
implemented on the outer object or is overridden by the outer object or is for the `IUnknown` interface
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or are overridden ..., or are for ....



In the latter scenario, extending an unsealed WinRT type is typically done via COM Aggregation
which C#/WinRT does behind the scenes when a C# class extends such a projected type. In COM aggregation,
Copy link
Member

@Scottj1s Scottj1s Jul 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd include links inline in the body of the text, rather than at the end of the doc, like COM Aggregation here (https://docs.microsoft.com/en-us/windows/win32/com/aggregation)

would be handled by the outer object itself. The last part means that the lifetime and the COM reference
counting of this aggregated object is maintained by the outer object and more specifically its `IUnknown`
implementation on the CCW from ComWrappers. This is where the standard COM reference tracking
convention described earlier starts to differ. As we know for CCWs, there are 2 things which
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spell out any number less than 10 (standard technical writing guideline)

other than facilitating QI calls for them from the native side where `Release` isn't called right after.
The recommendation for tear off interfaces which do want to support such uses on aggregated objects
is that they can continue to be constructed on demand upon the first QI for it, but the interface
should not be cleaned up until the object is cleaned up even if there are no longer any reference
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

references

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we be more crisp about the recommendation - that a tear-off implementor should cache all instances to prevent premature destruction?

objects if one of these interfaces need to be QIed for by the composed object as part of the
projection implementation. This is because a `Release` would happen right after which would trigger
the cleanup of the interface as its lifetime isn't tied to the outer. Given that tear off interfaces
are rare and not typically used by C# consumers, C#/WinRT today doesn't address this
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"not typically used by C# consumers" ? the tear-off implementor has no idea what client code is using it

or only have references to it from objects that are pending clean up. Specifically, when a C# wrapper
is created for a XAML runtime tracked object (implements `IReferenceTracker`), the XAML runtime
needs to be informed of it by a call to `ConnectFromTrackerSource` on `IReferenceTracker`. This is
done by the ComWrappers implementation when an RCW is created. After that, any references to that
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(introduce 'RCW' above)

needs to be informed of it by a call to `ConnectFromTrackerSource` on `IReferenceTracker`. This is
done by the ComWrappers implementation when an RCW is created. After that, any references to that
object that are tracked by the other reference tracking system (.NET garbage collector in this case)
needs to be informed to XAML by a call to `AddRefFromTrackerSource`. This is done by both C#/WinRT and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need

needs to be informed to XAML by a call to `AddRefFromTrackerSource`. This is done by both C#/WinRT and
ComWrappers after any `AddRef` call to increment the COM reference count. Similarly, before the
`Release` call, there would be a `ReleaseFromTrackerSource` to indicate a reference on the object
was released. When the RCW is destructed, there would similarly be a call to
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

destructed -> finalized

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Documentation for C#/WinRT memory management
4 participants