我一直在开发一个使用DLL互操作到外部数据库应用程序的C#应用程序。
这个外部应用程序与我的C#应用程序同时启动,只要我的C#应用程序正在运行就可用。
现在真正的问题与管理我需要创建以与外部应用程序交互的对象有关。
当我声明从引用的DLL中可用的对象时,这些对象具有使用文件(专有)操作的方法并运行一些查询(就像这个外部应用程序GUI一样)。这些对象使用Marshal.ReleaseComObject(A_OBJECT)
“由我”销毁,而其他对象在不同的应用程序域中运行,使用AppDomain.CreateDomain("A_DOMAIN")
,执行操作并调用AppDomain.Unload("A_DOMAIN")
,释放用于操作的DLL ...
这些解决方法是为了确保此外部应用程序不会“阻止”这些操作中使用的文件,因此允许删除或从文件夹中移动它们。
e.g。
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手册。这些解决方案是否正确你能解释一下这些差异吗?我真的需要使用这两种解决方案,还是应该满足?
谢谢!
答案 0 :(得分: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对象的编组,而不是通过代理实例化它。这段代码完全写在这里,没有经过测试,所以可能有问题。
答案 1 :(得分: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的使用没有任何帮助,因为除了创建空域然后卸载它之外,您的代码实际上没有做任何事情。 AppDomains对于隔离加载的.NET Framework程序集非常有用。由于涉及非托管COM代码,因此AppDomains实际上并不实用(如果需要隔离,请使用进程隔离)。第二个函数可能被重写为:
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
}
}