.NET 中的 IMAPI2 MsftFileSystemImage 在创建 ISO 时未释放文件

Posted

技术标签:

【中文标题】.NET 中的 IMAPI2 MsftFileSystemImage 在创建 ISO 时未释放文件【英文标题】:IMAPI2 MsftFileSystemImage in .NET not releasing files when creating ISO 【发布时间】:2011-12-15 09:01:55 【问题描述】:

我成功创建了 ISO 映像,但在调用此 Create 方法返回后尝试删除 rootFolderPath 中的文件时出现“文件正在使用”IO 错误。我错过了Marshal.ReleaseComObject 电话吗?

/// <summary>
/// Create iso image from rootFolderPath and write to isoImageFilePath. Does not include the actual rootFolder itself
/// </summary>
public void Create()

    IFileSystemImage ifsi = new MsftFileSystemImage();
    try
    
        ifsi.ChooseImageDefaultsForMediaType(IMAPI_MEDIA_PHYSICAL_TYPE.IMAPI_MEDIA_TYPE_DISK);
        ifsi.FileSystemsToCreate =
                FsiFileSystems.FsiFileSystemJoliet | FsiFileSystems.FsiFileSystemISO9660;
        ifsi.VolumeName = this.volumeName;
        ifsi.Root.AddTree(rootFolderPath, false);//use a valid folder
        //this will implement the Write method for the formatter
        IStream imagestream = ifsi.CreateResultImage().ImageStream;
        if (imagestream != null)
        
            System.Runtime.InteropServices.ComTypes.STATSTG stat;
            imagestream.Stat(out stat, 0x01);
            IStream newStream;
            if (0 == SHCreateStreamOnFile(isoImageFilepath, 0x00001001, out newStream) && newStream != null)
            
                IntPtr inBytes = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(long)));
                IntPtr outBytes = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(long)));
                try
                
                    imagestream.CopyTo(newStream, stat.cbSize, inBytes, outBytes);
                    Marshal.ReleaseComObject(imagestream);
                    imagestream = null;
                    newStream.Commit(0);
                
                finally
                
                    Marshal.ReleaseComObject(newStream);
                    Marshal.FreeHGlobal(inBytes);
                    Marshal.FreeHGlobal(outBytes);
                    if (imagestream != null)
                        Marshal.ReleaseComObject(imagestream);
                
            
        
    
    finally
    
        Marshal.ReleaseComObject(ifsi); 
    

【问题讨论】:

【参考方案1】:

大文件上的锁肯定存在问题。在网上四处挖掘会产生以下谜题:

http://social.msdn.microsoft.com/Forums/en-US/windowsopticalplatform/thread/5ae4a173-ccb2-4c10-8fd5-c6e59a9c0ac9

MS 的第一个回答承认大文件存在问题。 (NB 设置“Staging = false”对我不起作用。)

http://www.codeproject.com/KB/miscctrl/ISOImage.aspx?msg=2532334

使用流添加要刻录的文件。

http://social.msdn.microsoft.com/Forums/en-AU/windowsopticalplatform/thread/124017ea-79c5-45e2-b62e-589b3b4505af

讨论 AddRef/Release 不平衡。

所以,消化了所有这些之后,我有了一个解决方案:在写入之后,迭代文件系统映像根并释放任何流数据。

多区段光盘有几个问题——因为(通过 ImportFileSystem)导入现有文件,它们都被检查锁定,这可能需要一些时间,并且对于每个未写入的文件都会引发 COMException当前会话。经过一番努力,我确定可以缓存 AddTree 之前和之后的文件系统之间的差异,并且只检查那些文件。

无论如何...在调用 Write 之后,我们调用 ReleaseIFsiItems...

       
          // Write...

          // Call to release any locks
          ReleaseIFsiItems(fileSystemImage.Root);

           // Complete tidy up...
          Marshal.FinalReleaseComObject(fileSystem);
          Marshal.FinalReleaseComObject(fileSystemImageResult);
        

 private static void ReleaseIFsiItems(IFsiDirectoryItem rootItem)
  
     if (rootItem == null)
     
        return;
     

     var enm = rootItem.GetEnumerator();
     while (enm.MoveNext())
     
        var currentItem = enm.Current as IFsiItem;
        var fsiFileItem = currentItem as IFsiFileItem;
        if (fsiFileItem != null)
        
           try
           
              var stream = fsiFileItem.Data;
              var iUnknownForObject = Marshal.GetIUnknownForObject(stream);
              // Get a reference - things go badly wrong if we release a 0 ref count stream!
              var i = Marshal.AddRef(iUnknownForObject);
              // Release all references
              while (i > 0)
              
                 i = Marshal.Release(iUnknownForObject);
              
              Marshal.FinalReleaseComObject(stream);
           
           catch (COMException)
           
              // Thrown when accessing fsiFileItem.Data
           
        
        else
        
           ReleaseIFsiItems(currentItem as IFsiDirectoryItem);
        
     
  

我希望这对你有用!

【讨论】:

当文件被锁定并且我需要在用 c# 刻录到 DVD 后释放它们时,这对我有用。 它肯定与文件大小有关,但与“大”文件无关——确切的截止值是 128 KB;任何等于或大于该大小的东西都不会释放其手柄。文件系统似乎会自动对小于该大小的文件执行不同的操作;在我的项目中,我有一个ManagedIStream,用于添加文件而不是SHCreateStreamOnFilefsiFileItem.Data 将返回ManagedIStream 对象,用于高于截止值,但低于此值,它返回一些COM 对象实现IStream【参考方案2】:

有一个针对 Windows 7 的修补程序,用于解决 IMAPIv2 中的句柄泄漏问题。 https://support.microsoft.com/en-us/kb/2878035

出现此问题是因为 IMAPIv2 中的句柄泄漏,当 多会话写入会话发生在 DVD-RW 媒体上。

【讨论】:

问题中提到的问题发生在写入 ISO 时,而不是在刻录 DVD 时。另外,这个问题在 Windows 10 中仍然存在,所以修补程序不是答案。 我提到的问题发生在构建任何磁盘映像时,并且仅适用于没有此更新的 Windows7 SP1。此更新包含在 Windows7 SP2 中。 该修补程序专门针对在 Windows 7 上出现的问题,当您“以多会话模式将数据刻录到 DVD-RW” 时,如修补程序页面所述, “此修补程序仅用于更正本文中描述的问题”。最初的问题是关于构建 ISO 映像,并不特定于 Windows 7(该问题存在于其他版本的操作系统中)。所以,问题不是你说的问题,hotfix也没有解决问题。

以上是关于.NET 中的 IMAPI2 MsftFileSystemImage 在创建 ISO 时未释放文件的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 IMAPI2 检索和设置刻录速度?

Windows XP 上的 IMAPI2 错误

IMAPI2:添加文件和文件夹失败

IMAPI2 如何刻录已经创建的iso

IMAPI2 如何将 UniqueID 与驱动器号相关联

IMAPI2 可以刻录大于 4Gb 的文件吗?