简体   繁体   中英

C# -My application and Interopability (DLL/COM) with an external application

I've been developing a C# application that uses DLL interop to an external database application.

This external app starts up at the same time along with my C# app and is available as long as my C# app is running.

Now the real question is related to managing the objects that I need to create to interact with the external application.

When I declare objects that are available from the referenced DLL's these objects have methods that operate with files (that are proprietary) and run some queries (like if did it by this external app GUI). These objects are destroyed "by me" using Marshal.ReleaseComObject(A_OBJECT) while others run in a diferent application domain, by using AppDomain.CreateDomain("A_DOMAIN") , do the operations and call an AppDomain.Unload("A_DOMAIN") , releasing the DLLs used for the operation...

These workarounds are made to ensure that this external application doesn't "block" files used in these operations, therefore allowing deletion or moving them from a folder.

eg

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();
    }
}

Both of these "solutions" were reached by trial and error, due to the fact I do not possess any kind of API manual. Are these solutions correct? Can you explain me the differences? Do I really need to work with both solutions or one should suffice?

Thanks!

Your use of AppDomains is wrong. Just because you create a new AppDomain before line X doesn't mean that line X is actually executing in that AppDomain.

You need to marshall a proxy class back across your AppDomain and use it in the current one.

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;
        }
    }
}

and

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);
    }
}

You can actually create an marshall back the COM object itself instead of instantiating it via your proxy. This code is completely written here and not tested, so may be buggy.

The first solution is the best one. Unmanaged COM uses a reference-counting scheme; IUnknown is the underlying reference-counting interface: http://msdn.microsoft.com/en-us/library/ms680509(VS.85).aspx . When the reference count reaches zero, it is freed.

When you create a COM object in .NET, a wrapper is created around the COM object. The wrapper maintains a pointer to the underlying IUnknown. When garbage collection occurs, the wrapper will call the underlying IUnknown::Release() function to free the COM object during finalization. As you noticed, the problem is that sometimes the COM object locks certain critical resources. By calling Marshal.ReleaseComObject, you force an immediate call to IUnknown::Release without needing to wait (or initiate) a general garbage collection. If no other references to the COM object are held, then it will immediately be freed. Of course, the .NET wrapper becomes invalid after this point.

The second solution apparently works because of the call to GC.Collect(). The solution is more clumsy, slower, and less reliable (the COM object might not necessarily be garbage collected: behavior is dependent on the specific .NET Framework version). The use of AppDomain contributes nothing as your code doesn't actually do anything apart from creating an empty domain and then unloading it. AppDomains are useful for isolating loaded .NET Framework assemblies. Because unmanaged COM code is involved, AppDomains won't really be useful (if you need isolation, use process isolation). The second function can probably be rewritten as:

    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
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM