Late Binding of COM components

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Stephany Young

    #1

    Late Binding of COM components

    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.GetActi veObject(ProgID )' in Button1_Click is
    the equivalent of GetObject.

    The line m_object = Activator.Creat eInstance(m_typ e)' in Button1_Click is
    the equivalent of CreateObject.

    The logic is that calling Marshal.GetActi veObject 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.Offic e.Interop.Excel .ApplicationCla ss
    12.0

    My problem is that when an existing instance is present then the Type of the
    object returned by the call to Marshal.GetActi veObject (or GetObject for
    that matter), is System.__ComObj ect 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.__ComOb ject) to m_type
    (Microsoft.Offi ce.Interop.Exce l.ApplicationCl ass) 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.Reflecti on
    Imports System.Runtime. InteropServices

    Public Class Form1

    Private Const ProgID As String = "Excel.Applicat ion"

    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(B yVal sender As Object, ByVal e As EventArgs)
    Handles Button1.Click

    m_type = Type.GetTypeFro mProgID(ProgID)

    Try
    m_object = Marshal.GetActi veObject(ProgID )
    m_created = False
    Catch
    m_object = Activator.Creat eInstance(m_typ e)
    m_created = True
    End Try

    Console.WriteLi ne(m_object.Get Type.ToString)

    m_parameters = New Object() {}

    Console.WriteLi ne(m_type.Invok eMember("Versio n",
    BindingFlags.Ge tProperty, Nothing, m_object, m_parameters))

    End Sub

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

    If m_created Then
    m_parameters = New Object() {}
    m_type.InvokeMe mber("Quit", BindingFlags.In vokeMethod, Nothing,
    m_object, m_parameters)
    End If

    GCCom(m_object)

    Console.WriteLi ne("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.MaxValu e
    Do
    _references = Marshal.Release ComObject(_obje ct)
    Loop While _references 0
    _object = Nothing
    End If
    Next

    GC.Collect()

    GC.WaitForPendi ngFinalizers()

    End Sub

    End Class

  • Duracel

    #2
    Re: Late Binding of COM components


    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


    Comment

    • Stephany Young

      #3
      Re: Late Binding of COM components

      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.__ComObj ect to a variable of type
      Microsoft.Offic e.Interop.Excel .ApplicationCla ss.

      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.



      "Duracel" <duracel@nthotm ail.comwrote in message
      news:f2uoph$78s $1$8302bc10@new s.demon.co.uk.. .
      >
      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
      >

      Comment

      • Robin Tucker

        #4
        Re: Late Binding of COM components


        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.Applicati on

        msExcel.Visible = True

        Marshal.Release ComObject(msExc el)



        (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
        programmaticall y as above. Now execute the Marshal... line of code. There
        should now only be 1, the one you originally opened.




        Robin


        Comment

        • Stephany Young

          #5
          Re: Late Binding of COM components

          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.__ComObj ect to a
          variable of type Excel.Applicati on.

          Note that CreateObject (or the equivalent) returns an object where the
          underlying type is Excel.Applicati on whereas GetObject (or the equivalent)
          returns an object where the underlying type is System.__ComObj ect.


          "Robin Tucker" <rtgroups@hotma il.comwrote in message
          news:f2uug0$6b$ 1$8300dec7@news .demon.co.uk...
          >
          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.Applicati on
          >
          msExcel.Visible = True
          >
          Marshal.Release ComObject(msExc el)
          >
          >
          >
          (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
          programmaticall y as above. Now execute the Marshal... line of code.
          There should now only be 1, the one you originally opened.
          >
          >
          >
          >
          Robin
          >

          Comment

          • Stephany Young

            #6
            Re: Late Binding of COM components

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


            "Duracel" <duracel@nthotm ail.comwrote in message
            news:f2uoph$78s $1$8302bc10@new s.demon.co.uk.. .
            >
            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
            >

            Comment

            • Stephany Young

              #7
              Re: Late Binding of COM components

              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.__ComObj ect returned by the
              GetObject function to an object of type Excel.Applicati on works a treat, and
              therefore the business rules can easily be honoured.

              The amended code is shown below:

              Imports Microsoft.Offic e.Interop
              Imports System.Reflecti on
              Imports System.Runtime. InteropServices

              Public Class Form1

              Private Const ProgID As String = "Excel.Applicat ion"

              Private m_type As Type
              Private m_object As Excel.Applicati on
              Private m_created As Boolean

              Private Sub Button1_Click(B yVal sender As System.Object, ByVal e As
              System.EventArg s) Handles Button1.Click

              m_type = Type.GetTypeFro mProgID(ProgID)

              m_object = Nothing

              m_created = False

              Try
              m_object = CType(GetObject (, ProgID), Excel.Applicati on)
              Catch
              Try
              m_object = CType(CreateObj ect(ProgID), Excel.Applicati on)
              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.Versio n)
              End If

              Console.WriteLi ne(_message)

              End Sub

              Private Sub Button2_Click(B yVal sender As System.Object, ByVal e As
              System.EventArg s) Handles Button2.Click

              If m_object Is Nothing Then
              Console.WriteLi ne("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.WriteLi ne(_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.MaxValu e
              Do
              _references = Marshal.Release ComObject(_obje ct)
              Loop While _references 0
              _object = Nothing
              End If
              Next

              GC.Collect()

              GC.WaitForPendi ngFinalizers()

              End Sub

              End Class

              Comment

              Working...