#.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().