Handling application.quit event

  • Thread starter Thread starter kostas
  • Start date Start date
K

kostas

Hello, i am developing an exe application and i use global object to declare
outlook application withevents. I use outlook 2007 sp2 and i am having
trouble releasing my variable references or reusing then. I use this code :

Public Class StartOE
Private WithEvents o As Outlook.Application = Nothing
Private ns As Outlook.NameSpace = Nothing
Private omFolder As Outlook.MAPIFolder = Nothing

Public Sub New()
If detectOutlook() Then
o = GetObject(, "Outlook.Application")
Else
o = New Outlook.Application
End If

Try
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", True, True) 'log into new Outlook session
omFolder =
ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) 'get folder
If o.Explorers.Count = 0 Then o.Explorers.Add(omFolder) 'show
explorer
Catch ex As Exception
Err.Clear()
End Try
End Sub

Private Sub o_Quit() Handles o.Quit
' MsgBox("quit")
If Not omFolder Is Nothing Then Marshal.ReleaseComObject(omFolder)
If Not ns Is Nothing Then Marshal.ReleaseComObject(ns)
If Not o Is Nothing Then Marshal.ReleaseComObject(o)
omFolder = Nothing
ns = Nothing
o = Nothing 'Causes RPC exception
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
End Class

The problem starts when the user closes outlook and the event triggers .
I know that when quit event is executed, outlook releases all its references
to external objects, so i am getting RPC exception setting object o to
nothing (see my code). I do not want to prevent users from closing outlook, i
just want to be able to launch outlook again using the same declared objects.
I get this error even if, after the execution of quit event, i try to set "
o = New Outlook.Application" . Can anyone point me to a workaround? I want to
be able to reuse 'o' object using 'new' or getobject.
Thank you in advance
 
I'm not sure why you're using a logon calling for a new session and a logon
dialog. If Outlook is already running you usually use a logon of Logon("",
"", False, False).

To get an event for when Outlook is closing before Quit() fires and Outlook
objects are out of scope you can trap the Explorer.Close() event. If there
are no other Explorers and no Inspectors are open then Outlook is closing
and you can release your objects then.
 
Thank you for your answer. I will try the workaround you mentioned and post
the code here tomorrow.
 
Hello again, it seems that even if i trap explorer.close event, all objects
are automatically released. I now get error "COM object that has been
separated from its underlying RCW can not be used". Can anyone please point
me to what am i doing wrong?
Here is my code:

Public Class StartO
Private WithEvents o As Outlook.Application = Nothing

Private ns As Outlook.NameSpace = Nothing

Private WithEvents ex As Outlook.Explorer

Private omFolder As Outlook.MAPIFolder = Nothing

Public Sub New()
If detectOutlook() Then
o = GetObject(, "Outlook.Application")
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, False) 'log into new Outlook session
Else
o = New Outlook.Application
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, True) 'log into new Outlook session
End If

Try
omFolder =
ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) 'get folder
If o.Explorers.Count = 0 Then o.Explorers.Add(omFolder) 'show
explorer
ex = o.ActiveExplorer
Catch ex As Exception
Err.Clear()
End Try

End Sub

Private Sub ex_Close() Handles ex.Close
Try
If o.Inspectors.Count = 0 Then 'here i get the error
If o.Explorers.Count = 0 Then 'or sometimes here
If Not omFolder Is Nothing Then
Marshal.ReleaseComObject(omFolder)
If Not ns Is Nothing Then Marshal.ReleaseComObject(ns)
If Not o Is Nothing Then Marshal.ReleaseComObject(o)
omFolder = Nothing
ns = Nothing
o = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End If
End If
Catch ex As Exception
Err.Clear()
End Try

End Sub
End Class
 
The RCW error is when you access a COM component where the wrapper for the
CLR on that COM object has already been released.

Explorer.Close() should fire long enough before any Quit() events so that
your Outlook objects should still be in scope and not released yet. You can
and should first test for (Outlook.Explorers != null), however in the
Explorer.Close() event that collection should never be null unless you are
releasing your Explorers collection object prior to that event.
 
Hello again ken and thank you for your precious help. I have created a new
project in vs2008 just to make sure that i do not release any objects by
mistake. This code runs really well as long as there is no outlook running.
the close event fires and i can release all objects. The problem is that if
outlook is running i am getting the same RCW error for object "explorer" as
long as the close event fires. In this case senario :
1. outlook 2007 is running
2. my program runs and gets a reference to running outlook using getobject.
3. user closes outlook
4. close event fires,
5. vs2008 seems to be busy for a few seconds
6. A small blue dot appears at breakpoint "the process or thread has
changed since last step"
7. i catch RCW error
This is all the code i use:

Imports Microsoft.Office.Interop
Imports System
Imports System.IO
Public Class Form1

Private WithEvents o As Outlook.Application = Nothing

Private ns As Outlook.NameSpace = Nothing

Private WithEvents explorer As Outlook.Explorer

Private omFolder As Outlook.MAPIFolder = Nothing

Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As IntPtr) As IntPtr

Public Sub New1()
If detectOutlook() Then

o = GetObject(, "Outlook.Application")
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, False) 'log into Outlook session
Else
o = New Outlook.Application
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, True) 'log into new Outlook session
End If

Try
omFolder =
ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) 'get folder
If o.Explorers.Count = 0 Then o.Explorers.Add(omFolder) 'show
explorer()
explorer = o.ActiveExplorer
Catch ex1 As Exception
Err.Clear()
End Try

End Sub
Private Function detectOutlook() As Boolean
detectOutlook = False
Dim hwnd As IntPtr
hwnd = FindWindow("rctrl_renwnd32", IntPtr.Zero)
If hwnd <> IntPtr.Zero Then Return True
End Function
Private Sub ex_Close() Handles explorer.Close
'this works fine if i create outlook application using o = New
Outlook.Application
Try
If Not o.Explorers Is Nothing Then
If o.Explorers.Count = 0 Then
If Not omFolder Is Nothing Then
Marshal.ReleaseComObject(omFolder)
If Not explorer Is Nothing Then
Marshal.ReleaseComObject(explorer)
explorer = Nothing
If Not ns Is Nothing Then Marshal.ReleaseComObject(ns)
If Not o Is Nothing Then Marshal.ReleaseComObject(o)

omFolder = Nothing
ns = Nothing
o = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End If
End If
End If
Catch ex1 As Exception
Err.Clear() 'Here i catch RCW error
End Try

End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
New1()
End Sub
End Class
 
I don't use VB.NET very often, but I don't really use GetObject() at all in
managed code. I'm not saying it doesn't work or has bad effects, I just
don't use it. What I do use when I want to get an Outlook instance in a
standalone program is something like this, which is a C# procedure. It
shouldn't be hard to translate to VB.NET though:

private void InstantiateOutlook()

{

try

{

System.Diagnostics.Process[] processes =
System.Diagnostics.Process.GetProcessesByName("OUTLOOK");

int collCount = processes.Length;

if (collCount != 0)

{

// Outlook already running, hook into the Outlook instance

outlookApp = Marshal.GetActiveObject("Outlook.Application") as
Outlook.Application;

}

if (outlookApp != null)

{

canQuit = false;

}

else

{

// Outlook not already running, start it

outlookApp = new Outlook.Application();

canQuit = true;

}

if (outlookApp != null)

{

if (canQuit)

{

ns = outlookApp.GetNamespace("MAPI");

ns.Logon(System.Reflection.Missing.Value,
System.Reflection.Missing.Value, System.Reflection.Missing.Value,
System.Reflection.Missing.Value);

}

else

{

// Outlook was running, just hook into existing Session

ns = outlookApp.Session;

}

}

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

And I handle Explorer.Close() as well as Application.Quit() to do my
shutdowns.
 
Hello again Ken, i translated your code in vb.net skipping the outlook
detection algorithm. i used Marshal.GetActiveObject("Outlook.Application") to
get running outlook and then assign o.session to namespace. The code now
looks like this :
Public Sub New1()
If detectOutlook() Then
o = Marshal.GetActiveObject("Outlook.Application")
ns = o.Session
Else
o = New Outlook.Application
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, True) 'log into new Outlook session
End If

Try
omFolder =
ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) 'get folder
If o.Explorers.Count = 0 Then o.Explorers.Add(omFolder) 'show
explorer()
explorer = o.ActiveExplorer
Catch ex1 As Exception
Err.Clear()
End Try

End Sub

The RCW error in explorer.close event still occurs. All objects seem to be
unaffected except explorer object which produces the RCW error. I have also
tried to change registry to change shutdown event to preSP2 behavior but
still nothing.
Maybe somehow this happens because i declare explorer object withevents and
then it gets its reference from o.activeExplorer. I can't figure it out why
is this happening only when i use running outlook application and not when i
create outlook app.
Ken Slovak - said:
I don't use VB.NET very often, but I don't really use GetObject() at all in
managed code. I'm not saying it doesn't work or has bad effects, I just
don't use it. What I do use when I want to get an Outlook instance in a
standalone program is something like this, which is a C# procedure. It
shouldn't be hard to translate to VB.NET though:

private void InstantiateOutlook()

{

try

{

System.Diagnostics.Process[] processes =
System.Diagnostics.Process.GetProcessesByName("OUTLOOK");

int collCount = processes.Length;

if (collCount != 0)

{

// Outlook already running, hook into the Outlook instance

outlookApp = Marshal.GetActiveObject("Outlook.Application") as
Outlook.Application;

}

if (outlookApp != null)

{

canQuit = false;

}

else

{

// Outlook not already running, start it

outlookApp = new Outlook.Application();

canQuit = true;

}

if (outlookApp != null)

{

if (canQuit)

{

ns = outlookApp.GetNamespace("MAPI");

ns.Logon(System.Reflection.Missing.Value,
System.Reflection.Missing.Value, System.Reflection.Missing.Value,
System.Reflection.Missing.Value);

}

else

{

// Outlook was running, just hook into existing Session

ns = outlookApp.Session;

}

}

}

catch (System.Exception ex)

{

MessageBox.Show(ex.Message);

}

}

And I handle Explorer.Close() as well as Application.Quit() to do my
shutdowns.




kostas said:
Hello again ken and thank you for your precious help. I have created a new
project in vs2008 just to make sure that i do not release any objects by
mistake. This code runs really well as long as there is no outlook
running.
the close event fires and i can release all objects. The problem is that
if
outlook is running i am getting the same RCW error for object "explorer"
as
long as the close event fires. In this case senario :
1. outlook 2007 is running
2. my program runs and gets a reference to running outlook using
getobject.
3. user closes outlook
4. close event fires,
5. vs2008 seems to be busy for a few seconds
6. A small blue dot appears at breakpoint "the process or thread has
changed since last step"
7. i catch RCW error
This is all the code i use:

Imports Microsoft.Office.Interop
Imports System
Imports System.IO
Public Class Form1

Private WithEvents o As Outlook.Application = Nothing

Private ns As Outlook.NameSpace = Nothing

Private WithEvents explorer As Outlook.Explorer

Private omFolder As Outlook.MAPIFolder = Nothing

Private Declare Function FindWindow Lib "user32" Alias _
"FindWindowA" (ByVal lpClassName As String, _
ByVal lpWindowName As IntPtr) As IntPtr

Public Sub New1()
If detectOutlook() Then

o = GetObject(, "Outlook.Application")
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, False) 'log into Outlook session
Else
o = New Outlook.Application
ns = o.GetNamespace("MAPI") ' get namespace
ns.Logon("", "", False, True) 'log into new Outlook session
End If

Try
omFolder =
ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox) 'get folder
If o.Explorers.Count = 0 Then o.Explorers.Add(omFolder) 'show
explorer()
explorer = o.ActiveExplorer
Catch ex1 As Exception
Err.Clear()
End Try

End Sub
Private Function detectOutlook() As Boolean
detectOutlook = False
Dim hwnd As IntPtr
hwnd = FindWindow("rctrl_renwnd32", IntPtr.Zero)
If hwnd <> IntPtr.Zero Then Return True
End Function
Private Sub ex_Close() Handles explorer.Close
'this works fine if i create outlook application using o = New
Outlook.Application
Try
If Not o.Explorers Is Nothing Then
If o.Explorers.Count = 0 Then
If Not omFolder Is Nothing Then
Marshal.ReleaseComObject(omFolder)
If Not explorer Is Nothing Then
Marshal.ReleaseComObject(explorer)
explorer = Nothing
If Not ns Is Nothing Then
Marshal.ReleaseComObject(ns)
If Not o Is Nothing Then
Marshal.ReleaseComObject(o)

omFolder = Nothing
ns = Nothing
o = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()
End If
End If
End If
Catch ex1 As Exception
Err.Clear() 'Here i catch RCW error
End Try

End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button1.Click
New1()
End Sub
End Class

.
 
I don't know why you get this when you hook into a running Outlook
application. You can try getting your Explorer object from Explorers(1)
instead of from ActiveExplorer, and I'd certainly test for ActiveExplorer
not being Nothing (null).

Usually you would only get an RCW error if you have already released the
object elsewhere or if your release code runs twice.

I don't think the problem is WithEvents, but you can always comment that out
and instantiate your handler using AddHandler instead and see if that makes
a difference.
 
Back
Top