Friday, November 21, 2008
Serialization and File Not Found for AutoDeploys

I know there are a lot of developers thinking of the power of migrating from VS6 to VS7 by making bolt-on applets that are themselves AutoDeploys that use Remoting. It seems perfect, just make a COM Callable wrapper on a LoadFrom "loader" dll, the COM app passes the criteria (including server, assembly and classname) and the generic loader pulls the Applet down from an IIS location, then the applet main form loads and passes it's handle to the COM app, so that the COM app can run a SetOwner and appear to be simply running another aspect of itself (of course the COM app was compiled against a type library of the Applet so that full cross program communication can take place).

With this you can pick apart your big VS6 Apps a bit at a time till one day all you have is a bunch of VS7 applets autodeploying to desktops as-needed and you can throw the COM shell away.

It's a great dream and I promise you from my release experience at drugstore.com that putting VB6 and VB7 together seemlessly can be done (though having a Microsoft trainer come to the office and say "I haven't seen anyone on campus able to get all of that working in one app" did make me scratch my head; Glad he showed up after I'd finished it <g>). The thing is that getting it right takes a bit more effort than the high-level articles show.

When doing deserializations of custom objects there are times when the Fusion Assembly Manager (used to validate and bind assemblies at runtime) will seem to "lose" the object types and throw File Not Found exceptions. This becomes extrememly annoying in the case of AutoDeploys and Remoted objects.

As I read it, the root cause is that Fusion resolves the type at the current AppDomain level, and that the physical file that contains the definitions of the types must be located in the same folder as the parent assembly for that AppDomain for Fusion to find them. In the case of an Interop assembly that AppDomain's physical location is the App.Path of the COM App, however for an AutoDeploy assembly the files will not be downloaded to that location, instead they will be downloaded to a special dynamically named cache location.

At VSLive in February Rockford Lhotka gave a workaround that includes iterating through the types of the running Application ( http://www.fawcette.com/reports/vslive/021202/vsnet/default.asp ) However, his example didn't completely work on an Interop project, esecially when working with Interop of AutoDeployed assemblies (in that you want the COM app to use a wrapped Loader assembly that will pull down the working assembly, create an instance and then pass the instance back to the COM app for control). As stated, in these cases the needed files are not in the Current AppDomain location so even that "catch" will probably fail. Plus, why add the net when you can prevent the fall up front?

Here is generic code that seems to work a bit better for me (in fact, except for the exception handling this is about all the code that will most ever be needed for any Interop Loader project) When the Loader is called it creates a new AppDomain, sets the BaseDirectory for that AppDomain to the location of the AutoDeploy source files and then the Applet lives within the new domain so it's reference files will be used for validations:

Public Function GetApplet(ByVal ServerName As String, ByVal FolderName As String, _ 
   ByVal AssemblyName As String, ByVal ClassName As String) As Object 

   'this is a paranoid level way, making 
   'sure that the type is in the remote assembly. 
   'IF you trust yourself well, you don't 
   'have to to the LoadFrom and loop, 
   'you could just skip ahead to the 
   'CreateInstanceAndUnWrap line and return the objret. 

   Dim ad As AppDomain 
   ad = System.AppDomain.CreateDomain("testdomain", Nothing, _ 
      "HTTP://" & ServerName & "/" & FolderName & "/", _ 
      Nothing, False) 

   Dim t As Type 
   Dim arTypes As Type() 
   Dim a As [Assembly] = [Assembly].LoadFrom("HTTP://" & _
      ServerName & "/" & FolderName & "/" & _ 
      AssemblyName & ".dll")

   arTypes = a.GetExportedTypes() 
   
   For Each t In arTypes 
      If t.Name.ToString = ClassName Then 
         MsgBox(t.Assembly.GetName.Name.ToString) 
         
         Dim obj As Object = _
            ad.CreateInstanceAndUnwrap(t.Assembly.GetName.Name, _  
            t.FullName.ToString) 
         Return obj 
      
         Exit For 

      End If 

   Next 

End Function

And for a non-interop, where the Loader is itself a .net program that you don't want to have hard coded refs to the applets, and you assume that all applets called by this function will be GUIs "run" from a main form:

Public sub GetApplet(ByVal ServerName As String, ByVal FolderName As String, _ 
   ByVal AssemblyName As String, ByVal ClassName As String, _
   Optional ByVal StartModal as Boolean = False) 

   ''This is more trusting 
   Dim ad As AppDomain 
   ad = System.AppDomain.CreateDomain("testdomain", Nothing, _ 
      "HTTP://" & ServerName & "/" & FolderName & "/", _ 
      Nothing, False) 

   Dim obj As Object = _
      ad.CreateInstanceAndUnwrap(AssemblyName, AssemblyName & "." & ClassName) 

   'obj comes as a MarshallByRefObject type, convert it 
   'assume Applets are GUIs and the classname 
   'is the name of a main form, cast to form and show 

   Dim f As Windows.Forms.Form 
   f = CType(obj, Windows.Forms.Form) 
   If StartModal = False then 
      f.Show() 
   Else 
      f.ShowDialog() 
   End if 

End Sub

A VB6 app using the first option is set up simply with this pseudocode:

 

---Declaration Section--- 
Private WithEvents testapp As AppletNameSpaceNameFOO.AppletClassNameFOO 
Private m_TestAppIsLoaded as boolean 

---Load button code --- 
sub button_click
 
   Dim obj As New NewLoader.NewLoader 
   Set testapp = obj.GetApplet("myserver", _
      "myserverfolder", "AppletNameSpaceNameFOO", _
      "AppletClassNameFOO") 
   
   testapp.ShowByCustomShowMethod 
   MsgBox testapp.CustomPropertyReturningAString 

end sub

I'm of a mind that, on instantiation, all such interop applets should raise an AppletLoaded event that passes the handle of the newly instantiated form so that the VB6 app can

1) set the boolean flag for later interrogation

2) Immediately use SetOwner to link the applet gui to the COM App gui (or SetParent in the case of a .net form being displayed as a control hosted in a frame on a VB6 App form).

Also, of course, along with Applet-specific interfaces all such applets should raise an AppletClosing event so that the flag can be unset.

Additionally, there will be a short but noticable lag between the time you first load an applet into a custom AppDomain and the time that applet is available for use so you might want to consider pre-loading the main components of such an applet during the "splashscreen" of your main program (subsequent instances load and display very quickly). If there is a chance that the applets will be called more than once, then the Loader should handle management of the new AppDomains, using the same custom Appdomain for multiple instances of the same applet, this will help the apparent speed and help keep the memory usage lower.

Robert Smith
Kirkland, WA

added September 2002


Print  

pagecomment
  Add Comment



Submit Comment
  View Ratings
50.00%0
40.00%0
30.00%0
20.00%0
10.00%0

Number of Comments 0 , Average of Ratings
  View Comments
No comment.


Privacy Statement  |  Terms Of Use
Copyright 2008 by Robert C. Smith