Forms Revisited, and DLL Base Address
This column appeared in the July 1999 edition of EXE. I
occasionally encounter relatively advanced Visual Basic programmers who still
don't seem to grasp some of the concepts of manipulating forms. A quick
explanation, and then a chat about the DLL Base Address setting in the Make DLL
dialog box.
The first action that every new Visual Basic programmer
performs is to paint a button control onto the default Form1, add a MsgBox
statement to the Command1_Click event, and then run it. From this humble
beginning the newbie developer then moves on to understand how to manipulate
methods and properties on controls, and gradually learns the core language set.
More advanced concepts such as writing ActiveX components eventually follow.
Its not unusual, however, for a developer to become so proficient with
the basic usage of forms that the true nature of their architecture can be
overlooked. To this end I want to start this months column by reviewing some
information about Forms, one of the most fundamental building blocks of Visual
Basic applications.
Forms are objects
Consider the following piece of code:
Load Form1
Form1.Show
Unload Form1
Once the Unload call has been made does all of the memory used
by Form1 get freed up? The answer is not necesarily, which on the face of it is
rather surprising. The reason is due to the way that forms are handled by
Visual Basic. A form is actually an object that is manipulated behind the
scenes by the Visual Basic engine. The best way to manipulate a form is to
treat it as an object, thus:
Dim ofrmDataEntry As FrmDataEntry
Set ofrmDataEntry = New FrmDataEntry
Load ofrmDataEntry
ofrmDataEntry.Show vbModal
and then...
Unload ofrmDataEntry
Set ofrmDataEntry = Nothing
When the Unload statement is executed the associated window
resource is deleted but module-level variable data is still retained until all
references to the form are set to Nothing. This is the way that MDI child forms
are typically implemented in Visual Basic applications.
The behind the scenes manipulation that I mentioned is that
Visual Basic automatically creates a global-scope object variable to cater for
forms that are referenced directly by the programmer. For example if a project
contains a form called Form1 and the programmer makes a call such as
Load Form1
then Visual Basic will quietly create a global variable along
the lines of
Dim Form1 As Form1
Set Form1 = New Form1
so that the Form1 that the programmer is referencing is
actually the object instance rather than the base class. Getting used to
dealing with forms in this manner can lead to a more efficient use of memory.
For example, an application that has a large number of forms will consume more
memory as each of these forms are called into existence even when they
have been unloaded. By treating the forms as objects and subsequently setting
the references to Nothing after use will destroy the forms and lead to less
wasted memory.
Form lifecycle
The Form object exposes several important events during its
lifetime:
-
The first of these is the Initialize
event which occurs when the new instance of the form object
comes into existence. At this stage module-level declarations
are accessible and Subs, Functions and Properties can be accessed.
However the form itself will yet not be loaded into memory,
and consequently nor will any child controls.
-
The Load event occurs when the underlying window that is associated with the
form is created. At this time all child controls are also created. If any
preceding code makes a reference to any aspect of the form itself then the Load
event will automatically trigger. Any subsequent calls to load a form are then
ignored a Load will only occur once between every invocation of the
corresponding Unload call.
-
The Activate event occurs whenever the form gains focus in relation to other
forms within the same application. For example an MDI application might have a
number of child windows open. As the user selects each child window in turn the
Activate event for the newly-selected form will fire, as will the Deactivate
event for the form that has just lost the focus. One possible gotcha here is
that a form will not issue a Deactivate event if it is being unloaded from a
status of having focus.
-
The Unload event occurs just before the window is destroyed. It provides the
opportunity to save any state data or to update another part of the
application. After this event the form object will still be in existence, so it
will revert to its post-Initialize Status. Consequently all declaration-level
data will still be present, but any future reference to the actual form itself
will cause a new Load event to take place which will initialise the properties
of all controls.
This event comes with a parameter called Cancel which allows
you to halt the unloading of the form, but this isnt the best place to
make this kind of decision. An event that occurs directly before Unload is
QueryUnload which also provides information as to what originated the call to
unload the form. For instance it might be appropriate to ask the users whether
they really want to close a top-level window (and therefore the whole
application) if they press the X in the top right-hand corner of the window,
but on the other hand if Windows itself is closing then you might not feel that
it is appropriate to ask.
Finally, the Terminate event is triggered just before the
class instance is destroyed. After this happens all remaining module-level
declarations disappear.
One final word to discuss with the lifecycle of forms is that
calling the End command will immediately halt execution, forgoing any Unload or
Terminate calls.
DLL Base Addresses
Moving on to a different topic, there is a phenomenon known as
synchronicity in which the same event occurs at the same time or in unison. As
far as I can recall at no time in my life previously has anybody ever asked me
to explain what the DLL Base Address option in the compile dialog is for, and
then three separate people have asked me this very question during the past few
weeks. I know a sign when Im given one.
In order to understand what the DLL Base Address is for, it is
worth briefly reviewing the architecture of a Win32 process first. In both
Windows NT and Windows 9x an application resides within a process, which is
defined as a 4GB address space within which it and any dependent components
reside. Although the specifics of this implementation vary for each platform
the basic idea is that the lower 2GB is specifically used by the application
and its resources, while the upper 2GB is used by the operating system. The
main application executable loads first at a point close to the bottom of the
2GB map, although this initial offset varies depending on the platform. This
initial address is generally assigned by the operating system. However, the
various components that are called upon by the application DLLs, OCXs,
and so on can nominate their own preferred location address. If nothing
else is occupying that address at the time the component is loaded then Windows
is usually happy to oblige, otherwise the memory location must be altered
an operation known as rebasing.
The options dialog that accompanies the Make Project form
contains a text box in which you can enter a preferred address. The default
value provided by Visual Basic is always &H11000000, so unless you change
it the likelihood is that every component you produce will have the same load
address. If a component doesnt get in at the address that it requested
then of course its not the end of the world, it just gets loaded into a
different location. There are a couple of good reasons why developers take the
time to produce a set of components with different load addresses. First of all
it is to avoid the overhead that is incurred by Windows having to perform the
rebasing operation. The other reason is to do with the way that Windows manages
its internal memory. Within a Win32 environment there can be a sharing of code
pages for in-process components among different processes when the component
has been able to load at its base address. However if the code has been rebased
then this cannot happen and a fresh load must take place for each new
invocation by another application. In practical terms this can make the
difference between the same component physically being loaded once or five
times.
Base addresses are calculated on 64K boundaries, starting at
16MB (&H1000000) and going up to 2GB (&H80000000). This means that
there can theoretically be up to 32,512 separate 64K addresses available to
choose from. Microsoft generally recommend that you choose any of these
possible values at random, and therefore avoid the default base address
whenever possible. Owners of the Bruce McKinney book Hardcore Visual Basic (2nd
edition, Microsoft Press) will find on the accompanying CD a utility called
Address-o-matic that will randomly generate a suitable address. Corporate
development teams might find it worthwhile to maintain a central register of
components that have been developed and which are still in use throughout the
organisation. Through this database they would be able to keep track of all
base addresses currently allocated and could assign new ones accordingly.
VSVIEW Review
I've recently received a review copy of VideoSoft VSVIEW 6.0
which replaces the Printer object in Visual Basic. Support for printing has
never been particularly rich in Visual Basic although the situation was
improved somewhat with the inclusion of the Data Reports engine into the
current version.
The VSVIEW product is shipped in the form of three components:
vsPrinter, vsDraw, and vsViewPort. The most significant component is vsPrinter
which "allows you to quickly and easily create documents for printing and
previewing". In essence this means that you can create a document that
includes columns, graphics, headers and footers, and the sort of formatting
that is defined by underlying RTF or HTML descriptions. Having defined this
document you can then display multiple page print previews that include
zooming, panning, and thumbnails. This particular component offers a rich set
of supporting methods and functions. Also in the package, the vsDraw component
provides a means of producing images such as charts, diagrams, and maps, while
vsViewPort is concerned with creating scrollable areas within forms, for
example to implement forms that are larger than the actual window.
As third-party toolkits go I must say that I am impressed with the quality of
the documentation. A 279-page manual accompanies the CD and provides a full
description of all of the properties, methods, and so on. One touch that I also
like it that the first page of each component description includes a list of
useful information such as the component file name, the GUID, the description
(as it will appear in the Components dialog in Visual Basic), the icon that
will appear in the toolbox, and a general descriptive text. It's clear that a
lot of effort was put into this. There are also four separate tutorial
chapters, and a fair amount of sample code is scattered throughout the whole
book.
VSVIEW 6.0 is priced at £199, and is available from most
software resellers. My thanks to Contemporary plc for the review copy. Further
details can be found at http://www.contemporary.co.uk
or by phone on 01344 873434.