C # -Мое приложение и совместимость (DLL / COM) с внешним приложением

#c# #com-interop #winforms-interop

#c# #com-взаимодействие #winforms-взаимодействие

Вопрос:

Я разрабатывал приложение на C #, которое использует взаимодействие DLL с приложением внешней базы данных.

Это внешнее приложение запускается одновременно с моим приложением на C # и доступно до тех пор, пока работает мое приложение на C #.

Теперь реальный вопрос связан с управлением объектами, которые мне нужно создать для взаимодействия с внешним приложением.

Когда я объявляю объекты, доступные из библиотеки DLL, на которую ссылается ссылка, у этих объектов есть методы, которые работают с файлами (которые являются собственностью) и запускают некоторые запросы (например, if сделал это с помощью этого внешнего графического интерфейса приложения). Эти объекты уничтожаются «мной» с помощью 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. Верны ли эти решения? Можете ли вы объяснить мне различия? Действительно ли мне нужно работать с обоими решениями или одного должно быть достаточно?

Спасибо!

Ответ №1:

Вы неправильно используете AppDomains. То, что вы создаете новый AppDomain перед строкой X, не означает, что строка 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-объекта вместо того, чтобы создавать его экземпляр через ваш прокси. Этот код полностью написан здесь и не тестировался, поэтому может содержать ошибки.

Комментарии:

1. Спасибо, я собираюсь попробовать решение, опубликованное @James, я думаю, что оно более чистое для моих целей. Спасибо

Ответ №2:

Первое решение является лучшим. Неуправляемый COM использует схему подсчета ссылок; IUnknown — это базовый интерфейс подсчета ссылок:http://msdn.microsoft.com/en-us/library/ms680509 (VS.85).aspx. Когда количество ссылок достигает нуля, оно освобождается.

Когда вы создаете COM-объект в .NET, вокруг COM-объекта создается оболочка. Оболочка поддерживает указатель на базовый IUnknown. Когда происходит сборка мусора, оболочка вызовет базовую функцию IUnknown::Release(), чтобы освободить COM-объект во время завершения. Как вы заметили, проблема в том, что иногда COM-объект блокирует определенные критически важные ресурсы. Вызывая Marshal.ReleaseComObject позволяет принудительно вызвать IUnknown::Release без необходимости ждать (или инициировать) общую сборку мусора. Если никакие другие ссылки на COM-объект не хранятся, то он будет немедленно освобожден. Конечно, .СЕТЕВАЯ оболочка становится недействительной после этого момента.

Второе решение, по-видимому, работает из-за вызова GC.Collect(). Решение более неуклюжее, медленное и менее надежное (объект COM не обязательно может быть сборщиком мусора: поведение зависит от конкретной версии .NET Framework). Использование AppDomain ничего не дает, поскольку ваш код фактически ничего не делает, кроме создания пустого домена и последующей его выгрузки. Домены приложений полезны для изоляции загруженных сборок .NET Framework. Поскольку задействован неуправляемый COM-код, домены приложений на самом деле не будут полезны (если вам нужна изоляция, используйте process isolation). Вторая функция, вероятно, может быть переписана как:

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

Комментарии:

1. Спасибо за совет! Я много читал о RCW и механизмах схемы подсчета ссылок COM, но, честно говоря, я не был уверен, что это действительно лучшее решение… Я собираюсь изменить все свои методы, которые работают с этим внешним приложением, протестировать его и перенести результаты сюда! Еще раз спасибо!

2. Есть еще один вопрос, порядок выпуска важен, верно? представьте этот случай: db = objApp.openDatabase(файл); table = (COMDBLib) db.TableDef(); Я должен в предложении finally: сначала выполнить маршалирование. ReleaseComObject(таблица); а затем Маршал. ReleaseComObject(db); Я спрашиваю об этом из-за того факта, что счетчик ссылок на «таблицу» теоретически должен зависеть от ссылки на «db», из-за того факта, что «таблица» была инициализирована «db»… Верно? Спасибо.

3. Это может иметь значение, а может и не иметь, в зависимости от библиотеки. Например, если реализация «table» внутренне содержит ссылку на переменную «db», то «db» не будет освобожден до тех пор, пока вы и «table» не освободите его. Однако на практике я обычно делаю это так, как вы предлагаете, и не беспокоюсь об этом.