Fix CppunitTest_sc_subsequent_filters_test2 on non-default DPI
[LibreOffice.git] / vcl / README.lifecycle.md
blob8b0e5de2d0fd0b5013f90e949beed08a3630f233
1 # Understanding Transitional VCL Lifecycle
3 ## How it used to look
5 All VCL classes were explicitly lifecycle managed; so you would do:
7 ```
8 Dialog aDialog(...);   // old - on stack allocation
9 aDialog.Execute(...);
10 ```
12 or:
14 ```
15 Dialog *pDialog = new Dialog(...);  // old - manual heap allocation
16 pDialog->Execute(...);
17 delete pDialog;
18 ```
20 or:
22 ```
23 std::shared_ptr<Dialog> xDialog(new pDialog()); // old
24 xDialog->Execute(...);
25 // depending who shared the ptr this would be freed sometime
26 ```
28 In several cases this lead to rather unpleasant code, when
29 various `shared_ptr` wrappers were used, the lifecycle was far less than
30 obvious. Where controls were wrapped by other ref-counted classes -
31 such as UNO interfaces, which were also used by native Window
32 pointers, the lifecycle became extremely opaque. In addition VCL had
33 significant issues with re-enterancy and event emission - adding
34 various means such as DogTags to try to detect destruction of a window
35 between calls:
37 ```
38 ImplDelData aDogTag( this );    // 'orrible old code
39 Show( true, ShowFlags::NoActivate );
40 if( !aDogTag.IsDead() )         // did 'this' go invalid yet ?
41     Update();
42 ```
44 Unfortunately use of such protection is/was ad-hoc, and far
45 from uniform, despite the prevalence of such potential problems.
47 When a lifecycle problem was hit, typically it would take the
48 form of accessing memory that had been freed, and contained garbage due
49 to lingering pointers to freed objects.
52 ## Where we are now
54 To fix this situation we now have a `VclPtr` - which is a smart
55 reference-counting pointer (`include/vcl/vclptr.hxx`) which is
56 designed to look and behave -very- much like a normal pointer
57 to reduce code-thrash. `VclPtr` is used to wrap all `OutputDevice`
58 derived classes thus:
60 ```
61 VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE );
62 ...
63 pDialog.disposeAndClear();
64 ```
66 However - while the `VclPtr` reference count controls the
67 lifecycle of the Dialog object, it is necessary to be able to
68 break reference count cycles. These are extremely common in
69 widget hierarchies as each widget holds (smart) pointers to
70 its parents and also its children.
72 Thus - all previous `delete` calls are replaced with `dispose`
73 method calls:
75 ## What is dispose ?
77 Dispose is defined to be a method that releases all references
78 that an object holds - thus allowing their underlying
79 resources to be released. However - in this specific case it
80 also releases all backing graphical resources. In practical
81 terms, all destructor functionality has been moved into
82 `dispose` methods, in order to provide a minimal initial
83 behavioral change.
85 As such a `VclPtr` can have three states:
87 ```
88 VclPtr<PushButton> pButton;
89 ...
90 assert (pButton == nullptr || !pButton);    // null
91 assert (pButton && !pButton->isDisposed()); // alive
92 assert (pButton &&  pButton->isDisposed()); // disposed
93 ```
95 ## `ScopedVclPtr` - making disposes easier
97 While replacing existing code with new, it can be a bit
98 tiresome to have to manually add `disposeAndClear()`
99 calls to `VclPtr<>` instances.
101 Luckily it is easy to avoid that with a `ScopedVclPtr` which
102 does this for you when it goes out of scope.
104 ## One extra gotcha - an initial reference-count of 1
106 In the normal world of love and sanity, eg. creating UNO
107 objects, the objects start with a ref-count of zero. Thus
108 the first reference is always taken after construction by
109 the surrounding smart pointer.
111 Unfortunately, the existing VCL code is somewhat tortured,
112 and does a lot of reference and de-reference action on the
113 class -during- construction. This forces us to construct with
114 a reference of 1 - and to hand that into the initial smart
115 pointer with a `SAL_NO_ACQUIRE`.
117 To make this easier, we have `Instance` template wrappers
118 that make this apparently easier, by constructing the
119 pointer for you.
121 ### How does my familiar code change ?
123 Lets tweak the exemplary code above to fit the new model:
126 -       Dialog aDialog(... dialog params ... );
127 -       aDialog.Execute(...);
128 +       ScopedVclPtrInstance<Dialog> pDialog(... dialog params ... );
129 +       pDialog->Execute(...); // VclPtr behaves much like a pointer
135 -       Dialog *pDialog = new Dialog(... dialog params ...);
136 +       VclPtrInstance<Dialog> pDialog(... dialog params ...);
137         pDialog->Execute(...);
138 -       delete pDialog;
139 +       pDialog.disposeAndClear(); // done manually - replaces a delete
145 -       std::shared_ptr<Dialog> xDialog(new Dialog(...));
146 +       ScopedVclPtrInstance<Dialog> xDialog(...);
147         xDialog->Execute(...);
148 +       // depending how shared_ptr was shared perhaps
149 +       // someone else gets a VclPtr to xDialog
155 -       VirtualDevice aDev;
156 +       ScopedVclPtrInstance<VirtualDevice> pDev;
159 Other things that are changed are these:
162 -       pButton = new PushButton(NULL);
163 +       pButton = VclPtr<PushButton>::Create(nullptr);
165 -       vcl::Window *pWindow = new PushButton(NULL);
166 +       VclPtr<vcl::Window> pWindow;
167 +       pWindow.reset(VclPtr<PushButton>::Create(nullptr));
170 ### Why are these `disposeOnce` calls in destructors?
172 This is an interim measure while we are migrating, such that
173 it is possible to delete an object conventionally and ensure
174 that its dispose method gets called. In the 'end' we would
175 instead assert that a Window has been disposed in its
176 destructor, and elide these calls.
178 As the object's vtable is altered as we go down the
179 destruction process, and we want to call the correct dispose
180 methods we need this `disposeOnce();` call for the interim in
181 every destructor. This is enforced by a clang plugin.
183 The plus side of disposeOnce is that the mechanics behind it
184 ensure that a `dispose()` method is only called a single time,
185 simplifying their implementation.
188 ### Who owns & disposes what?
190 Window sub-classes tend to create their widgets in one of two
191 ways and often both.
193 1. Derive from `VclBuilderContainer`. The `VclBuilder` then owns
194    many of the sub-windows, which are fetched by a `get`
195    method into local variables often in constructors eg.
198 VclPtr<PushButton> mpButton;  // in the class
199 , get(mpButton, "buttonName") // in the constructor
200 mpButton.clear();             // in dispose.
203 We only clear, not `disposeAndClear()` in our dispose method
204 for this case, since the `VclBuilder` / Container truly owns
205 this Window, and needs to dispose its hierarchy in the
206 right order - first children then parents.
208 2. Explicitly allocated Windows. These are often created and
209    managed by custom widgets:
212 VclPtr<ComplexWidget> mpComplex;                     // in the class
213 , mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor
214 mpComplex.disposeAndClear();                         // in dispose
217 ie. an owner has to dispose things they explicitly allocate.
219 In order to ensure that the VclBuilderConstructor
220 sub-classes have their Windows disposed at the correct time
221 there is a `disposeBuilder();` method - that should be added
222 -only- to the class immediately deriving from
223 `VclBuilderContainer`'s dispose.
225 ### What remains to be done?
227 * Expand the `VclPtr` pattern to many other less
228   than safe VCL types.
230 * create factory functions for `VclPtr<>` types and privatize
231   their constructors.
233 * Pass `const VclPtr<> &` instead of pointers everywhere
234   + add `explicit` keywords to VclPtr constructors to
235     accelerate compilation etc.
237 * Cleanup common existing methods such that they continue to
238   work post-dispose.
240 * Dispose functions should be audited to:
241   + not leave dangling pointsr
242   + shrink them - some work should incrementally
243     migrate back to destructors.
245 * `VclBuilder`
246   + ideally should keep a reference to pointers assigned
247     in `get()` calls - to avoid needing explicit `clear`
248     code in destructors.
250 ## FAQ / debugging hints
252 ### Compile with dbgutil
254 This is by far the best way to turn on debugging and
255 assertions that help you find problems. In particular
256 there are a few that are really helpful:
259 vcl/source/window/window.cxx (Window::dispose)
260 "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
261           ^^^ class name                 window title ^^^
262 with live children destroyed:  N4sfx27sidebar6TabBarE ()
263 N4sfx27sidebar4DeckE () 10FixedImage ()"
266 You can de-mangle these names if you can't read them thus:
269 $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
270 sfx2::sidebar::SidebarDockingWindow
273 In the above case - it is clear that the children have not been
274 disposed before their parents. As an aside, having a dispose chain
275 separate from destructors allows us to emit real type names for
276 parents here.
278 To fix this, we will need to get the dispose ordering right,
279 occasionally in the conversion we re-ordered destruction, or
280 omitted a `disposeAndClear()` in a `::dispose()` method.
282 - If you see this, check the order of `disposeAndClear()` in
283    the `sfx2::Sidebar::SidebarDockingWindow::dispose()` method
285 - also worth `git grep`ing for `new sfx::sidebar::TabBar` to
286    see where those children were added.
288 ### Check what it used to do
290 While a ton of effort has been put into ensuring that the new
291 lifecycle code is the functional equivalent of the old code,
292 the code was created by humans. If you identify an area where
293 something asserts or crashes here are a few helpful heuristics:
295 * Read the `git log -u -- path/to/file.cxx`
297 ### Is the order of destruction different?
299 In the past many things were destructed (in reverse order of
300 declaration in the class) without explicit code. Some of these
301 may be important to do explicitly at the end of the destructor.
303 eg. having a `Idle` or `Timer` as a member, may now need an
304    explicit `.Stop()` and/or protection from running on a
305    disposed Window in its callback.
307 ### Is it `clear` not `disposeAndClear`?
309 sometimes we get this wrong. If the code previously used to
310 use `delete pFoo;` it should now read `pFoo->disposeAndClear();`.
311 Conversely if it didn't delete it, it should be `clear()` it
312 is by far the best to leave disposing to the `VclBuilder` where
313 possible.
315 In simple cases, if we allocate the widget with `VclPtrInstance`
316 or `VclPtr<Foo>::Create` - then we need to `disposeAndClear` it too.
318 ### Event / focus / notification ordering
320 In the old world, a large amount of work was done in the
321 `~Window` destructor that is now done in `Window::dispose`.
323 Since those Windows were in the process of being destroyed
324 themselves, their vtables were adjusted to only invoke Window
325 methods. In the new world, sub-classed methods such as
326 `PreNotify`, `GetFocus`, `LoseFocus` and others are invoked all down
327 the inheritance chain from children to parent, during dispose.
329 The easiest way to fix these is to just ensure that these
330 cleanup methods, especially LoseFocus, continue to work even
331 on disposed Window sub-class instances.
333 ### It crashes with some invalid memory...
335 Assuming that the invalid memory is a Window sub-class itself,
336 then almost certainly there is some cockup in the
337 reference-counting; eg. if you hit an `OutputDevice::release`
338 assert on `mnRefCount` - then almost certainly you have a
339 Window that has already been destroyed. This can easily
340 happen via this sort of pattern:
343 Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */);
344 // by here the pDlg quite probably points to free'd memory...
347 It is necessary in these cases to ensure that the `*pDlg` is
348 a `VclPtr<Dialog>` instead.
350 ### It crashes with some invalid memory #2...
352 Often a `::dispose` method will free some `pImpl` member, but
353 not `NULL` it; and (cf. above) we can now get various `virtual`
354 methods called post-dispose; so:
356 a) `delete pImpl; pImpl = NULL; // in the destructor`
357 b) `if (pImpl && ...)           // in the subsequently called method`