Lee Gillie
8/31/2007 8:35:00 PM
One confounding issue I have with .NET is that objects are not always
destructed. When you release instances they are destructed during GC. If
a GC has not occurred at image run down then the memory block is
released without rundown. Protect yourself by always closing or
disposing when available. But of course COM object interop is a little
different animal. I use this for out of process servers to provide
myself with a dispose method:
HTH - Lee
Imports System.Diagnostics
''' <summary>
''' This class helps manage out of process COM servers.
''' Create one new instance of this class for each object you want to
create.
''' Use the CreateObject method to instance your out of process server.
''' When done, do your best to shut down the COM object.
''' Finally call dispose on this object.
''' You can simply let this object go out of scope, but GC runs deferred
and sometimes is never run.
'''
''' Lee Gillie, CCP
''' Online Data Processing, Inc.
''' Spokane, WA
'''
''' </summary>
''' <remarks></remarks>
Public Class OutOfProcessComServer
Implements IDisposable
' THERE ARE KNOWN BUGS AUTOMATICE OFFICE PROGRAMS
' such as WORD, EXCEL, and so on. You PROPERLY
' shut down the component, but the process continues
' to live!
Friend m_Object As Object
Friend m_ProcessID As Integer
Friend m_ServerName As String
Friend m_ProgID As String
Friend m_ProcessName As String
Public Sub New()
m_Object = Nothing
m_ProcessID = -1
m_ServerName = ""
m_ProgID = ""
End Sub
Public Sub New(ByVal ProgID As String)
CreateObject(ProgID)
End Sub
Public Sub New(ByVal ProgID As String, ByVal ServerName As String)
CreateObject(ProgID, ServerName)
End Sub
Public Function CreateObject(ByVal ProgID As String) As Object
Return CreateObject(ProgID, "")
End Function
Public Function CreateObject(ByVal ProgID As String, ByVal
ServerName As String) As Object
If m_Object Is Nothing Then
' If the COM object is NOT already known, then create it
m_ProgID = ProgID
m_ServerName = ServerName
' For any given COM object, we MUST know the name of
' the program it runs for the out-of-process server.
' No way around this I know of except to hard code it.
' Maybe it could be queried via INTEROP ?
Select Case m_ProgID.Trim.ToUpper
Case "WORD.APPLICATION"
m_ProcessName = "WINWORD"
Case "EXCEL.APPLICATION"
m_ProcessName = "EXCEL"
Case Else
Throw New Exception("Process Name for OUT OF
PROCESS SERVER " & ProgID & " is not known.")
End Select
Dim PIDList As New ArrayList
Dim Processes() As Process
If m_ServerName.Length > 0 Then
' Just prior to creating the out of process server,
' find the processes for the given name.
Processes = Process.GetProcessesByName(m_ProcessName,
m_ServerName)
' Now instance the COM object which starts the service
process
m_Object = Microsoft.VisualBasic.CreateObject(ProgID,
m_ServerName)
Else
' Just prior to creating the out of process server,
' find the processes for the given name.
Processes = Process.GetProcessesByName(m_ProcessName)
' Now instance the COM object which starts the service
process
m_Object = Microsoft.VisualBasic.CreateObject(ProgID)
End If
' Now make a list process ids of all the pre-existing processes
Dim proc As Process
For Each proc In Processes
PIDList.Add(proc.Id)
Next
' And again, survey by the given name
If m_ServerName.Length > 0 Then
Processes = Process.GetProcessesByName(m_ProcessName,
m_ServerName)
Else
Processes = Process.GetProcessesByName(m_ProcessName)
End If
' Now any process id not in our pre-existing list is the
new one
For Each proc In Processes
If Not PIDList.Contains(proc.Id) Then
' We found the new one we just created !
Me.m_ProcessID = proc.Id
Exit For
End If
Next
End If
' Return our object to caller
Return m_Object
End Function
Public Sub Dispose() Implements System.IDisposable.Dispose
' Last ditch effort to release the server after the
' user has closed all instances.
If m_Object IsNot Nothing Then
Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(m_Object)
m_Object = Nothing
Catch ex As Exception
Debug.WriteLine("WARNING: Unable to ReleaseComObject: "
& ex.Message)
End Try
End If
If m_ProcessID = -1 Then
Debug.WriteLine("WARNING: Out Of Process Server ProcessID
was never discovered.")
Else
Dim proc As Process
Try
If m_ServerName.Length > 0 Then
proc = Process.GetProcessById(m_ProcessID,
m_ServerName)
Else
proc = Process.GetProcessById(m_ProcessID)
End If
Catch ex As ArgumentException
' Server shut down as it should have
Return
End Try
' Server did NOT shut down as it should have. It needs our
help.
' This routine WONT return until the process has fully run
down.
' Or 10 seconds, which ever comes first.
Debug.WriteLine("Saftey shut down of out of process COM
server " & m_ProgID)
proc.Kill()
If proc.WaitForExit(10000) Then
' Process exited within the 10 seconds we are willing
to wait.
Else
If m_ServerName.Length > 0 Then
Debug.WriteLine("WARNING: Out of process COM server
" & m_ProgID & ", local ProcessID " & m_ProcessID.ToString & " did not
exit in 10 seconds.")
Else
Debug.WriteLine("WARNING: Out of process COM server
" & m_ProgID & ", " & m_ServerName & " ProcessID " &
m_ProcessID.ToString & " did not exit in 10 seconds.")
End If
End If
End If
m_ProcessID = -1
m_ServerName = ""
m_ProgID = ""
End Sub
End Class