W
Wayne Pearson
Hi,
I have been testing different methods of terminating an Access application and
I've come across a few issues that may be of interest. These issues may be
specific to my particular machine and setup - I haven't had the opportunity to
test them on other machines. I've previously made a couple of posts pertaining
to this topic but I've finally come up with an appropriate (hopefully)
subject title - Terminating Access Applications. This will be my last post on
this topic and I make it with the hope that it may benefit others who have
experienced unpredictable results when using DoCmd.Quit to terminate
Access applications.
Using DoCmd.Quit to exit a database can give unexpected results if a user has
a form with a member variable whose type is a user defined class or with a
member variable that is a dynamic array. Problems can arise if the form with
the member variable is open at the time the user decides to exit the database.
One problem arises if the form's member variable has a type that is a user
defined class; its Terminate event will not be fired. Another problem
arises if code attempts to access the member variable in the Form_Unload event;
the user will encounter an error because member variables that are dynamic
arrays and member variables whose type is a user defined class will be
destroyed BEFORE the Form_Unload event.
These problems are NOT evident if
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
terminate an Access application. Note that PostMessage is suggested here NOT
SendMessage; SendMessage is vulnerable to the same deficiencies as DoCmd.Quit.
One benefit of using
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& to terminate an
Access application is that member variables that are dynamic arrays and
member variables whose type is a user defined class are not destroyed until
AFTER Form_Unload. Note also that the Terminate event WILL be fired for
member variables whose type is a user defined class. The Terminate event for
these member variables will be fired when the instance is destroyed, which
occurs AFTER the Form_Unload event.
Since using PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& to
terminate an Access application appears to result in a more orderly shutdown
I favor using it over DoCmd.Quit.
To use PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& for exiting
a database you can add the following code module to an access database project.
'------------------------------- Start of Module ------------------------------
Option Compare Database
Option Explicit
Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Const WM_CLOSE = &H10
'If the function succeeds, the return value is nonzero
Public Function ExitDatabase() As Long
ExitDatabase = PostMessage(Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&)
End Function
'-------------------------------- End of Module -------------------------------
Now it is possible to exit the database by typing
status = ExitDatabase()
Note that this works well for me; your mileage may vary.
Any comments or insights into these issues are welcomed.
Comments and full source code for a simple access project that demonstrates
these findings follow.
Simple Demonstration Application
I created a simple application to demonstrate the above behavior. The
application has two forms frmPrimary and frmSecondary, a class module Test,
and a module APIDefs.
frmPrimary has two buttons, a Display button and an Exit button. Clicking
the Display button invokes cmdDisplay_Click, which then displays frmSecondary.
Clicking the Exit button invokes cmdExit_Click, which then calls DoCmd.Quit.
Code is also present to call
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& (ExitDatabase) but
it has been commented.
frmSecondary has a class member variable m_Test, which is a class variable of
type Test
When frmSecondary is displayed, its Form_Load event procedure creates a new
instance of the member variable m_Test and populates it. When frmSecondary is
closed its Form_Unload event procedure prints m_Test's Value.
Test defines a class named Test which has one public property named Value.
APIDefs provides definitions for PostMessage, WM_CLOSE, and ExitDatabase. Note
that only ExitDatabase is defined as public.
If I open frmPrimary, press its Display button to open frmSecondary, and,
finally, press frmPrimary's Exit button to end the application then I get a
Run-time error '91': Object variable or With Block variable not set in
frmSecondary's Form_Unload event procedure. m_Test is destroyed before
Form_Unload is called. Note that m_Test's Terminate event procedure did
NOT fire.
However replacing DoCmd.Quit in frmPrimary's cmdExit_Click with
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& (ExitDatabase)
appears to solve the problem. frmSecondary's m_Test member variable is NOT
destroyed prior to calling Form_Unload and things appear to work as expected.
Note that m_Test's Terminate event procudure is called AFTER the
Form_Unload event procedure.
Note that PostMessage is used here and NOT SendMessage. Using SendMessage gives
a Run-time error '91': Object variable or With Block variable not set in
frmSecondary's Form_Unload event routine.
I would be interested in any comments regarding these issues -
the application code is given in its entirety below.
'------------------ Start of Demonstration Application Code ------------------
'-------------------------- Start of frmPrimary Code -------------------------
Option Compare Database
Option Explicit
Private Sub cmdDisplay_Click()
DoCmd.OpenForm "frmSecondary"
End Sub
Private Sub cmdExit_Click()
Dim status As Long
' Using DoCmd.Quit results in a
' Run-time error '91': Object variable or With Block variable not set
' in frmSecondary's Form_Unload event routine
' If DoCmd.Quit is used to terminate an application then
' Form class member variables are destroyed prior to calling
' Form_Unload - Why?
'
' Note also that the Terminate event procedure for class member variables
' is NOT called when DoCmd.Quit is used to end an application - Why?
'
'
' ---------------- Terminate Application Using DoCmd.Quit ----------------
DoCmd.Quit
'
'
' Using PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&, however,
' allows the application to exit gracefully
'
' Note that the Terminate event procedure for class variables IS called when
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
' end an application. The Class Terminate event procudure is called AFTER
' the Form_Unload event procedure - Good!!!
'
' Based on these findings I favor terminating Access applications with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& rather
' than DoCmd.Quit.
'
' Are there any known problems with using this method of terminating
' an application?
'
'
' ---------------- Terminate Application Using PostMessage ----------------
' status = ExitDatabase()
End Sub
'--------------------------- End of frmPrimary Code --------------------------
'------------------------- Start of frmSecondary Code ------------------------
Option Compare Database
Option Explicit
'Note that Test is defined in the Class Module Test
Dim m_Test As Test
Private Sub Form_Load()
' Set frmSecondary's m_test member variable to a new instance of Test and
' populate it
Set m_Test = New Test
m_Test.Value = 1
End Sub
Private Sub Form_Unload(Cancel As Integer)
' The next line will fail with
' Run-time error '91': Object variable or With Block variable not set - Why?
' When DoCmd.Quit is used to end the application the m_Test instance gets
' destroyed BEFORE Form_Unload is called - Why?
'
' Note also that the Terminate event procedure for class member variables is
' NOT called when DoCmd.Quit is used to end an application - Why?
'
' Note that if the application is closed with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&
' then the m_Test instance does not get destroyed BEFORE Form_Unload is
' called and the next line executes without error - Good!!!
'
' Note that the Terminate event procedure for class variables IS called when
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
' end an application. The Class Terminate event procudure is called AFTER
' the Form_Unload event procedure - Good!!!
'
' Based on these findings I favor terminating Access applications with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& rather
' than DoCmd.Quit.
'
' Are there any known problems with using this method of terminating
' an application?
Debug.Print m_Test.Value
Set m_Test = Nothing
End Sub
'-------------------------- End of frmSecondary Code -------------------------
'---------------------- Start of Class Module Test Code ----------------------
Option Compare Database
Option Explicit
Dim m_value As Long
Public Property Let Value(ByVal pValue As Long)
m_value = pValue
End Property
Public Property Get Value() As Long
Value = m_value
End Property
Private Sub Class_Initialize()
Debug.Print "Class Initialized"
End Sub
Private Sub Class_Terminate()
Debug.Print "Class Terminated"
End Sub
'----------------------- End of Class Module Test Code -----------------------
'------------------------ Start of Module APIDefs Code -----------------------
Option Compare Database
Option Explicit
Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Const WM_CLOSE = &H10
'If the function succeeds, the return value is nonzero
Public Function ExitDatabase() As Long
ExitDatabase = PostMessage(Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&)
End Function
'------------------------- End of Module APIDefs Code ------------------------
'------------------- End of Demonstration Application Code -------------------
Thanks
Wayne Pearson
I have been testing different methods of terminating an Access application and
I've come across a few issues that may be of interest. These issues may be
specific to my particular machine and setup - I haven't had the opportunity to
test them on other machines. I've previously made a couple of posts pertaining
to this topic but I've finally come up with an appropriate (hopefully)
subject title - Terminating Access Applications. This will be my last post on
this topic and I make it with the hope that it may benefit others who have
experienced unpredictable results when using DoCmd.Quit to terminate
Access applications.
Using DoCmd.Quit to exit a database can give unexpected results if a user has
a form with a member variable whose type is a user defined class or with a
member variable that is a dynamic array. Problems can arise if the form with
the member variable is open at the time the user decides to exit the database.
One problem arises if the form's member variable has a type that is a user
defined class; its Terminate event will not be fired. Another problem
arises if code attempts to access the member variable in the Form_Unload event;
the user will encounter an error because member variables that are dynamic
arrays and member variables whose type is a user defined class will be
destroyed BEFORE the Form_Unload event.
These problems are NOT evident if
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
terminate an Access application. Note that PostMessage is suggested here NOT
SendMessage; SendMessage is vulnerable to the same deficiencies as DoCmd.Quit.
One benefit of using
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& to terminate an
Access application is that member variables that are dynamic arrays and
member variables whose type is a user defined class are not destroyed until
AFTER Form_Unload. Note also that the Terminate event WILL be fired for
member variables whose type is a user defined class. The Terminate event for
these member variables will be fired when the instance is destroyed, which
occurs AFTER the Form_Unload event.
Since using PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& to
terminate an Access application appears to result in a more orderly shutdown
I favor using it over DoCmd.Quit.
To use PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& for exiting
a database you can add the following code module to an access database project.
'------------------------------- Start of Module ------------------------------
Option Compare Database
Option Explicit
Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Const WM_CLOSE = &H10
'If the function succeeds, the return value is nonzero
Public Function ExitDatabase() As Long
ExitDatabase = PostMessage(Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&)
End Function
'-------------------------------- End of Module -------------------------------
Now it is possible to exit the database by typing
status = ExitDatabase()
Note that this works well for me; your mileage may vary.
Any comments or insights into these issues are welcomed.
Comments and full source code for a simple access project that demonstrates
these findings follow.
Simple Demonstration Application
I created a simple application to demonstrate the above behavior. The
application has two forms frmPrimary and frmSecondary, a class module Test,
and a module APIDefs.
frmPrimary has two buttons, a Display button and an Exit button. Clicking
the Display button invokes cmdDisplay_Click, which then displays frmSecondary.
Clicking the Exit button invokes cmdExit_Click, which then calls DoCmd.Quit.
Code is also present to call
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& (ExitDatabase) but
it has been commented.
frmSecondary has a class member variable m_Test, which is a class variable of
type Test
When frmSecondary is displayed, its Form_Load event procedure creates a new
instance of the member variable m_Test and populates it. When frmSecondary is
closed its Form_Unload event procedure prints m_Test's Value.
Test defines a class named Test which has one public property named Value.
APIDefs provides definitions for PostMessage, WM_CLOSE, and ExitDatabase. Note
that only ExitDatabase is defined as public.
If I open frmPrimary, press its Display button to open frmSecondary, and,
finally, press frmPrimary's Exit button to end the application then I get a
Run-time error '91': Object variable or With Block variable not set in
frmSecondary's Form_Unload event procedure. m_Test is destroyed before
Form_Unload is called. Note that m_Test's Terminate event procedure did
NOT fire.
However replacing DoCmd.Quit in frmPrimary's cmdExit_Click with
PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& (ExitDatabase)
appears to solve the problem. frmSecondary's m_Test member variable is NOT
destroyed prior to calling Form_Unload and things appear to work as expected.
Note that m_Test's Terminate event procudure is called AFTER the
Form_Unload event procedure.
Note that PostMessage is used here and NOT SendMessage. Using SendMessage gives
a Run-time error '91': Object variable or With Block variable not set in
frmSecondary's Form_Unload event routine.
I would be interested in any comments regarding these issues -
the application code is given in its entirety below.
'------------------ Start of Demonstration Application Code ------------------
'-------------------------- Start of frmPrimary Code -------------------------
Option Compare Database
Option Explicit
Private Sub cmdDisplay_Click()
DoCmd.OpenForm "frmSecondary"
End Sub
Private Sub cmdExit_Click()
Dim status As Long
' Using DoCmd.Quit results in a
' Run-time error '91': Object variable or With Block variable not set
' in frmSecondary's Form_Unload event routine
' If DoCmd.Quit is used to terminate an application then
' Form class member variables are destroyed prior to calling
' Form_Unload - Why?
'
' Note also that the Terminate event procedure for class member variables
' is NOT called when DoCmd.Quit is used to end an application - Why?
'
'
' ---------------- Terminate Application Using DoCmd.Quit ----------------
DoCmd.Quit
'
'
' Using PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&, however,
' allows the application to exit gracefully
'
' Note that the Terminate event procedure for class variables IS called when
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
' end an application. The Class Terminate event procudure is called AFTER
' the Form_Unload event procedure - Good!!!
'
' Based on these findings I favor terminating Access applications with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& rather
' than DoCmd.Quit.
'
' Are there any known problems with using this method of terminating
' an application?
'
'
' ---------------- Terminate Application Using PostMessage ----------------
' status = ExitDatabase()
End Sub
'--------------------------- End of frmPrimary Code --------------------------
'------------------------- Start of frmSecondary Code ------------------------
Option Compare Database
Option Explicit
'Note that Test is defined in the Class Module Test
Dim m_Test As Test
Private Sub Form_Load()
' Set frmSecondary's m_test member variable to a new instance of Test and
' populate it
Set m_Test = New Test
m_Test.Value = 1
End Sub
Private Sub Form_Unload(Cancel As Integer)
' The next line will fail with
' Run-time error '91': Object variable or With Block variable not set - Why?
' When DoCmd.Quit is used to end the application the m_Test instance gets
' destroyed BEFORE Form_Unload is called - Why?
'
' Note also that the Terminate event procedure for class member variables is
' NOT called when DoCmd.Quit is used to end an application - Why?
'
' Note that if the application is closed with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&
' then the m_Test instance does not get destroyed BEFORE Form_Unload is
' called and the next line executes without error - Good!!!
'
' Note that the Terminate event procedure for class variables IS called when
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& is used to
' end an application. The Class Terminate event procudure is called AFTER
' the Form_Unload event procedure - Good!!!
'
' Based on these findings I favor terminating Access applications with
' PostMessage Application.hWndAccessApp, WM_CLOSE, &H0&, &H0& rather
' than DoCmd.Quit.
'
' Are there any known problems with using this method of terminating
' an application?
Debug.Print m_Test.Value
Set m_Test = Nothing
End Sub
'-------------------------- End of frmSecondary Code -------------------------
'---------------------- Start of Class Module Test Code ----------------------
Option Compare Database
Option Explicit
Dim m_value As Long
Public Property Let Value(ByVal pValue As Long)
m_value = pValue
End Property
Public Property Get Value() As Long
Value = m_value
End Property
Private Sub Class_Initialize()
Debug.Print "Class Initialized"
End Sub
Private Sub Class_Terminate()
Debug.Print "Class Terminated"
End Sub
'----------------------- End of Class Module Test Code -----------------------
'------------------------ Start of Module APIDefs Code -----------------------
Option Compare Database
Option Explicit
Declare Function PostMessage Lib "user32" Alias "PostMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
Const WM_CLOSE = &H10
'If the function succeeds, the return value is nonzero
Public Function ExitDatabase() As Long
ExitDatabase = PostMessage(Application.hWndAccessApp, WM_CLOSE, &H0&, &H0&)
End Function
'------------------------- End of Module APIDefs Code ------------------------
'------------------- End of Demonstration Application Code -------------------
Thanks
Wayne Pearson