1

我有一个简单的 VB.Net 4 WinForms 应用程序,它可以生成基本代码。代码生成完美地创建了一个 DLL 程序集,但每次生成 DLL 时都需要以编程方式向 GAC 注册。必须注册它的原因是它是一个 COM 对象,在部署时会从 VB6 应用程序通过 CreateObject 调用。呃,我知道。

所有这些工作正常:DLL 生成、以编程方式注册和使用从 VB6 应用程序生成的 DLL。

问题是我的执行代码生成的应用程序只能在 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 中,但是我提出的实现都没有奏效。事实上,几乎所有这些最终都生成了然后在两个 AppDomain 中定义的程序集。我也试过做一个 ReflectionOnly 程序集加载,但为了注册函数,程序集必须在活动模式而不是反射模式下加载。

它抛出的实际错误是这个美妙的宝石:

Error Number: BC31019
Error Message: Unable to write to output file 'C:\Users\Me\AppData\Local\Temp\my.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: " & compileError.Line)
            sb.AppendLine("Error Number: " & compileError.ErrorNumber)
            sb.AppendLine("Error Message: " & 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 & "\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 & "\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: " & appDomain__1.FriendlyName)
    For Each loadedAssembly As Assembly In AppDomain.CurrentDomain.GetAssemblies()
        sb.AppendLine("- " & 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
4

3 回答 3

1

我们为自动安装 .Net 安装程序类(在当前应用程序域中加载和锁定 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

类文件注册:

''' <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
于 2011-11-08T00:52:26.290 回答
0

需要以编程方式向 GAC 注册。必须注册的原因是它是一个 COM 对象

您不需要将基于 .NET 的 COM 对象放入 GAC。您只需使用标准的 .NET regasm 实用程序注册它们,并且可以使用 System.Diagnostics.Proces 对象将 regasm 实用程序作为外部进程调用。

您将 GAC 用于您的 COM 对象还有其他原因吗?

于 2011-11-08T12:58:43.217 回答
0

使用 CompileAssemblyFromSource 时,您不仅在编译,而且还在将程序集加载到当前进程中,然后在卸载 appdomain 之前不释放它是正常的。

您是否考虑过只使用命令行编译器 vbc?它会独立于应用程序域执行编译,然后 DLL 只会在磁盘上创建,没有对它的引用会锁定它?

于 2011-11-08T17:28:34.987 回答