2011-11-08 44 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