Chapter Two: Loading Add-ins

Here is the second chapter in my integration journey. If you have not looked at the first blog post, it would be a good idea to look there first, because that post will explain the application I am working with and my idea behind it.

First, let’s look at a few things that have changed since my first app. If you open the attached .zip file, you will notice that I have separated the ContactClass and ContactObj classes into their own files. The largest change to this app by far, however, is the way the script (which I will refer to as an Add-In) loads and runs. You may recall that the app formerly had the script file right in the project. For this app revision, the script runs entirely from a .dll file (VB class library). I can do this now, because the main app no longer has to reference the script file. The script file must reference the main app, but not the other way around. Let me show you how I did this:

IAddIn.vb:

'Defines an interface with which to connect to the addin.

Public Interface IAddIn

    Sub Connect(ByRef TheContact As ContactClass)

End Interface

 

      ContactClass.vb:

 

'Called by the ContactApp class's ContactApp_Load() method; asks if the user wants to run the app in script mode. The method then stores the user's response in a boolean variable, and, if the user's answer was yes, the method calls the LoadAddin method and passes in the name of the addin file and the name of the addin class.

    Public Sub AppLoad()

        UpdateDataset()

        Dim MBResult = MsgBox("Would you like to run this program from Script?" + Environment.NewLine + "(If Yes, the Interface will be hidden.)", MsgBoxStyle.YesNo)

        Select Case MBResult

            Case MsgBoxResult.Yes

                ScriptBool = True

                LoadAddin("AddIns/ContactAuto.dll", "ContactAuto.ContactAuto")

                ContactApp.Close()

            Case MsgBoxResult.No

                ScriptBool = False

        End Select

    End Sub

 

    'Loads the specified addin and runs its connect method.

    Private Function LoadAddin(ByVal addinPath As String, ByVal startUpClass As String) As IAddIn

        Try

            Dim assemStream() As Byte = Nothing, pdbStream() As Byte = Nothing

            Using fs As New FileStream(addinPath, FileMode.Open)

                assemStream = New Byte(fs.Length) {}

                fs.Read(assemStream, 0, CType(fs.Length, Integer))

            End Using

 

            Dim pdbPath As String = Path.Combine(Path.GetDirectoryName(addinPath), Path.GetFileNameWithoutExtension(addinPath) + ".pdb")

            Dim hasPdb As Boolean = False

            If File.Exists(pdbPath) Then

                Using fs As New FileStream(pdbPath, FileMode.Open)

                    pdbStream = New Byte(fs.Length) {}

                    fs.Read(pdbStream, 0, CType(fs.Length, Integer))

                    hasPdb = True

                End Using

 

            Else

                Debug.WriteLine("Cannot find the pdb file: " + pdbPath)

            End If

            Dim assem As System.Reflection.Assembly

            If Not hasPdb Then

                assem = Assembly.Load(assemStream)

            Else

                assem = Assembly.Load(assemStream, pdbStream)

            End If

 

            Dim t As Type = assem.GetType(startUpClass)

 

            Dim addin As IAddIn = CType((System.Activator.CreateInstance(t)), IAddIn)

            addin.Connect(Me)

            Return addin

        Catch ex As Exception

            ' Do not let error of this Add-in affect others

            System.Diagnostics.Trace.WriteLine(ex.ToString())

            System.Diagnostics.Debug.Assert(False)

 

            Return Nothing

        End Try

    End Function

Direct your attention first up to the IAddIn interface. You can see that all it references is the Connect method in ContactAuto. This interface provides a sort of bridge between the ContactAuto class library and the main application. Now look at the AppLoad method. Notice that it calls a method by the name of LoadAddIn and specifies two parameters. This LoadAddIn method is a part of a VSTA design-time-only integration and is the core to the whole scripting process in this app. All that the AppLoad method needs to do is call the LoadAddIn method and pass it the path to the .dll file (class library) and the class in the library in which to start. Once the method loads and parses the .dll file, it will call the Connect method in the IAddIn interface. The app does not need to reference the script library, it just knows how to load a certain file out of a certain directory, and call the Connect method on it through the IAddIn interface. The Add-In must still hold a reference to the main app, so it can call the app’s methods and handle the app’s events, but the Add-In can be separate from the app. The app does not have to know anything about it other than where it is, one of its classes, and that it has a Connect method. Here is a simple diagram of how ContactClass, IAddIn, and ContactAuto interact:

ContactAuto references ContactClass, but ContactClass does not reference ContactAuto. If ContactAuto exists, ContactClass calls its Connect method through the IAddIn interface.

This design allows the Add-In to be free of the application, and allows a programmer to alter the Add-In without altering the main program. All any new Add-Ins would have to do is reside in the right folder, and have a Connect method and a class that is the same name as the one specified in the start-up class parameter of LoadAddIn. This is one way of discovering and loading Add-Ins. I will be showing you a different, even more “Loosely Coupled” way in my next post.


Posted Jun 29 2009, 10:34 AM by BillL
Filed under: , ,
Copyright Summit Software Company, 2008. All rights reserved.