Late Binding of COM components

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
 
D

Duracel

Hi Stephany,

I think if you reference typelib 11 then you are compatible with 12 by
default but also you should fail to bind to an earlier version if that is
the only version on their system. At least our software references 9, won't
work with '97 but is compatible with 2K, XP, 2003 and 2007.




Robin
 
S

Stephany Young

Thanks for the response Robin.

I don't think I follow fully what you're trying to say but I think I get the
gist of it.

Unfortunately it doesn't solve the issue of casting a variable of type
System.__ComObject to a variable of type
Microsoft.Office.Interop.Excel.ApplicationClass.

This is the main point of my post because of the requirement (as stipuilated
in the business rules) to leave an existing intact when we have finished
with it.
 
R

Robin Tucker

Steph,

You create a reference to the application, it's a completely different
instance. You create and destroy it at will. Do an experiment to convince
yourself. Open Excel. Now run a little program that does this:

Dim msExcel As New Excel.Application

msExcel.Visible = True

Marshal.ReleaseComObject(msExcel)



(make sure you reference and import references for Excel)

Stick a breakpoint on Marshal.Release.... Look at the taskbar, there are 2
instances of excel. One you started manually and one started
programmatically as above. Now execute the Marshal... line of code. There
should now only be 1, the one you originally opened.




Robin
 
S

Stephany Young

It's Stephany, Robin, not Steph!

The (immutable) requirement is that if an instance already exists then that
is used and left intact when finished. In some cases we are dealing with a
sheet that the user already has open.

Therefore we must use GetObject (or the equivalent), but if that fails then
we use CreateObject (or the equivalent.

The issue is not how do I get the existing instance or how do I get a new
instance.

The issue is how do I cast a variable of type System.__ComObject to a
variable of type Excel.Application.

Note that CreateObject (or the equivalent) returns an object where the
underlying type is Excel.Application whereas GetObject (or the equivalent)
returns an object where the underlying type is System.__ComObject.
 
S

Stephany Young

Just to clarify. It's the grammar of your reply that I don't follow. It just
doesn't make any sense.
 
S

Stephany Young

Isn't it amazing what a bit more research reveals.

The worst aspect is that you can research a subject for weeks and get
nowhere, but a matter of hours after you post a question in here you stumble
across the piece of information that you were missing. I'm going to call it
Stephany's Colollary on Murphy's Law :)

The upshot is that the Office 11 (Office 2003) PIA's are forward compatible
with Office 12 (Office 2007), but not backward compatible with Office 10
(Office XP). Unfortunately, to install the Office 11 PIA's on your
development machine you need to have Office 2003 installed which is not
practical if you have Office 2007 installed.

A workaround for this is to collect up the Office 11 PIA's and place them
(in a folder) on your development machine and add references to these PIA's
rather than the Office 12 PIA's.

Once you've done this you don't have to worry about late-binding anymore.

Obviously you can't use any Office 12-specific features is your code.

Now the casting of the object of type System.__ComObject returned by the
GetObject function to an object of type Excel.Application works a treat, and
therefore the business rules can easily be honoured.

The amended code is shown below:

Imports Microsoft.Office.Interop
Imports System.Reflection
Imports System.Runtime.InteropServices

Public Class Form1

Private Const ProgID As String = "Excel.Application"

Private m_type As Type
Private m_object As Excel.Application
Private m_created As Boolean

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

m_type = Type.GetTypeFromProgID(ProgID)

m_object = Nothing

m_created = False

Try
m_object = CType(GetObject(, ProgID), Excel.Application)
Catch
Try
m_object = CType(CreateObject(ProgID), Excel.Application)
m_created = True
Catch
m_object = Nothing
End Try
End Try

Dim _message As String

If m_object Is Nothing Then
_message = String.Format("Failed to instantiate compatible {0}
object", ProgID)
Else
Dim _created As String = "existing"
If m_created Then _created = "new"
_message = String.Format("Instantiated {0} compatible {1} object -
Version {2}", _created, ProgID, m_object.Version)
End If

Console.WriteLine(_message)

End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As
System.EventArgs) Handles Button2.Click

If m_object Is Nothing Then
Console.WriteLine("m_object Is Nothing - No action")
Else
If m_created Then m_object.Quit()
GCCom(m_object)
Dim _message As String
If m_created Then
_message = String.Format("Destroyed new {0} object", ProgID)
Else
_message = String.Format("Destroyed reference to existing {0}
object", ProgID)
End If
Console.WriteLine(_message)
End If

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top