Quantcast
Channel: Planet Object Pascal
Viewing all articles
Browse latest Browse all 1725

The Wiert Corner - irregular stream of stuff: jpluimers

$
0
0

I long time ago, I asked about Why doesn’t WINWORD.EXE quit after Closing the document from Delphi?.

It turns out that question is a lot harder in .NET than it is in Delphi. I already had a gut feeling of this when at clients I saw many more .NET applications leaking WINWORD.EXE stray processes than Delphi applications, even though both kinds were calling Quit on the Word application object.

Delphi has a deterministic way of coping with interfaces (hence you can do a One-liner RAII in Delphi, or make a memento): Interface references are released at the end of their scope.

.NET has non-deterministic finalization of the Common Language Runtime (CLR) and has Runtime Callable Wrappers (RCWs) around your COM references which are sometimes created “on the fly”.

The combination of non-deterministic finalization and RCWs can be very confusing, so lets start with the parts first.

Non-deterministic finalization

One of the biggest traps in C# (see Ten Traps in C# for C++ Programmers) is non-deterministic finalization is. It is explained very well by Non-Deterministic Destructors – What Are They?

There are quite a few non-deterministic parts in the whole finalization proces.

  1. It starts the point at which an object becomes eligible for finalization: you don’t know exactly when that will happen. The deterministic part is that an object has no root references. But the point in time that is determined is unknown. That point in time depends on when the garbage collector runs. You don’t know when the garbage collector starts to run: the garbage collector runs you. As the garbage collector runs in a separate thread, there are the basic multi-threading uncertainties. In addition, the garbage collecting thread however does not run continuously: so another uncertainty is when it will start to run. It will start when memory pressure gets high enough. But there are other factors like LatencyMode that the CLR takes into consideration. See the “further reading” for a few links on this.
  2. By now you should understand that garbage collection and final disposal are separated. When an object is eligible for finalization, the second non-deterministic part kicks in: during the garbage collection phase, all objects that are not reachable any more will have their finalizer called at some point in time. This is done in yet another thread: the finalization thread which finalizes objects from the finalization queue (which you should think of as a list, not a queue). In addition, you do not know in which order they are called. Since the execution order of fianlizers is unknown, any references between unreachable objects (private fields from one object to another object) are a no-go area in your finalizer code.

Mike Rosenblum wrote a great answer about finalizers on .net – Release Excel Object In My Destructor?

It is not wise to call the Garbage Collector yourself, as it manages 3 generations and does a balancing act to keep objects that are frequently used into the lowest two generations.

This is contrary to the popular belief of many posts. Just don’t follow that popular belief (:

One way to make this deterministic is to actively use the Dispose pattern. The COM support in .NET uses those. When implementing them yourself, you usually don’t have to write finalizers, especially since the introduction of SafeHandle in .NET 2.0 (they and the COM support in .NET implement the finalizers for you).

RCWs: the COM references are embedded in Runtime Callable Wrappers

The .NET COM support wraps RCWs around COM references: they make the transition from the managed .NET world into the unmanaged COM world. RCWs are automatically created when you access the COM objects described in an imported type library. When the garbage collector determines the RCW is not referenced any more, the RCW finalizer is called, decrementing the COM reference count. When all COM references to an object have been released, the COM object is removed from memory.

The trick is in this little piece of the Runtime Callable Wrapper documentation:

Each RCW maintains a cache of interface pointers on the COM object it wraps and releases its reference on the COM object when the RCW is no longer needed. The runtime performs garbage collection on the RCW.

This is where RCW and non-deterministic finalization come together.

As long as the CLR has not garbage collected the RCW (remember this event is non-deterministic!), it keeps a COM reference. As long as the COM references to a COM object exist, that COM object is being kept alive. Which means that WINWORD.EXE can be kept alive even though you think you are not using it any more. Arthur has written a nice SO answer about when COM references get incremented: Every time the COM object is passed from COM environment to .NET.

Combining RCW and memory management: make it as deterministic as possible

To make the management more deterministic you have to do these things:

In addition, read the Avoid the PIA events by Amit Mittal.

Finally something I need to research further: NetOffice – MS Office in .NET was mentioned by BTownTKD. It is supposed to be like PIA, but better, and more deterministic. And it is maintained.

References for further reading

COM inteoperability:

Garbage collection and memory management:

–jeroen

via:  Why doesn’t WINWORD.EXE quit after Closing the document from Delphi? – Stack Overflow.


Filed under: .NET, Delphi, Development, Software Development

Viewing all articles
Browse latest Browse all 1725

Trending Articles