Регистрация сгенерированного VB.Net Сборка DLL программным путем без блокировки DLL

#.net #vb.net #process #locking #regasm

#.net #vb.net #процесс #блокировка #regasm

Вопрос:

У меня есть простой VB.Net 4 Приложения WinForms, которые выполняют базовую генерацию кода. Генерация кода создает сборку DLL совершенно нормально, но каждый раз, когда генерируется DLL, ее необходимо программно регистрировать в GAC. Причина, по которой она должна быть зарегистрирована, заключается в том, что это COM-объект, который при развертывании вызывается через CreateObject из приложения VB6. Фу, я знаю.

Все это работает нормально: генерация DLL, программная регистрация и использование сгенерированной DLL из приложения VB6.

Проблема в том, что мое приложение, выполняющее генерацию кода, может сгенерировать DLL только один раз, прежде чем DLL будет заблокирована процессом, и нет возможности разблокировать ее без остановки EXE-файла и повторного запуска. Это, очевидно, не позволяет пользователю инструмента генерации кода вносить изменения и перекомпилировать DLL без перезапуска приложения.

Код, вызывающий блокировку, следующий (в строке, определяющей переменную «asm»):

 Public Function RegisterAssembly() As Boolean
    Dim success As Boolean = False

    Try
        Dim asm As [Assembly] = [Assembly].LoadFile(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "my.dll"))
        Dim regasm As New RegistrationServices()

        success = regasm.RegisterAssembly(asm, AssemblyRegistrationFlags.None)
    Catch ex As Exception
        success = False
        Throw ex
    End Try

    Return success
End Function
  

Я пытался перенести определение сборки в другой AppDomain, как я видел в ряде статей в Интернете, но ни одна из реализаций, которые я придумал, не сработала. Фактически, почти все из них заканчивались тем, что сгенерированная сборка определялась в ОБОИХ доменах приложений. Я также пытался выполнить загрузку сборки ReflectionOnly, но для того, чтобы регистр функционировал, сборка должна быть загружена в активном режиме, а не в режиме отражения.

Фактическая ошибка, которую она выдает, — это замечательный драгоценный камень:

 Error Number: BC31019
Error Message: Unable to write to output file 'C:UsersMeAppDataLocalTempmy.dll': The process cannot access the file because it is being used by another process. 
  

Если у кого-нибудь есть ответ для меня о том, что я могу сделать, чтобы исправить это, я был бы очень признателен! Как я уже сказал, это отлично работает при первой компиляции DLL, но последующие компиляции DLL завершаются неудачей, потому что сборка заблокирована процессом приложения.

Мое полное определение класса компилятора приведено ниже. Я оставил некоторые дополнительные материалы, которые я пробовал, извините за беспорядок:

 Imports System.CodeDom.Compiler
Imports System.Text
Imports System.IO
Imports System.Reflection
Imports System.Runtime.InteropServices

Public Class ExitCompiler

Private _errorMessageContents As String = ""
Private _errorCount As Integer = -1

Public ReadOnly Property ErrorMessageText As String
    Get
        Return _errorMessageContents
    End Get
End Property

Public ReadOnly Property ErrorCount As Integer
    Get
        Return _errorCount
    End Get
End Property

Public Function Compile(ByVal codeFileInfo As FileInfo) As Boolean
    Dim success As Boolean = False
    Dim codeContents As String = CodeReader.ReadAllContents(codeFileInfo.FullName)

    success = Compile(codeContents)

    Return success
End Function

Public Function Compile(ByVal codeContents As String) As Boolean
    _errorMessageContents = ""

    'asmAppDomain = AppDomain.CreateDomain("asmAppDomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath, AppDomain.CurrentDomain.ShadowCopyFiles)

    LogLoadedAssemblies(AppDomain.CurrentDomain)
    ' LogLoadedAssemblies(asmAppDomain)

    Try
        ' Remove output assemblies from previous compilations
        'RemoveAssembly()
    Catch uaEx As UnauthorizedAccessException
        Throw uaEx
    End Try

    Dim success As Boolean = False
    Dim outputFileName As String = Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "my.dll")
    Dim results As CompilerResults

    Dim codeProvider As New VBCodeProvider()
    Dim parameters As New CompilerParameters()

    parameters.TreatWarningsAsErrors = False
    parameters.CompilerOptions = "/optimize"
    parameters.TempFiles = New TempFileCollection(My.Computer.FileSystem.SpecialDirectories.Temp, False)
    parameters.OutputAssembly = outputFileName
    parameters.ReferencedAssemblies.Add("System.dll")
    parameters.ReferencedAssemblies.Add("System.Data.dll")
    parameters.ReferencedAssemblies.Add("System.Xml.dll")

    results = codeProvider.CompileAssemblyFromSource(parameters, codeContents)

    _errorCount = results.Errors.Count

    If _errorCount > 0 Then
        success = False

        'There were compiler errors
        Dim sb As New StringBuilder
        For Each compileError As CompilerError In results.Errors
            sb.AppendLine()
            sb.AppendLine("Line number: " amp; compileError.Line)
            sb.AppendLine("Error Number: " amp; compileError.ErrorNumber)
            sb.AppendLine("Error Message: " amp; compileError.ErrorText)
            sb.AppendLine()
        Next

        _errorMessageContents = sb.ToString()
    Else
        success = True

        ' Successful compile, now generate the TLB (Optional)
        'success = GenerateTypeLib()

        If success Then
            ' Type lib generated, now register with GAC
            Try
                success = RegisterAssembly()
            Catch ex As Exception
                success = False
            End Try
        End If
    End If

    Return success
End Function

'Private Function GenerateTypeLib() As Boolean
'    Dim success As Boolean = False

'    Try
'        Dim asm As [Assembly] = [Assembly].ReflectionOnlyLoadFrom(My.Computer.FileSystem.SpecialDirectories.Temp amp; "my.dll")
'        Dim converter As New TypeLibConverter()
'        Dim eventHandler As New ConversionEventHandler()

'        Dim typeLib As UCOMICreateITypeLib = CType(converter.ConvertAssemblyToTypeLib(asm, My.Computer.FileSystem.SpecialDirectories.Temp amp; "my.tlb", 0, eventHandler), UCOMICreateITypeLib)
'        typeLib.SaveAllChanges()

'        success = True
'    Catch ex As Exception
'        success = False
'        Throw ex
'    End Try

'    Return success
'End Function

Public Function RegisterAssembly() As Boolean
    Dim success As Boolean = False

    Try
        Dim asm As [Assembly] = [Assembly].LoadFile(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "my.dll"))
        Dim regasm As New RegistrationServices()

        success = regasm.RegisterAssembly(asm, AssemblyRegistrationFlags.None)
    Catch ex As Exception
        success = False
        Throw ex
    End Try

    Return success
End Function

Public Sub RemoveAssembly()
    'AppDomain.Unload(asmAppDomain)

    File.Delete(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "my.dll"))
    'File.Delete(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Temp, "my.tlb"))
End Sub

Private Shared Sub LogLoadedAssemblies(appDomain__1 As AppDomain)
    Dim sb As New StringBuilder

    sb.AppendLine("Loaded assemblies in appdomain: " amp; appDomain__1.FriendlyName)
    For Each loadedAssembly As Assembly In AppDomain.CurrentDomain.GetAssemblies()
        sb.AppendLine("- " amp; loadedAssembly.GetName().Name)
    Next

    MessageBox.Show(sb.ToString())
End Sub

'Private Shared Function CurrentDomain_ReflectionOnlyAssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
'    Return System.Reflection.Assembly.ReflectionOnlyLoad(args.Name)
'End Function
End Class
  

Комментарии:

1. Вы вырыли себе довольно глубокую яму. Избавьтесь от всего этого. Затем Проект Свойства, вкладка Компиляция. Установите флажок «Зарегистрироваться для COM-взаимодействия».

2. @HansPassant Я думаю, что он динамически генерирует сборку во время выполнения, поэтому нет проекта, в котором можно было бы установить эту опцию. В качестве альтернативы он может делать то, что делает этот флажок, то есть использовать RegAsm.

Ответ №1:

Способ, которым мы обработали это для автоматической установки.Классы Net installer, у которых возникает та же проблема с загрузкой и блокировкой DLL в текущем домене приложения, должны выполнить регистрацию в новом домене приложения.

Однако, прежде чем я перейду к этому, вы могли бы рассмотреть еще один короткий путь — использовать процесс для вызова regsvr32 для автоматической регистрации в DLL.

Следующий код специфичен для нашего решения, но должен дать вам представление:

 ''' <summary>
''' Register the specified file, using the mechanism specified in the .Registration property
''' </summary>
''' <param name="sFileName"></param>
''' <remarks></remarks>
Private Sub Register(ByVal sFileName As String)
    ' Exceptions are ignored

    Dim oDomain As AppDomain = Nothing
    Try
        Dim oSetup As New System.AppDomainSetup()

        With oSetup
            .ApplicationBase = ApplicationConfiguration.AppRoot
            .ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile
            .LoaderOptimization = LoaderOptimization.SingleDomain
            .DisallowBindingRedirects = False
            .DisallowCodeDownload = True
        End With

        ' Launch the application
        oDomain = AppDomain.CreateDomain("AutoUpdater", AppDomain.CurrentDomain.Evidence, oSetup)

        oDomain.CreateInstance(GetType(FileRegistration).Assembly.FullName, GetType(FileRegistration).ToString, True, Reflection.BindingFlags.Default, Nothing, New Object() {sFileName}, Nothing, Nothing, AppDomain.CurrentDomain.Evidence)
    Catch theException As Exception
        ' Suppress errors to the end user
        Call ReportError(theException, True)
    Finally
        If oDomain IsNot Nothing Then
            AppDomain.Unload(oDomain)
        End If
    End Try
End Sub
  

Class FileRegistration:

 ''' <summary>
''' This class is used to register .Net installer files. This functionality is contained in its own class because it is 
''' launched in a separate appdomain in order to prevent loading the files into the host appdomain (which locks 
''' them and makes them unavailable until the host application is shutdown).
''' </summary>
''' <remarks></remarks>
Public Class FileRegistration
    Inherits MarshalByRefObject

    Public Sub New(ByVal sFileName As String)
        ' Exceptions are ignored
        Try
            Using oInstaller As New System.Configuration.Install.AssemblyInstaller(sFileName, New String() {"/logfile=autoupdate.log"})
                Dim htSavedState As New Collections.Hashtable()

                oInstaller.Install(htSavedState)
                oInstaller.Commit(htSavedState)
            End Using
        Catch theException As Exception
            ' Suppress errors to the end user
            Call ReportError(theException, True)
        End Try
    End Sub
  

Комментарии:

1. Хм, я пытался использовать процесс для прямого вызова regasm для сгенерированной сборки, а также ваше опубликованное решение выше, и оба дополнительных метода все еще каким-то образом блокируют сборку.

2. Вы перенесли свой метод LoadFile из основного кода в код, который запускается с новым AppDomain? Если вы этого не сделаете, ваша DLL все равно будет загружена в ваш основной домен приложения.

3. На самом деле изменение ваших методов a touch, похоже, работает и не блокирует файл, но после того, как сборка сгенерирована и зарегистрирована, приложение создает экземпляр сборки через CreateObject, который в конечном итоге снова блокирует DLL. Похоже, что файл блокируется в два этапа: 1) регистрация сборки (другим способом, который я пробовал) и 2) последующее создание экземпляра с помощью CreateObject. Шаг 1, похоже, решаем, нужно только разобраться с шагом 2.

4. Использование CreateObject определенно заблокирует DLL, если вы не делаете это в отдельном домене приложения, который позже выгружается.

5. Я изолировал CreateObject в его собственном AppDomain и выгрузил AppDomain сразу после использования объекта, но dll по-прежнему заблокирована. Есть идеи?

Ответ №2:

необходимо программно зарегистрировать в GAC. Причина, по которой она должна быть зарегистрирована, заключается в том, что это COM-объект

Вам не нужно помещать вас .Сетевые COM-объекты в GAC. Вам просто нужно зарегистрировать их с помощью стандартной утилиты .NET regasm, и вы можете вызвать утилиту regasm как внешний процесс, используя систему.Диагностика.Обрабатывается объект.

Есть ли другая причина, по которой вы используете GAC для своего COM-объекта?

Комментарии:

1. Я просто искал «более чистый» способ регистрации сборки «на лету», отличный от прямого вызова regasm. Я знаю, что на самом деле это не так уж плохо, поскольку я делал это много раз раньше, я просто изучал этот вариант, и, похоже, он должен сработать, если блокировки сборки можно избежать.

Ответ №3:

При использовании CompileAssemblyFromSource вы не только компилируете, но и загружаете сборку в текущий процесс, который затем будет нормально не выпускать, пока не будет выгружен appdomain.

Рассматривали ли вы возможность просто использовать компиляторы командной строки vbc? Это выполняло бы компиляцию независимо от домена приложения, а затем DLL создавалась бы только на диске, никакие ссылки на нее не блокировали бы ее?

Комментарии:

1. Вызов CompileAssemblyFromSource не блокирует сборку, я проверял это множество раз. Если я не вызываю метод RegisterAssembly, я могу регенерировать сборку столько раз, сколько захочу, но тогда вновь сгенерированная сборка не будет использована позже при вызове VB6 CreateObject, поскольку она все еще использует ранее зарегистрированную версию. Она не блокируется до [Сборки]. LoadFile вызывается внутри RegisterAssembly().