'Hot-loading' .dll's

'Hot-loading' .dll's

Anonymous
Not applicable
451 Views
2 Replies
Message 1 of 3

'Hot-loading' .dll's

Anonymous
Not applicable

Hello

 

I have this class which will hopefully let me essentially hot-load my .dll's, so that I can push changes to them while my coworkers are using them, without needing them to totally restart their applications to begin seeing the effects.

I had been using `.LoadFrom()`, but found it prefers using already-loaded versions of the files, whereas `.LoadFile()` is guaranteed to take the latest. The catch being that any dependencies the loaded file needs have to be resolved as well. But I've managed to do this by listening to the `AppDomain.AssemblyResolve` event, and it seems to work for any of the custom .dll's, which are all located in the same folder.

 

But we also have third-party dependencies (Autodesk Inventor 2022 .dll's) which my Visual Studio projects/solution don't generate automatically in my build folder (despite a couple I *have* had to include), and which I can't find anywhere on my machine. I've tried using `.Load()` for these, but it doesn't find anything (again, I guess, because they don't seem to be actually on my machine; the first one I consistently encounter is "Autodesk.iLogic.Core.resources.dll").

A 'FileNotFoundException' (not the one in the class) is thrown when the script instantiating the class runs within Inventor, so maybe my `AppDomain`(?) is the same as Inventor's, and by using `.LoadFile()` I'm obligated to resolve Inventor's dependencies too, but I really have no idea.

 

Has anyone encountered anything like this before?

Imports System.IO
Imports System.Reflection

Public NotInheritable Class LibInterface
    Private Const sBuildDir As String = "F:\Lib\Build"
    Private Const sReleaseRootDir As String = "F:\Lib\Releases"

    Private _oAssembly As System.Reflection.Assembly = Nothing
    Public Property oAssembly() As System.Reflection.Assembly
        Get
            Return _oAssembly
        End Get
        Protected Set(oVal As System.Reflection.Assembly)
            _oAssembly = oVal
        End Set
    End Property

    Private _oType As Type = Nothing
    Public Property oType() As Type
        Get
            Return _oType
        End Get
        Protected Set(oVal As Type)
            _oType = oVal
        End Set
    End Property


    Private Class Initialiser
        Public Sub New(
            ByVal oParent As LibInterface
        )
            AddHandler AppDomain.CurrentDomain.AssemblyResolve, AddressOf oParent.HandleResolve
        End Sub
        End Class

    Private _initialiser As New Initialiser(Me)

    Public Function HandleResolve(
        ByVal sender As Object _
        , ByVal args As ResolveEventArgs
    ) As System.Reflection.Assembly
        Return _GetAssembly(String.Split(args.Name, ",")(0)) + ".dll")
    End Function


    Private Function _GetAssembly(
        ByVal sFileName As String
    ) As System.Reflection.Assembly
        Return System.Reflection.Assembly.LoadFile(_SearchLastBuild(sFileName))
    End Function

    Private Function _SearchLastBuild(
        ByVal sFileName As String
    ) As String
        Dim sPathName = System.IO.Path.Combine(sBuildDir, sFileName)
        If Not File.Exists(sPathName) Then
            Throw New FileNotFoundException(".dll not found", sPathName)
        End If
        Dim oVersionInfo As FileVersionInfo = FileVersionInfo.GetVersionInfo(sPathName)
        Dim sReleaseDir As String = System.IO.Path.Combine(sReleaseRootDir, oVersionInfo.FileVersion)
        Dim sReleasePathName = System.IO.Path.Combine(sReleaseDir, sFileName)
        If File.Exists(sReleasePathName) Then
            Return sReleasePathName
        End If
        Directory.CreateDirectory(sReleaseDir)
        _CopyFilesRecursively(sBuildDir, sReleaseDir)
        Return sReleasePathName
    End Function

    Private Sub _CopyFilesRecursively(
        ByVal sSrcPath As String _
        , ByVal sDestPath As String
    )
        For Each sDirPath As String In Directory.GetDirectories(sSrcPath, "*", SearchOption.AllDirectories)
            Directory.CreateDirectory(sDirPath.Replace(sSrcPath, sDestPath))
        Next
        For Each sOldPath As String In Directory.GetFiles(sSrcPath, "*.*", SearchOption.AllDirectories)
            File.Copy(sOldPath, sOldPath.Replace(sSrcPath, sDestPath), True)
        Next
    End Sub

    Public Sub New(
        ByVal sFileName As String _
        , ByVal sType As String
    )
        _oAssembly = _GetAssembly(sFileName)
        _oType = _oAssembly.GetType(sType, False)
    End Sub
End Class

Any advice or guidance is much appreciated


Thanks!

0 Likes
452 Views
2 Replies
Replies (2)
Message 2 of 3

petr.meduna
Advocate
Advocate

I use this dumb solution for a while and works pretty well. There is a sub in every addin called LoadLibraries() which replace current dll with newer version. Dll file is placed on shared storage and if there is a difference in date of change of local and shared files, libraries will be copied. The trick is, that you can't directly copy dll because of its attach to Inventor, but you can rename it and copy new dll to application folder. Just add this function to any inventor event or trigger (except Activate() but you can use event OnReady or make Forms.Timer for checking versions in intervals).

 

Sub LoadLibraries()
            For Each Library As FileInfo In New DirectoryInfo("Z:\MyAddinsSource\App1").GetFiles("*.dll", SearchOption.TopDirectoryOnly)
                Try
                    Dim newPath As String = IO.Path.Combine(My.Application.Info.DirectoryPath, Library.Name)
                    Dim oldLib As String = ""
                    For i = 1 To m_inventorApplication.ApplicationAddIns.Count
                        If m_inventorApplication.ApplicationAddIns.Item(i).DisplayName = "MyAppDisplayName" Then
                            invApp = m_inventorApplication.ApplicationAddIns.Item(i)
                            oldLib = invApp.Location
                        End If
                    Next
                    serDate = IO.File.GetLastWriteTime(Library.FullName)
                    locDate = IO.File.GetLastWriteTime(oldLib)
                    If IO.File.Exists(IO.Path.Combine(My.Application.Info.DirectoryPath, "temp.dll")) Then
                       IO.File.Delete(IO.Path.Combine(My.Application.Info.DirectoryPath, "temp.dll"))
                    End If
                    If serDate <> locDate Then
                        My.Computer.FileSystem.RenameFile(oldLib, "temp.dll")
                        IO.File.Copy(Library.FullName, newPath, True)
                    Else
                        Exit Sub
                    End If
                Catch ex As Exception
                    Forms.MessageBox.Show("Error" & vbLf & ex.Message & vbLf & ex.StackTrace & vbLf & ex.Source)
                End Try
            Next
        End Sub

 

 

Message 3 of 3

Anonymous
Not applicable
So is this only replacing .dll's when the add-in is initialised? What I'm looking for, and what I have a kind-of-working solution for, is for rules attached to events, that reference some .dll's, to have the newest version each execution.

What I've done for now, that seems to be working, even if it's a little inconvenient, is I've consolidated all the .dll's into one single library, which I think allows me to no longer worry about resolving dependencies when using `Assembly.LoadFile()`, although I'm still fuzzy on how this works exactly.
0 Likes