如何在.NET中释放COM句柄

时间:2012-03-26 07:19:08

标签: c# asp.net .net windows-installer com-interop

我在ASP.NET 4.0框架下使用以下代码从Web应用程序获取MSI文件的版本:

string strVersion = "";

try
{
    Type InstallerType;
    WindowsInstaller.Installer installer;

    InstallerType = Type.GetTypeFromProgID("WindowsInstaller.Installer");
    installer = (WindowsInstaller.Installer)Activator.CreateInstance(InstallerType);

    WindowsInstaller.Database db = installer.OpenDatabase(strMSIFilePath, 0);

    WindowsInstaller.View dv = db.OpenView("SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'");

    WindowsInstaller.Record record = null;

    dv.Execute(record);

    record = dv.Fetch();

    strVersion = record.get_StringData(1).ToString();

    dv.Close();
    //db.Commit();
    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dv);
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(db);
}
catch
{
    //Failed
    strVersion = "";
}

它工作正常,但是当代码完成运行时它保存一个内部MSI文件句柄,所以当我尝试移动或重命名MSI文件时,我得到文件仍在使用中的错误。这一直持续到我实际离开调用上述方法的ASPX页面。

我的问题是,我显然没有在上面的代码中关闭一些句柄或对象。但那可能是什么呢?

PS。我正在VS2010的开发IDE中测试它。

编辑:按照Adriano的建议编辑代码。谢谢!

3 个答案:

答案 0 :(得分:9)

COM对象尚未发布(当它超出范围时应该自动释放,但在.NET中这不能很好地工作)。因为它没有实现IDisposable接口,所以无法调用其Dispose()方法,也无法在using语句中使用它。您必须明确调用Marshal.FinalReleaseComObject。例如:

try
{
    // Your stuffs
}
finally
{
    dv.Close();
    Marshal.FinalReleaseComObject(dv);
    Marshal.FinalReleaseComObject(db);
}

此外请注意,您并不需要调用Commit()方法,因为您没有进行任何更改,只是查询。

答案 1 :(得分:3)

FWIW,您应该使用Windows Installer XML(WiX)部署工具基础(DTF)。这是Microsoft的FOSS项目,可以在CodePlex上找到。它有MSI互操作库,其类与COM类非常相似,但实现了IDisosable并在后台使用P / Invoke而不是COM。如果你愿意,甚至可以支持Linq到MSI。完整的源代码可用。

DTF是.NET世界中MSI互操作的黄金标准。以下是两个例子:

using System;
using System.Linq;
using Microsoft.Deployment.WindowsInstaller;
using Microsoft.Deployment.WindowsInstaller.Linq;

namespace ConsoleApplication3
{
    class Program
    {
        const string DATABASE_PATH = @"C:\FOO..MSI";
        const string SQL_SELECT_PRODUCTVERSION = "SELECT `Value` FROM `Property` WHERE `Property`='ProductVersion'";

        static void Main(string[] args)
        {
            using (Database database = new Database(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
            {
                Console.WriteLine(database.ExecuteScalar(SQL_SELECT_PRODUCTVERSION).ToString());
            }
            using (QDatabase database = new QDatabase(DATABASE_PATH, DatabaseOpenMode.ReadOnly))
            {
                var results = from property in database.Properties where property.Property == "ProductVersion" select property.Value;
                Console.WriteLine(results.AsEnumerable<string>().First());                    
            }
        }
    }
}

答案 2 :(得分:-2)

尝试Dispose对象。

dv.Dispose();
db.Dispose();