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 包装器在此之后变得无效。

由于调用了 GC.Collect(),第二个解决方案显然有效。该解决方案更笨拙、更慢且更不可靠(COM 对象可能不一定被垃圾收集:行为取决于特定的 .NET Framework 版本)。AppDomain 的使用无济于事,因为您的代码实际上除了创建一个空域然后卸载它之外并没有做任何事情。AppDomain 可用于隔离已加载的 .NET Framework 程序集。因为涉及到非托管 COM 代码,所以 AppDomain 不会真正有用(如果需要隔离,请使用进程隔离)。第二个函数可能可以重写为:

    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 回答