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 prevelance 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 * Cleanup DogTags and LazyDelete.
194 * Expand the VclPtr pattern to many other less
197 * create factory functions for VclPtr<> types and privatize
200 * Pass 'const VclPtr<> &' instead of pointers everywhere
201 + add 'explicit' keywords to VclPtr constructors to
202 accelerate compilation etc.
204 * Cleanup common existing methods such that they continue to
207 * Dispose functions should be audited to:
208 + not leave dangling pointsr
209 + shrink them - some work should incrementally
210 migrate back to destructors.
213 + ideally should keep a reference to pointers assigned
214 in 'get()' calls - to avoid needing explicit 'clear'
217 * VclBuilder 'makeFoo' methods
218 + these should return VclPtr<> types and have their
219 signatures adjusted en-masse.
220 + currently we use a VclPtr<> constructor with
221 SAL_NO_ACQUIRE inside the builder.
223 ---------- FAQ / debugging hints ----------
225 ** Compile with dbgutil
227 This is by far the best way to turn on debugging and
228 assertions that help you find problems. In particular
229 there are a few that are really helpful:
231 vcl/source/window/window.cxx (Window::dispose)
232 "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
233 ^^^ class name window title ^^^
234 with live children destroyed: N4sfx27sidebar6TabBarE ()
235 N4sfx27sidebar4DeckE () 10FixedImage ()"
237 You can de-mangle these names if you can't read them thus:
239 $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
240 sfx2::sidebar::SidebarDockingWindow
242 In the above case - it is clear that the children have not been
243 disposed before their parents. As an aside, having a dispose chain
244 separate from destructors allows us to emit real type names for
247 To fix this, we will need to get the dispose ordering right,
248 occasionally in the conversion we re-ordered destruction, or
249 omitted a disposeAndClear() in a ::dispose() method.
251 => If you see this, check the order of disposeAndClear() in
252 the sfx2::Sidebar::SidebarDockingWindow::dispose() method
254 => also worth git grepping for 'new sfx::sidebar::TabBar' to
255 see where those children were added.
257 ** Check what it used to do
259 While a ton of effort has been put into ensuring that the new
260 lifecycle code is the functional equivalent of the old code,
261 the code was created by humans. If you identify an area where
262 something asserts or crashes here are a few helpful heuristics:
264 * Read the git log -u -- path/to/file.cxx
266 => Is the order of destruction different ?
268 in the past many things were destructed (in reverse order of
269 declaration in the class) without explicit code. Some of these
270 may be important to do explicitly at the end of the destructor.
272 eg. having a 'Idle' or 'Timer' as a member, may now need an
273 explicit .Stop() and/or protection from running on a
274 disposed Window in its callback.
276 => Is it 'clear' not 'disposeAndClear' ?
278 sometimes we get this wrong. If the code previously used to
279 use 'delete pFoo;' it should now read pFoo->disposeAndClear();
280 Conversely if it didn't delete it, it should be 'clear()' it
281 is by far the best to leave disposing to the VclBuilder where
284 In simple cases, if we allocate the widget with VclPtrInstance
285 or VclPtr<Foo>::Create - then we need to disposeAndClear it too.
287 ** Event / focus / notification ordering
289 In the old world, a large amount of work was done in the
290 ~Window destructor that is now done in Window::dispose.
292 Since those Windows were in the process of being destroyed
293 themselves, their vtables were adjusted to only invoke Window
294 methods. In the new world, sub-classed methods such as
295 PreNotify, GetFocus, LoseFocus and others are invoked all down
296 the inheritance chain from children to parent, during dispose.
298 The easiest way to fix these is to just ensure that these
299 cleanup methods, especially LoseFocus continue to work even
300 on disposed Window sub-class instances.
302 ** It crashes with some invalid memory ...
304 Assuming that the invalid memory is a Window sub-class itself,
305 then almost certainly there is some cockup in the
306 reference-counting; eg. if you hit an OutputDevice::release
307 assert on mnRefCount - then almost certainly you have a
308 Window that has already been destroyed. This can easily
309 happen via this sort of pattern:
311 ModelessDialog *pDlg = VclPtr<ModelessDialog>(nullptr /* parent */);
312 // by here the pDlg quite probably points to free'd memory
315 It is necessary in these cases to ensure that the *pDlg is
316 a VclPtr<ModelessDialog> instead.
318 ** It crashes with some invalid memory #2 ...
320 Often a ::dispose method will free some pImpl member, but
321 not NULL it; and (cf. above) we can now get various virtual
322 methods called post-dispose; so:
324 a) delete pImpl; pImpl = NULL; // in the destructor
325 b) if (pImpl && ...) // in the subsequently called method