#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» не освободите его. Однако на практике я обычно делаю это так, как вы предлагаете, и не беспокоюсь об этом.