1 ** Understanding transitional VCL lifecycle **
3 ---------- How it used to look ----------
5 All VCL classes were explicitly lifecycle managed; so you would
7 Dialog aDialog(...); // old - on stack allocation
10 Dialog *pDialog = new Dialog(...); // old - manual heap allocation
11 pDialog->Execute(...);
14 std::shared_ptr<Dialog> xDialog(new pDialog()); // old
15 xDialog->Execute(...);
16 // depending who shared the ptr this would be freed sometime
18 In several cases this lead to rather unpleasant code, when
19 various shared_ptr wrappers were used, the lifecycle was far less than
20 obvious. Where controls were wrapped by other ref-counted classes -
21 such as UNO interfaces, which were also used by native Window
22 pointers, the lifecycle became extremely opaque. In addition VCL had
23 significant issues with re-enterancy and event emission - adding
24 various means such as DogTags to try to detect destruction of a window
27 ImplDelData aDogTag( this ); // 'orrible old code
28 Show( true, ShowFlags::NoActivate );
29 if( !aDogTag.IsDead() ) // did 'this' go invalid yet ?
32 Unfortunately use of such protection is/was ad-hoc, and far
33 from uniform, despite the prevalence of such potential problems.
35 When a lifecycle problem was hit, typically it would take the
36 form of accessing memory that had been freed, and contained garbage due
37 to lingering pointers to freed objects.
40 ---------- Where we are now: ----------
42 To fix this situation we now have a VclPtr - which is a smart
43 reference-counting pointer (include/vcl/vclptr.hxx) which is
44 designed to look and behave -very- much like a normal pointer
45 to reduce code-thrash. VclPtr is used to wrap all OutputDevice
48 VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE );
50 pDialog.disposeAndClear();
52 However - while the VclPtr reference count controls the
53 lifecycle of the Dialog object, it is necessary to be able to
54 break reference count cycles. These are extremely common in
55 widget hierarchies as each widget holds (smart) pointers to
56 its parents and also its children.
58 Thus - all previous 'delete' calls are replaced with 'dispose'
63 Dispose is defined to be a method that releases all references
64 that an object holds - thus allowing their underlying
65 resources to be released. However - in this specific case it
66 also releases all backing graphical resources. In practical
67 terms, all destructor functionality has been moved into
68 'dispose' methods, in order to provide a minimal initial
71 As such a VclPtr can have three states:
73 VclPtr<PushButton> pButton;
75 assert (pButton == nullptr || !pButton); // null
76 assert (pButton && !pButton->isDisposed()); // alive
77 assert (pButton && pButton->isDisposed()); // disposed
79 ** ScopedVclPtr - making disposes easier
81 While replacing existing code with new, it can be a bit
82 tiresome to have to manually add 'disposeAndClear()'
83 calls to VclPtr<> instances.
85 Luckily it is easy to avoid that with a ScopedVclPtr which
86 does this for you when it goes out of scope.
88 ** One extra gotcha - an initial reference-count of 1
90 In the normal world of love and sanity, eg. creating UNO
91 objects, the objects start with a ref-count of zero. Thus
92 the first reference is always taken after construction by
93 the surrounding smart pointer.
95 Unfortunately, the existing VCL code is somewhat tortured,
96 and does a lot of reference and de-reference action on the
97 class -during- construction. This forces us to construct with
98 a reference of 1 - and to hand that into the initial smart
99 pointer with a SAL_NO_ACQUIRE.
101 To make this easier, we have 'Instance' template wrappers
102 that make this apparently easier, by constructing the
105 ** How does my familiar code change ?
107 Lets tweak the exemplary code above to fit the new model:
109 - Dialog aDialog(... dialog params ... );
110 - aDialog.Execute(...);
111 + ScopedVclPtrInstance<Dialog> pDialog(... dialog params ... );
112 + pDialog->Execute(...); // VclPtr behaves much like a pointer
115 - Dialog *pDialog = new Dialog(... dialog params ...);
116 + VclPtrInstance<Dialog> pDialog(... dialog params ...);
117 pDialog->Execute(...);
119 + pDialog.disposeAndClear(); // done manually - replaces a delete
121 - std::shared_ptr<Dialog> xDialog(new Dialog(...));
122 + ScopedVclPtrInstance<Dialog> xDialog(...);
123 xDialog->Execute(...);
124 + // depending how shared_ptr was shared perhaps
125 + // someone else gets a VclPtr to xDialog
127 - VirtualDevice aDev;
128 + ScopedVclPtrInstance<VirtualDevice> pDev;
130 Other things that are changed are these:
132 - pButton = new PushButton(NULL);
133 + pButton = VclPtr<PushButton>::Create(nullptr);
135 - vcl::Window *pWindow = new PushButton(NULL);
136 + VclPtr<vcl::Window> pWindow;
137 + pWindow.reset(VclPtr<PushButton>::Create(nullptr));
139 ** Why are these 'disposeOnce' calls in destructors ?
141 This is an interim measure while we are migrating, such that
142 it is possible to delete an object conventionally and ensure
143 that its dispose method gets called. In the 'end' we would
144 instead assert that a Window has been disposed in its
145 destructor, and elide these calls.
147 As the object's vtable is altered as we go down the
148 destruction process, and we want to call the correct dispose
149 methods we need this disposeOnce(); call for the interim in
150 every destructor. This is enforced by a clang plugin.
152 The plus side of disposeOnce is that the mechanics behind it
153 ensure that a dispose() method is only called a single time,
154 simplifying their implementation.
157 ---------- Who owns & disposes what ? ----------
159 Window sub-classes tend to create their widgets in one of two
162 1. Derive from VclBuilderContainer. The VclBuilder then owns
163 many of the sub-windows, which are fetched by a 'get'
164 method into local variables often in constructors eg.
166 VclPtr<PushButton> mpButton; // in the class
167 , get(mpButton, "buttonName") // in the constructor
168 mpButton.clear(); // in dispose.
170 We only clear, not disposeAndClear() in our dispose method
171 for this case, since the VclBuilder / Container truly owns
172 this Window, and needs to dispose its hierarchy in the
173 right order - first children then parents.
175 2. Explicitly allocated Windows. These are often created and
176 managed by custom widgets:
178 VclPtr<ComplexWidget> mpComplex; // in the class
179 , mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor
180 mpComplex.disposeAndClear(); // in dispose
182 ie. an owner has to dispose things they explicitly allocate.
184 In order to ensure that the VclBuilderConstructor
185 sub-classes have their Windows disposed at the correct time
186 there is a disposeBuilder(); method - that should be added
187 -only- to the class immediately deriving from
188 VclBuilderContainer's dispose.
190 ---------- What remains to be done ? ----------
192 * Expand the VclPtr pattern to many other less
195 * create factory functions for VclPtr<> types and privatize
198 * Pass 'const VclPtr<> &' instead of pointers everywhere
199 + add 'explicit' keywords to VclPtr constructors to
200 accelerate compilation etc.
202 * Cleanup common existing methods such that they continue to
205 * Dispose functions should be audited to:
206 + not leave dangling pointsr
207 + shrink them - some work should incrementally
208 migrate back to destructors.
211 + ideally should keep a reference to pointers assigned
212 in 'get()' calls - to avoid needing explicit 'clear'
215 ---------- FAQ / debugging hints ----------
217 ** Compile with dbgutil
219 This is by far the best way to turn on debugging and
220 assertions that help you find problems. In particular
221 there are a few that are really helpful:
223 vcl/source/window/window.cxx (Window::dispose)
224 "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
225 ^^^ class name window title ^^^
226 with live children destroyed: N4sfx27sidebar6TabBarE ()
227 N4sfx27sidebar4DeckE () 10FixedImage ()"
229 You can de-mangle these names if you can't read them thus:
231 $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
232 sfx2::sidebar::SidebarDockingWindow
234 In the above case - it is clear that the children have not been
235 disposed before their parents. As an aside, having a dispose chain
236 separate from destructors allows us to emit real type names for
239 To fix this, we will need to get the dispose ordering right,
240 occasionally in the conversion we re-ordered destruction, or
241 omitted a disposeAndClear() in a ::dispose() method.
243 => If you see this, check the order of disposeAndClear() in
244 the sfx2::Sidebar::SidebarDockingWindow::dispose() method
246 => also worth git grepping for 'new sfx::sidebar::TabBar' to
247 see where those children were added.
249 ** Check what it used to do
251 While a ton of effort has been put into ensuring that the new
252 lifecycle code is the functional equivalent of the old code,
253 the code was created by humans. If you identify an area where
254 something asserts or crashes here are a few helpful heuristics:
256 * Read the git log -u -- path/to/file.cxx
258 => Is the order of destruction different ?
260 in the past many things were destructed (in reverse order of
261 declaration in the class) without explicit code. Some of these
262 may be important to do explicitly at the end of the destructor.
264 eg. having a 'Idle' or 'Timer' as a member, may now need an
265 explicit .Stop() and/or protection from running on a
266 disposed Window in its callback.
268 => Is it 'clear' not 'disposeAndClear' ?
270 sometimes we get this wrong. If the code previously used to
271 use 'delete pFoo;' it should now read pFoo->disposeAndClear();
272 Conversely if it didn't delete it, it should be 'clear()' it
273 is by far the best to leave disposing to the VclBuilder where
276 In simple cases, if we allocate the widget with VclPtrInstance
277 or VclPtr<Foo>::Create - then we need to disposeAndClear it too.
279 ** Event / focus / notification ordering
281 In the old world, a large amount of work was done in the
282 ~Window destructor that is now done in Window::dispose.
284 Since those Windows were in the process of being destroyed
285 themselves, their vtables were adjusted to only invoke Window
286 methods. In the new world, sub-classed methods such as
287 PreNotify, GetFocus, LoseFocus and others are invoked all down
288 the inheritance chain from children to parent, during dispose.
290 The easiest way to fix these is to just ensure that these
291 cleanup methods, especially LoseFocus, continue to work even
292 on disposed Window sub-class instances.
294 ** It crashes with some invalid memory...
296 Assuming that the invalid memory is a Window sub-class itself,
297 then almost certainly there is some cockup in the
298 reference-counting; eg. if you hit an OutputDevice::release
299 assert on mnRefCount - then almost certainly you have a
300 Window that has already been destroyed. This can easily
301 happen via this sort of pattern:
303 Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */);
304 // by here the pDlg quite probably points to free'd memory...
306 It is necessary in these cases to ensure that the *pDlg is
307 a VclPtr<Dialog> instead.
309 ** It crashes with some invalid memory #2...
311 Often a ::dispose method will free some pImpl member, but
312 not NULL it; and (cf. above) we can now get various virtual
313 methods called post-dispose; so:
315 a) delete pImpl; pImpl = NULL; // in the destructor
316 b) if (pImpl && ...) // in the subsequently called method