Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IMAPI2 MsftFileSystemImage in .NET not releasing files when creating ISO

Tags:

c#

interop

imapi

I succesfully create the ISO image, but I get 'file in use' IO errors trying to delete files in the rootFolderPath after returning from calling this Create method. Am I missing a Marshal.ReleaseComObject call?

/// <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); 
    }
}
like image 500
cwchilders Avatar asked Oct 25 '11 23:10

cwchilders


1 Answers

There is certainly a problem with locks on large files. Digging around on the net produces the following bits of the puzzle:

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

    The first answer from MS acknowledges a problem with large files. (NB setting “Staging = false” does not work for me.)

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

    Uses a stream to add files to be burned.

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

    Discusses an AddRef/Release imbalance.

So, with all of this digested, I’ve got a solution: after Write, iterate the file system image root and release any stream data.

There are a couple of issues for a multisession disc – as the existing files are imported (by ImportFileSystem) they all are checked for a lock and this may take some time, and a COMException is thrown for each that was not written in the current session. With a bit of effort I’m sure the difference between the file system before and after AddTree can be cached, and only those files checked.

Anyhow... After the call to Write, we call 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);
        }
     }
  }

I hope this works for you!

like image 73
PaulS Avatar answered Oct 01 '22 21:10

PaulS