bump product version to 7.2.5.1
[LibreOffice.git] / vcl / README.lifecycle
blob9933e40f09559ba8927737eaac4e61252c9859f2
1 ** Understanding transitional VCL lifecycle **
3 ---------- How it used to look ----------
5         All VCL classes were explicitly lifecycle managed; so you would
6 do:
7         Dialog aDialog(...);   // old - on stack allocation
8         aDialog.Execute(...);
9 or:
10         Dialog *pDialog = new Dialog(...);  // old - manual heap allocation
11         pDialog->Execute(...);
12         delete pDialog;
13 or:
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
25 between calls:
27         ImplDelData aDogTag( this );    // 'orrible old code
28         Show( true, ShowFlags::NoActivate );
29         if( !aDogTag.IsDead() )         // did 'this' go invalid yet ?
30                 Update();
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
46         derived classes thus:
48         VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE );
49         ...
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'
59         method calls:
61 ** What is 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
69         behavioral change.
71         As such a VclPtr can have three states:
73         VclPtr<PushButton> pButton;
74         ...
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
103         pointer for you.
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(...);
118 -       delete pDialog;
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
160 ways and often both.
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
193           than safe VCL types.
195         * create factory functions for VclPtr<> types and privatize
196           their constructors.
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
203           work post-dispose.
205         * Dispose functions should be audited to:
206                 + not leave dangling pointsr
207                 + shrink them - some work should incrementally
208                   migrate back to destructors.
210         * VclBuilder
211                 + ideally should keep a reference to pointers assigned
212                   in 'get()' calls - to avoid needing explicit 'clear'
213                   code in destructors.
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
237         parents here.
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
274            possible.
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