Previous | Index | Next 

[HOWTO] Optimize startup time in N-tier applications

To closely reproduce the behavior of the original VB6 application in all cases, converted VB.NET apps must learn about external components and usercontrols that were also migrated. More specifically, this step is necessary

  • to ensure that CreateObject methods work correctly even if the original COM component has been converted to a .NET DLL
  • to ensure that Controls.Add methods can dynamically add a usercontrol that was converted to a .NET usercontrol.

To meet such requirements, when a converted VB.NET starts up, VB Migration Partner performs a quick scans of all the .NET DLLs in the application’s install folder, loads each assembly in memory, and takes note of all the classes marked with the VB6Object attribute. (These are the classes that were originated by the migration process.)

Notice that this parsing step is truly necessary only if the component or usercontrol is loaded dynamically – by means of CreateObject or Controls.Add methods. If the .NET DLL is directly referenced by the migrated application, all the classes it contains are automatically recognized by the migrated VB.NET program.

The parsing process is fast and effective, and adds no noticeable overhead to the startup process. However, if the application folder contains dozens or hundreds DLLs – or if you are absolutely certain that your application never requires external components or controls that are loaded dynamically – you can disable this process by simply setting the VB6Config.ParseAssembliesAtStartup property to False before initializing the support library. (This property is available in VB Migration Partner 1.10.03 and later versions.)

The best place to initialize this property is in the EnsureVB6LibraryInitialization method, that you find inside the VisualBasic6.Support.vb file in the My Project folder. In practice you just need to uncomment a statement inside the method:

        Public Sub EnsureVB6LibraryInitialization() 
            ' Uncomment next statement to skip assembly exploration at startup
            CodeArchitects.VB6Library.VB6Config.ParseAssembliesAtStartup  = False 
            CodeArchitects.VB6Library.InitializeLibrary(Application.OpenForms, _
               My.Application.Info, APP6_HELPFILE, APP6_HELPCONTEXTID, APP6_UNIQUEID)
        End Sub

If your application never instantiates external components or controls dynamically, this is all you need to do and can forget about the matter. If your application does loads external components dynamically, please continue reading.


Typically, VB projects that load external components dynamically are N-tier applications that load a specific data, business, or user interface object depending on configuration settings and end user actions. In cases like this, the ProgID of the component to be instantiated is formed by concatenating variable strings, as in this example:

        Dim dbName As String, tableName As String, progId As String
        dbName = GetDatabaseName()             ' e.g. "SqlServer" or "Oracle"
        tableName = GetTableName()             ' e.g. "Customers" or "Orders"
        ' eval the name of the business object that deals with current table
        progId = dbName & "BO." & tableName    ' e.g. "SqlServerBO.Customers"
        ' create the business object, communicate via the IBusinessObject interface
        Dim busObj As IBusinessObject
        Set busObj = CreateObject(progId)
        busObj.ReadData()

If you have disabled the parsing step at application launch, the CreateObject method is bound to fail in the converted VB.NET application. You avoid the exception by implementing an alternative technique to external assembly parsing. Basically, there are two ways to implement such a technique.

In the first approach, you manually parse one or more assemblies as soon as you have enough information about which external components are surely needed and which aren’t. In our sample code you might take this step when you know that the application is using SqlServer as its primary database engine, which in turn means that your app uses only objects whose ProgID is in the form “SqlServerBO.*”. 

Presumably, all the objects with this ProgID were originally stored in one COM DLL (e.g. SqlSvrBO.DLL) and were converted into a single .NET DLL, for example CodeArchitects. BusinessObjects.SqlServer.DLL, therefore all you need to do is parsing that specific DLL before a CreateObject method is attempted. Forcing the parsing of a DLL can be easily done with the VB6Utils.ParseAssembly method:

        ' if no rooted path, filename is taken relative to application folder
        VB6Utils.ParseAssembly("CodeArchitects.BusinessObjects.SqlServer.dll")

You can also parse multiple assemblies in a folder (or a directory tree), by means of the VB6Utils.ParseAssembliesInFolder method:

        ' 2nd optional argument must be True for exploring directory trees
        ' 3rd optional argument is a search pattern 
        VB6Utils.ParseAssembliesInFolder("BusinessObjects", True, "SqlServer*.dll")

The second approach is a bit more complex, but offers even more granularity and allows you to load and parse individual DLLs just one instant before they are needed.

In this second approach you use a custom method defined in the VB.NET project to “shadow” the CreateObject6 method defined in VB Migration Partner support library. You then have the opportunity to analyze the ProgID of the object which is about to be instantiated and correctly parse the DLL that contains that object. (For this mechanism to work, it is essential that you use a naming convention that allows you to deduce the DLL name from the ProgId.)

        Public Function CreateObject6(ByVal progId As String) As Object
            ' extract the original typelib name (e.g. "SqlServerBO.Customers" => "SqlServerBO") 
            Dim typeLibName As String = progId.Split(".")(0) 
            ' in your VB6 naming convention, the last two chars specify the object kind 
            ' (example: BO=business, DO=data, UI=userinterface) 
            Dim objType As String = "" 
            Select Case typeLibName.Substring(typeLibName.Length - 2).ToUpper() 
                Case "BO" : objType = "BusinessObject" 
                Case "DO" : objType = "DataObject" 
                Case "UI" : objType = "UserObject" 
            End Select 
            ' remaining portion is the database name  (e.g. "SqlServer") 
            Dim dbName As String = typeLibName.Substring(0, typeLibName.Length - 2)
            ' form the complete name of the .NET DLL, according to new .NET naming conventions 
            Dim dllName As String = String.Format("CodeArchitects.{0}.{1}", objType, dbName) 
            VB6Utils.ParseAssembly(dllName)
            ' finally delegate to VB Migration Partner's CreateObject6 method 
            Return CodeArchitects.VB6Library.CreateObject6(progId) 
        End Function

Notice that there is no overhead in invoking the VB6Utils.ParseAssembly method on a DLL that has been already parsed, because the method simply ignores repeated calls for a given DLL.

 

Previous | Index | Next