1

基本的なコード生成を行う単純な VB.Net 4 WinForms アプリケーションがあります。コード生成によって DLL アセンブリが完全に正常に作成されますが、DLL が生成されるたびに、プログラムで GAC に登録する必要があります。登録する必要があるのは、デプロイ時に VB6 アプリケーションから CreateObject を介して呼び出される COM オブジェクトであるためです。ええと、私は知っています。

DLL の生成、プログラムによる登録、VB6 アプリケーションからの生成された DLL の使用など、これらすべてが正常に機能します。

問題は、コード生成を行うアプリケーションが、プロセスによって DLL がロックアウトされる前に DLL を 1 回しか生成できず、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

Web 上の多くの記事で見たように、アセンブリ定義を別の AppDomain にスローしようとしましたが、思いついた実装はどれも機能しませんでした。実際、それらのほとんどすべてが、生成されたアセンブリになり、AppDomains の両方で定義されました。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のロードとロックと同じ問題があり、新しいアプリドメインで登録を実行することです。

ただし、その前に、検討する可能性のある追加のショートカットの1つは、プロセスを使用してzippy32を呼び出し、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
于 2011-11-08T00:52:26.290 に答える
0

プログラムで GAC に登録する必要があります。登録する必要がある理由は、それが COM オブジェクトであるためです。

.NET ベースの COM オブジェクトを GAC に入れる必要はありません。それらを標準の .NET regasm ユーティリティに登録するだけで、System.Diagnostics.Proces オブジェクトを使用して regasm ユーティリティを外部プロセスとして呼び出すことができます。

COM オブジェクトに GAC を使用している別の理由はありますか?

于 2011-11-08T12:58:43.217 に答える
0

CompileAssemblyFromSource を使用すると、コンパイルするだけでなく、アセンブリを現在のプロセスにロードします。これは、appdomain がアンロードされるまでリリースされないのが普通です。

コマンド ライン コンパイラ vbc の使用だけを検討しましたか? アプリドメインとは無関係にコンパイルを実行し、DLL はディスク上にのみ作成され、それへの参照はロックされませんか?

于 2011-11-08T17:28:34.987 に答える