0

私は、外部データベースアプリケーションへのDLL相互運用機能を使用するC#アプリケーションを開発してきました。

この外部アプリは、C#アプリと同時に起動し、C#アプリが実行されている限り使用できます。

ここで、本当の質問は、外部アプリケーションと対話するために作成する必要のあるオブジェクトの管理に関連しています。

参照されているDLLから利用できるオブジェクトを宣言すると、これらのオブジェクトには、ファイル(プロプライエタリ)を操作し、いくつかのクエリを実行するメソッドがあります(この外部アプリのGUIで実行した場合など)。これらのオブジェクトは、を使用して「自分で」破棄されますがMarshal.ReleaseComObject(A_OBJECT)、他のオブジェクトは別のアプリケーションドメインで実行され、を使用AppDomain.CreateDomain("A_DOMAIN")して操作を実行し、を呼び出して、操作にAppDomain.Unload("A_DOMAIN")使用されたDLLを解放します。

これらの回避策は、この外部アプリケーションがこれらの操作で使用されるファイルを「ブロック」しないようにするために作成されているため、フォルダーからファイルを削除または移動できます。

例えば

private static ClientClass objApp = new ClientClass();

public bool ImportDelimitedFile(
                string fileToImport, 
                string outputFile, 
                string rdfFile)    

{
    GENERICIMPORTLib import = new GENERICIMPORTLibClass();

    try
    {
        import.ImportDelimFile(fileToImport, outputFile, 0, "", rdfFile, 0);
        return true;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        return false;
    }
    finally
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(import);
        import = null;
    }
}

public int DbNumRecs(string file)
{
    if (!File.Exists(file))
    {
        return -1;
    }

    System.AppDomain newDomain = System.AppDomain.CreateDomain();
    COMMONIDEACONTROLSLib db = new COMMONIDEACONTROLSLibClass();
    try
    {
        db = objApp.OpenDatabase(file);
        int count = (int)db.Count;

        db.Close();
        objApp.CloseDatabase(file);

        return count;
    }
    catch (Exception ex)
    {
        return -1;
    }
    finally
    {
        System.AppDomain.Unload(newDomain);
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

私はいかなる種類のAPIマニュアルも持っていないため、これらの「解決策」は両方とも試行錯誤によって達成されました。これらの解決策は正しいですか?違いを説明してもらえますか?私は本当に両方のソリューションで作業する必要がありますか、それとも一方で十分ですか?

ありがとう!

4

2 に答える 2

1

AppDomains の使用は間違っています。行 X の前に新しい AppDomain を作成したからといって、行 X がその AppDomain で実際に実行されているわけではありません。

プロキシ クラスを AppDomain 全体にマーシャリングし、現在のプロキシ クラスで使用する必要があります。

public sealed class DatabaseProxy : MarshallByRefObject
{
    public int NumberOfRecords()
    {    
        COMMONIDEACONTROLSLib db = new COMMONIDEACONTROLSLibClass();
        try
        {
            db = objApp.OpenDatabase(file);
            int count = (int)db.Count;

            db.Close();
            objApp.CloseDatabase(file);

            return count;
        }
        catch (Exception ex)
        {
            return -1;
        }
    }
}

public int NumberOfRecords()
{    

    System.AppDomain newDomain = null;

    try
    {
        newDomain = System.AppDomain.CreateDomain();
        var proxy = newDomain.CreateInstanceAndUnwrap(
                                  typeof(DatabaseProxy).Assembly.FullName,
                                  typeof(DatabaseProxy).FullName);
        return proxy.NumberOfRecords();
    }
    finally
    {
        System.AppDomain.Unload(newDomain);
    }
}

プロキシ経由でインスタンス化する代わりに、実際に COM オブジェクト自体を元に戻すマーシャルを作成できます。このコードはここに完全に記述されており、テストされていないため、バグがある可能性があります。

于 2011-05-24T17:07:54.050 に答える
1

最初のソリューションが最適です。アンマネージ COM は参照カウント スキームを使用します。IUnknown は、基礎となる参照カウント インターフェイスです: http://msdn.microsoft.com/en-us/library/ms680509(VS.85).aspx。参照カウントがゼロになると解放されます。

.NET で COM オブジェクトを作成すると、COM オブジェクトの周りにラッパーが作成されます。ラッパーは、基になる IUnknown へのポインターを保持します。ガベージ コレクションが発生すると、ラッパーは基になる IUnknown::Release() 関数を呼び出して、ファイナライズ中に COM オブジェクトを解放します。お気づきのとおり、問題は、COM オブジェクトが特定の重要なリソースをロックする場合があることです。Marshal.ReleaseComObject を呼び出すことで、一般的なガベージ コレクションを待つ (または開始する) 必要なく、IUnknown::Release を即座に呼び出すことができます。COM オブジェクトへの他の参照が保持されていない場合は、すぐに解放されます。もちろん、この時点以降、.NET ラッパーは無効になります。

2 番目の解決策は、GC.Collect() への呼び出しにより明らかに機能します。解決策は、より不器用で、遅く、信頼性が低くなります (COM オブジェクトは必ずしもガベージ コレクションされるとは限りません。動作は特定の .NET Framework のバージョンに依存します)。AppDomain を使用しても、空のドメインを作成してアンロードする以外にコードは実際には何もしないため、何も貢献しません。AppDomains は、読み込まれた .NET Framework アセンブリを分離するのに役立ちます。アンマネージ COM コードが関係しているため、AppDomains はあまり役に立ちません (分離が必要な場合は、プロセス分離を使用してください)。2 番目の関数は、おそらく次のように書き換えることができます。

    public int DbNumRecs(string file) {
        if (!File.Exists(file)) {
            return -1;
        }
        // don't need to use AppDomain
        COMMONIDEACONTROLSLib db = null; // don't need to initialize class here
        try {
            db = objApp.OpenDatabase(file);
            return (int)db.Count;
        } catch (Exception) } // don't need to declare unused ex variable
            return -1;
        } finally {
            try {
                if (db != null) {
                    db.Close();
                    Marshal.ReleaseComObject(db);
                }
                objApp.CloseDatabase(file); // is this line really needed?
            } catch (Exception) {} // silently ignore exceptions when closing
        }
    }
于 2011-05-24T17:11:42.100 に答える