2011-11-08 41 views
1

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

所有这一切工作正常:该DLL的生成,编程注册和使用从VB6应用程序生成的DLL。

的问题是,我的应用程序,做DLL是没有办法解开它不停止EXE并重新开始它锁定在处理之前的代码生成可以一次生成的DLL。这显然会阻止代码生成工具的用户在不重新启动应用程序的情况下进行更改并重新编译该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的BOTH定义生成的程序集结束。我也试过做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 
+1

你挖了一个很深的洞。摆脱所有这一切。然后Project + Properties,Compile选项卡。勾选“注册COM互操作”复选框。 –

+1

@HansPassant我认为他是在运行时动态生成程序集,因此没有项目可以设置该选项。作为替代方案,他可以执行该复选框所执行的操作,即使用RegAsm。 – tcarvin

回答

1

,我们处理了这个浏览器自动安装.NET安装程序类,其具有装载和锁定同一个问题的方式,当前appdomain中的DLL是在新的appdomain中执行注册。

之前,我给,但是,一个你可以考虑使用进程调用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 

类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 
+0

嗯,我试过使用一个Process来直接调用生成的程序集上的regasm以及上面发布的解决方案,并且其他方法仍以某种方式锁定程序集。 – Kettch19

+0

您是否将LoadFile方法移出主代码并放入使用新AppDomain触发的代码中?除非你这样做,否则你的DLL仍然会被加载到你的主AppDomain中。 –

+0

实际上修改你的方法触摸似乎工作,并不锁定文件,但生成和注册程序集后,应用程序通过CreateObject创建程序集的实例,最终再次锁定DLL。看起来有两个步骤是锁定文件:1)注册程序集(我尝试的另一种方式),2)稍后通过CreateObject创建一个实例。第1步似乎解决了,只需要第2步就可以了。 – Kettch19

0

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

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

是否有另一个原因,您正在使用您的COM对象的GAC?

+0

我只是寻找一种“干净”的方式来直接注册程序集,而不是直接调用regasm。我知道这真的不是那么糟糕,因为我已经做了很多次,我只是在探索这个选项,并且如果能够避免组件上的锁定,它似乎应该起作用。 – Kettch19

0

使用CompileAssemblyFromSource时,您不仅要编译,而且要将程序集加载到当前进程中,然后才能正常进行,直到卸载appdomain才能释放它。

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

+0

CompileAssemblyFromSource调用不会锁定程序集,我已经检查了很多次。如果我不调用RegisterAssembly方法,那么我可以按照我喜欢的次数重新生成程序集,但是随后在VB6 CreateObject被调用时,新生成的程序集不会被使用,因为它仍然使用先前注册的版本。在[Assembly] .LoadFile在RegisterAssembly()内部调用之前它不会锁定。 – Kettch19