S
Stephany Young
Using VS2005 and VB.NET and given a Windows Forms application with a single
form (Form1) with 2 buttons (Button1 and Button2), I am attempting to
instantiate an instance of Excel utilising late binding. The pertinent code
is shown below.
The business rules are:
1. If an instance of Excel is already running then use the equivalent of
GetObject to obtain a reference to that instance, and, when finished, leave
that instance running.
2. If no instance of Excel is running then use the equivalent of
CreateObject to create a new instance, and, when finished, destroy that
instance.
The line 'm_object = Marshal.GetActiveObject(ProgID)' in Button1_Click is
the equivalent of GetObject.
The line m_object = Activator.CreateInstance(m_type)' in Button1_Click is
the equivalent of CreateObject.
The logic is that calling Marshal.GetActiveObject will throw an exception if
there are no existing instances of Excel.
If no instance of Excel is running then the code quite hapilly takes the
'create' path and all is well. The instance is created, and, when Button2 is
clicked, the instance is destroyed as expected. At the 'tell-tale' lines the
following is reported (again, as expected):
Microsoft.Office.Interop.Excel.ApplicationClass
12.0
My problem is that when an existing instance is present then the Type of the
object returned by the call to Marshal.GetActiveObject (or GetObject for
that matter), is System.__ComObject and the 2nd 'tell-tale' line fails
because the type of m_object and the type of m_type are mismatched.
What I am trying to find is a way of reliably 'casting' m_object
(System.__ComObject) to m_type
(Microsoft.Office.Interop.Excel.ApplicationClass) using late binding.
There is a little more background as to why late binding is required.
For the purposes of the application in question, we are unbale to dictate
which version of Office the customer shall have (if any). If they don't have
the component installed at all then that fact is easily determined and
access to the functionality that uses the component is supressed. If they do
have it then if their version is below a specific verison (11.0) then the
functionality that uses the component is also supressed. If they have
version 11.0 (Office 2003) or 12.0 (Office 2007) then all is fine except we
don't know this at compile time and therefore cannot bind against the
appropriate PIA.
If anyone else has successfully addressed this issue then I would
interested in hearing how it is done.
Content of Form1.vb
-------------------
Imports System.Reflection
Imports System.Runtime.InteropServices
Public Class Form1
Private Const ProgID As String = "Excel.Application"
Private m_type As Type = Nothing
Private m_object As Object = Nothing
Private m_created As Boolean = False
Private m_parameters As Object() = Nothing
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Handles Button1.Click
m_type = Type.GetTypeFromProgID(ProgID)
Try
m_object = Marshal.GetActiveObject(ProgID)
m_created = False
Catch
m_object = Activator.CreateInstance(m_type)
m_created = True
End Try
Console.WriteLine(m_object.GetType.ToString)
m_parameters = New Object() {}
Console.WriteLine(m_type.InvokeMember("Version",
BindingFlags.GetProperty, Nothing, m_object, m_parameters))
End Sub
Private Sub Button2_Click(ByVal sender As Object, ByVal e As EventArgs)
Handles Button2.Click
If m_created Then
m_parameters = New Object() {}
m_type.InvokeMember("Quit", BindingFlags.InvokeMethod, Nothing,
m_object, m_parameters)
End If
GCCom(m_object)
Console.WriteLine("Done")
End Sub
Private Sub GCCom(ByVal ParamArray objects As Object())
For Each _object As Object In objects
If _object IsNot Nothing Then
Dim _references As Integer = Integer.MaxValue
Do
_references = Marshal.ReleaseComObject(_object)
Loop While _references > 0
_object = Nothing
End If
Next
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
End Class
form (Form1) with 2 buttons (Button1 and Button2), I am attempting to
instantiate an instance of Excel utilising late binding. The pertinent code
is shown below.
The business rules are:
1. If an instance of Excel is already running then use the equivalent of
GetObject to obtain a reference to that instance, and, when finished, leave
that instance running.
2. If no instance of Excel is running then use the equivalent of
CreateObject to create a new instance, and, when finished, destroy that
instance.
The line 'm_object = Marshal.GetActiveObject(ProgID)' in Button1_Click is
the equivalent of GetObject.
The line m_object = Activator.CreateInstance(m_type)' in Button1_Click is
the equivalent of CreateObject.
The logic is that calling Marshal.GetActiveObject will throw an exception if
there are no existing instances of Excel.
If no instance of Excel is running then the code quite hapilly takes the
'create' path and all is well. The instance is created, and, when Button2 is
clicked, the instance is destroyed as expected. At the 'tell-tale' lines the
following is reported (again, as expected):
Microsoft.Office.Interop.Excel.ApplicationClass
12.0
My problem is that when an existing instance is present then the Type of the
object returned by the call to Marshal.GetActiveObject (or GetObject for
that matter), is System.__ComObject and the 2nd 'tell-tale' line fails
because the type of m_object and the type of m_type are mismatched.
What I am trying to find is a way of reliably 'casting' m_object
(System.__ComObject) to m_type
(Microsoft.Office.Interop.Excel.ApplicationClass) using late binding.
There is a little more background as to why late binding is required.
For the purposes of the application in question, we are unbale to dictate
which version of Office the customer shall have (if any). If they don't have
the component installed at all then that fact is easily determined and
access to the functionality that uses the component is supressed. If they do
have it then if their version is below a specific verison (11.0) then the
functionality that uses the component is also supressed. If they have
version 11.0 (Office 2003) or 12.0 (Office 2007) then all is fine except we
don't know this at compile time and therefore cannot bind against the
appropriate PIA.
If anyone else has successfully addressed this issue then I would
interested in hearing how it is done.
Content of Form1.vb
-------------------
Imports System.Reflection
Imports System.Runtime.InteropServices
Public Class Form1
Private Const ProgID As String = "Excel.Application"
Private m_type As Type = Nothing
Private m_object As Object = Nothing
Private m_created As Boolean = False
Private m_parameters As Object() = Nothing
Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs)
Handles Button1.Click
m_type = Type.GetTypeFromProgID(ProgID)
Try
m_object = Marshal.GetActiveObject(ProgID)
m_created = False
Catch
m_object = Activator.CreateInstance(m_type)
m_created = True
End Try
Console.WriteLine(m_object.GetType.ToString)
m_parameters = New Object() {}
Console.WriteLine(m_type.InvokeMember("Version",
BindingFlags.GetProperty, Nothing, m_object, m_parameters))
End Sub
Private Sub Button2_Click(ByVal sender As Object, ByVal e As EventArgs)
Handles Button2.Click
If m_created Then
m_parameters = New Object() {}
m_type.InvokeMember("Quit", BindingFlags.InvokeMethod, Nothing,
m_object, m_parameters)
End If
GCCom(m_object)
Console.WriteLine("Done")
End Sub
Private Sub GCCom(ByVal ParamArray objects As Object())
For Each _object As Object In objects
If _object IsNot Nothing Then
Dim _references As Integer = Integer.MaxValue
Do
_references = Marshal.ReleaseComObject(_object)
Loop While _references > 0
_object = Nothing
End If
Next
GC.Collect()
GC.WaitForPendingFinalizers()
End Sub
End Class