Click here to Skip to main content
15,886,919 members
Articles / Desktop Programming / Win32

Burning and Erasing CD/DVD/Blu-ray Media with C# and IMAPI2

Rate me:
Please Sign up or sign in to vote.
4.96/5 (200 votes)
23 Mar 2010CPOL10 min read 1.8M   32.5K   353   492
Using the Image Mastering API in C#.
Image 1

Introduction

Windows introduced the new IMAPIv2.0 with the release of the Vista Operating System which was a big improvement over the original IMAPI. The original IMAPI is great for CDROMs, but it has some huge limitations like not being able to write to DVD media. I am sure this limitation is due to almost nobody having a DVD writer when Windows XP was released back in 2001. IMAPIv2 allows you to write to CD, DVD, and even Blu-ray media, as well as read and write ISO files. IMAPIv2.0 had a problem since it was only available with Windows Vista. But in June of 2007, Microsoft released update packages for Windows XP and Windows 2003. You can download the updates here.

To use the features added in the Windows Feature Pack for Storage 1.0, you need to download the update here. Windows Vista Service Pack 2 already includes the update.

I wrote this article as a sequel to my C++ article, Burning CD/DVD Media with the Image Mastering API Version 2.0. Most of the IMAPI2 samples seem to be in scripting languages. The only C# sample I found was the IBurn console project that came with the Windows Vista SDK, and more recently, the article: How to Create Optical File Images using IMAPIv2.0 by dmihailescu, which shows you how to create an ISO file.

This article was more difficult than I had thought it would be. Normally, .NET applications are supposed to be easier, but there were a number of issues that I needed to figure out to get this to work. If you're not interested in hearing me rant and rave, you can skip over the next section.

The Problems

IMAPI2 was implemented using two separate COM DLLs: imapi2.dll and imapi2fs.dll. imapi2.dll handles most of the device and recording APIs, and imapi2fs.dll handles all of the file system and IStream APIs. This may not seem like much of a problem, especially if you are using C++. This does become a huge problem with .NET, because you need to take an IStream created from IMAPI2FS and use it in IMAPI2 to write to the media. You end up getting an error message, something like this:

Unable to cast object of type 'IMAPI2FS.FsiStreamClass' to type 'IMAPI2.IStream'

Microsoft realized this problem, and created a project called IBurn that they released in the Windows Vista SDK. They created an Interop namespace that combined many classes, enums, and interfaces of IMAPI2 and IMAPI2FS into one namespace in a file Interop.cs. This fixed the problem of not being able to cast an IStream from IMAPI2FS to IMAPI2.

Unfortunately, their implementation did not completely fix my problems. I was having other COM problems, like having the application throw exceptions whenever I would try to use any method that returned an Array. I later discovered that this problem could have been fixed by changing the Array to object[]. I was also having null reference exceptions when trying to burn multiple CDs. Microsoft admits that Interop.cs is not a complete solution and is only used to demonstrate how to use IMAPI2 using C#.

So, off I went on my journey to create a complete solution.

I created two .NET Interop assemblies from the COM DLLs by using the Microsoft Type Library to Assembly Converter, tlbimp.exe. I created the two assemblies by using the following commands:

tlbimp imapi2.dll /out:imapi2int.dll
tlbimp imapi2fs.dll /out:imapi2fsint.dll

Once I had the .NET assemblies, I used Reflector to disassemble the interops and create C# source code. I combined the two files and made many modifications to the interfaces and helper classes, and added support for all interfaces of IMAPI2. That sounds a lot easier than it really was.

One of the biggest issues I had that seemed to take forever to figure out was the reversing of the get, set properties. The auto-generated code from Reflector always placed the get before the set. Many properties in IMAPI2 require that the properties be defined with set before get. If they are not in the proper order, then it causes the application to crash and instantly exit. It doesn't throw an exception to give you any kind of clue as to what the problem might be. It just kills the app. So, after I finally figured out what was wrong, I opened up the COM DLLs with the OLE/COM Object Viewer and went through the actual TypeLib for every property and made sure they were in the correct order.

I also chose not to implement the AStream interface as they did in Interop.cs. I use the System.Runtime.InteropServices.ComTypes.IStream Interface directly.

Lastly, in order to receive notifications for all events, I had to go through the SDK and find all of the Dispatch IDs for all of the events. Without these values, the event handlers are unable to receive notifications.

New IMAPI2 Interop - Imapi2interop.cs

My replacement for Interop.cs is Imapi2Interop.cs, included in the source code. It defines the following classes and interfaces:

  • IBootOptions - Specifies the boot image to add to the optical disc
  • IEnumFsiItems - Enumerates the child directory and file items for an FsiDirectoryItem object
  • IEnumProgressItems - Enumerates a collection of progress items
  • IFileSystemImageResult - Gets information about the burn image, the image data stream, and progress information
  • IFsiDirectoryItem - Adds items to or removes items from the file-system image
  • IFsiFileItem - Identifies the file size and data stream of the file contents
  • IDiscFormat2Data - Writes a data stream to a disc
  • IDiscFormat2DataEventArgs - Retrieves information about the current write operation
  • IDiscFormat2Erase - Erases data from a disc
  • IDiscFormat2RawCD - Writes raw images to a disc device using Disc At Once (DAO) mode
  • IDiscFormat2RawCDEventArgs - Retrieves information about the current write operation
  • IDiscFormat2TrackAtOnce - Writes audio to blank CD-R or CD-RW media in Track-At-Once mode
  • IDiscFormat2TrackAtOnceEventArgs - Retrieves information about the current write operation
  • IDiscMaster2 - Enumerates the CD and DVD devices installed on the computer
  • IDiscRecorder2 - Represents a physical device
  • IDiscRecorder2Ex - Retrieves information not available through the IDiscRecorder2 interface
  • IProgressItem - Retrieves block information for one segment of the result file image
  • IProgressItems - Enumerates the progress items in a result image
  • IWriteEngine2 - Writes a data stream to a device
  • IWriteEngine2EventArgs - Retrieves information about the current write operation
  • IWriteSpeedDescriptor - Retrieves detailed write configurations supported by the disc recorder and current media

The Windows Feature Pack for Storage 1.0 also adds the following Interfaces:

  • IBurnVerification - Sets the verification level of the burn operation
  • IFileSystemImage - Builds, imports, and exports a file system image
  • IFileSystemImage2 - Extends the IFileSystemImage interface by writing multiple boot entries or boot images required for the EFI/UEFI support
  • IFileSystemImage3 - Extends the IFileSystemImage2 interface by setting or checking the metadata and metadata mirror files in a UDF file system (rev 2.50 and later) to determine redundancy
  • IFsiNamedStreams - Enumerates the named streams associated with a file in a file system image
  • IIsoImageManager - Verifies if an existing ISO file contains a valid image for burning
  • IMultiSession - Base interface containing the properties common to the derived multi-session interfaces.
  • IMultiSessionSequential - Extends the IMultiSession interface by retrieving information about the previous import session on a sequentially recorded media
  • IRawCDImageCreator - Creates a raw CD image for writing in Disc-at-once mode.
  • IRawCDImageTrackInfo - Tracks per-track properties that are applied to CD Media

It also defines the following events:

  • DDiscFormat2DataEvents
    • DiscFormat2Data_EventHandler
  • DDiscFormat2EraseEvents
    • DiscFormat2Erase_EventHandler
  • DDiscFormat2RawCDEvents
    • DiscFormat2RawCD_UpdateEventHandler
  • DDiscFormat2TrackAtOnceEvents
    • DiscFormat2TrackAtOnce_EventHandler
  • DDiscMaster2Events
    • DiscMaster2_NotifyDeviceAddedEventHandler
    • DiscMaster2_NotifyDeviceRemovedEventHandler
  • DFileSystemImageEvents
    • DFileSystemImage_EventHandler
  • DWriteEngine2Events
    • DWriteEngine2_EventHandler

Using the Code

Make sure that XP and 2003 have the IMAPI2 updates mentioned at the top of the article.

Do not add the imapi2.dll and imapi2fs.dll COM DLLs to your project. That will cause the problems listed above.

Add the file imapi2interop.cs to your project and define the namespace in your app:

C#
using IMAPI2.Interop;

In order to receive notification to your event handler from COM, you need to open up the file AssemblyInfo.cs and change the ComVisible attribute to true:

C#
[assembly: ComVisible(true)]

Determining the Media Type

To determine the media type and the available space on the hard drive, you create a MsftDiscFormat2Data object and set the current recorder in the Recorder property. You can then get the media type from the IDiscFormat2Data CurrentPhysicalMediaType property.

Once you have the media type, create a MsftFileSystemImage object and call the ChooseImageDefaultsForMediaType method with the media type.

To determine if any sessions have already been recorded on the media, check the IDiscFormatData2 MediaHeuristicallyBlank property.

If it is false, then other sessions have been recorded and you need to set the MsftFileSystemImage's MultisessionInterfaces property with the IDiscFormat2Data MultisessionInterfaces property, then call the IDiscFormat2Data ImportFileSystem() method.

Then, get the free media blocks by multiplying the MsftFileSystemImage's FreeMediaBlocks with the sector size (2048). If there were previous sessions recorded on the media, that space will be subtracted from the total size of the media.

C#
private void buttonDetectMedia_Click(object sender, EventArgs e)
{
    if (devicesComboBox.SelectedIndex == -1)
    {
        return;
    }

    var discRecorder =
        (IDiscRecorder2)devicesComboBox.Items[devicesComboBox.SelectedIndex];

    MsftFileSystemImage fileSystemImage = null;
    MsftDiscFormat2Data discFormatData = null;

    try
    {
        //
        // Create and initialize the IDiscFormat2Data
        //
        discFormatData = new MsftDiscFormat2Data();
        if (!discFormatData.IsCurrentMediaSupported(discRecorder))
        {
            labelMediaType.Text = "Media not supported!";
            _totalDiscSize = 0;
            return;
        }
        else
        {
            //
            // Get the media type in the recorder
            //
            discFormatData.Recorder = discRecorder;
            IMAPI_MEDIA_PHYSICAL_TYPE mediaType = discFormatData.CurrentPhysicalMediaType;
            labelMediaType.Text = GetMediaTypeString(mediaType);

            //
            // Create a file system and select the media type
            //
            fileSystemImage = new MsftFileSystemImage();
            fileSystemImage.ChooseImageDefaultsForMediaType(mediaType);

            //
            // See if there are other recorded sessions on the disc
            //
            if (!discFormatData.MediaHeuristicallyBlank)
            {
                fileSystemImage.MultisessionInterfaces = 
				discFormatData.MultisessionInterfaces;
                fileSystemImage.ImportFileSystem();
            }

            Int64 freeMediaBlocks = fileSystemImage.FreeMediaBlocks;
            _totalDiscSize = 2048 * freeMediaBlocks;
        }
    }
    catch (COMException exception)
    {
        MessageBox.Show(this, exception.Message, "Detect Media Error",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
    finally
    {
        if (discFormatData != null)
        {
            Marshal.ReleaseComObject(discFormatData);
        }

        if (fileSystemImage != null)
        {
            Marshal.ReleaseComObject(fileSystemImage);
        }
    }

    UpdateCapacity();
}

Adding Files and Directories to the Listbox

I created a generic interface called IMediaItem. IMediaItem contains three properties and one method:

C#
interface IMediaItem
{
    /// <summary>
    /// Returns the full path of the file or directory
    /// </summary>
    string Path { get; }

    /// <summary>
    /// Returns the size of the file or directory to the next largest sector
    /// </summary>
    Int64 SizeOnDisc { get; }

    /// <summary>
    /// Returns the Icon of the file or directory
    /// </summary>
    System.Drawing.Image FileIconImage { get; }

    // Adds the file or directory to the directory item, usually the root.
    bool AddToFileSystem(IFsiDirectoryItem rootItem);
}

For file items, I created the FileItem class. This class basically creates an IStream by PInvoking the SHCreateStreamOnFile Windows API call, and adds it to IFsiDirectoryItem.

For directory items, I created the DirectoryItem class. This uses a much simpler technique of calling the IFsiDirectoryItem.AddTree method to add the directory and all subdirectories to the IStream.

These classes and interfaces are located in the IMediaItem.cs file.

Creating the Image

I use the CreateMediaFileSystem method in the MainForm class to create the IStream image to write to the media. I enumerate through the files and directories that were added to the file listbox.

C#
private bool CreateMediaFileSystem(IDiscRecorder2 discRecorder, out IStream dataStream)
{
    MsftFileSystemImage fileSystemImage = null;
    try
    {
        fileSystemImage = new MsftFileSystemImage();
        fileSystemImage.ChooseImageDefaults(discRecorder);
        fileSystemImage.FileSystemsToCreate =
            FsiFileSystems.FsiFileSystemJoliet | FsiFileSystems.FsiFileSystemISO9660;
        fileSystemImage.VolumeName = textBoxLabel.Text;

        fileSystemImage.Update +=
        new DFileSystemImage_EventHandler(fileSystemImage_Update);

        //
        // If multisessions, then import previous sessions
        //
        if (multisessionInterfaces != null)
        {
            fileSystemImage.MultisessionInterfaces = multisessionInterfaces;
            fileSystemImage.ImportFileSystem();
        }

        //
        // Get the image root
        //
        IFsiDirectoryItem rootItem = fileSystemImage.Root;

        //
        // Add Files and Directories to File System Image
        //
        foreach (IMediaItem mediaItem in listBoxFiles.Items)
        {
            //
            // Check if we've cancelled
            //
            if (backgroundBurnWorker.CancellationPending)
            {
                break;
            }

            //
            // Add to File System
            //
            mediaItem.AddToFileSystem(rootItem);
        }

        fileSystemImage.Update -=
        new DFileSystemImage_EventHandler(fileSystemImage_Update);

        //
        // did we cancel?
        //
        if (backgroundBurnWorker.CancellationPending)
        {
            dataStream = null;
            return false;
        }

        dataStream = fileSystemImage.CreateResultImage().ImageStream;
    }
    catch (COMException exception)
    {
        MessageBox.Show(this, exception.Message,
            "Create File System Error",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
        dataStream = null;
        return false;
    }
    finally
    {
        if (fileSystemImage != null)
        {
            Marshal.ReleaseComObject(fileSystemImage);
        }
    }

    return true;
}

Multithreading

Burning or formatting media can take some time, so we do not want to perform these actions on the main UI thread. I use the BackgroundWorker class to handle the multithreading of these lengthy tasks. The BackgroundWorker class allows you to set values within the thread and then call the ReportProgress method which fires a ProgressChanged event in the calling thread. When you are finished with your worker thread, it fires the RunWorkerCompleted event to notify the calling thread that it is finished. I won't go into much detail on the whole threading process since that is not the main topic of this article.

Writing Data to Media

I write the data in the MainForm's backgroundBurnWorker_DoWork which is the DoWork event for the BackgroundWorker backgroundBurnWorker.

C#
private void backgroundBurnWorker_DoWork(object sender, DoWorkEventArgs e)
{
    MsftDiscRecorder2 discRecorder = null;
    MsftDiscFormat2Data discFormatData = null;

    try
    {
        //
        // Create and initialize the IDiscRecorder2 object
        //
        discRecorder = new MsftDiscRecorder2();
        var burnData = (BurnData)e.Argument;
        discRecorder.InitializeDiscRecorder(burnData.uniqueRecorderId);

        //
        // Create and initialize the IDiscFormat2Data
        //
        discFormatData = new MsftDiscFormat2Data
            {
                Recorder = discRecorder,
                ClientName = ClientName,
                ForceMediaToBeClosed = _closeMedia
            };

        //
        // Set the verification level
        //
        var burnVerification = (IBurnVerification)discFormatData;
        burnVerification.BurnVerificationLevel = _verificationLevel;

        //
        // Check if media is blank, (for RW media)
        //
        object[] multisessionInterfaces = null;
        if (!discFormatData.MediaHeuristicallyBlank)
        {
            multisessionInterfaces = discFormatData.MultisessionInterfaces;
        }

        //
        // Create the file system
        //
        IStream fileSystem;
        if (!CreateMediaFileSystem(discRecorder, multisessionInterfaces, out fileSystem))
        {
            e.Result = -1;
            return;
        }

        //
        // add the Update event handler
        //
        discFormatData.Update += discFormatData_Update;

        //
        // Write the data here
        //
        try
        {
            discFormatData.Write(fileSystem);
            e.Result = 0;
        }
        catch (COMException ex)
        {
            e.Result = ex.ErrorCode;
            MessageBox.Show(ex.Message, "IDiscFormat2Data.Write failed",
                MessageBoxButtons.OK, MessageBoxIcon.Stop);
        }
        finally
        {
            if (fileSystem != null)
            {
                Marshal.FinalReleaseComObject(fileSystem);
            }
        }

        //
        // remove the Update event handler
        //
        discFormatData.Update -= discFormatData_Update;

        if (_ejectMedia)
        {
            discRecorder.EjectMedia();
        }
    }
    catch (COMException exception)
    {
        //
        // If anything happens during the format, show the message
        //
        MessageBox.Show(exception.Message);
        e.Result = exception.ErrorCode;
    }
    finally
    {
        if (discRecorder != null)
        {
            Marshal.ReleaseComObject(discRecorder);
        }

        if (discFormatData != null)
        {
            Marshal.ReleaseComObject(discFormatData);
        }
    }
}

Progress Update Events

The IDiscFormat2Data supports cancelling with the CancelWrite method. When I receive the Update Event from IDiscFormatData2, I check to see if the user pressed the Cancel button. If the user has cancelled, the BackgroundWorker's CancellationPending property will be true, and I cancel the write operation and immediately return. Otherwise, I collect the data from the IDiscFormat2DataEventArgs object, then call the backgroundBurnWorker.ReportProgress so the UI thread can update the data and progress bar.

C#
void discFormatData_Update([In, MarshalAs(UnmanagedType.IDispatch)] object sender,
                           [In, MarshalAs(UnmanagedType.IDispatch)] objectprogress)
{
    //
    // Check if we've cancelled
    //
    if (backgroundBurnWorker.CancellationPending)
    {
        var format2Data = (IDiscFormat2Data)sender;
        format2Data.CancelWrite();
        return;
    }

    var eventArgs = (IDiscFormat2DataEventArgs)progress;

    _burnData.task = BURN_MEDIA_TASK.BURN_MEDIA_TASK_WRITING;

    // IDiscFormat2DataEventArgs Interface
    _burnData.elapsedTime = eventArgs.ElapsedTime;
    _burnData.remainingTime = eventArgs.RemainingTime;
    _burnData.totalTime = eventArgs.TotalTime;

    // IWriteEngine2EventArgs Interface
    _burnData.currentAction = eventArgs.CurrentAction;
    _burnData.startLba = eventArgs.StartLba;
    _burnData.sectorCount = eventArgs.SectorCount;
    _burnData.lastReadLba = eventArgs.LastReadLba;
    _burnData.lastWrittenLba = eventArgs.LastWrittenLba;
    _burnData.totalSystemBuffer = eventArgs.TotalSystemBuffer;
    _burnData.usedSystemBuffer = eventArgs.UsedSystemBuffer;
    _burnData.freeSystemBuffer = eventArgs.FreeSystemBuffer;

    //
    // Report back to the UI
    //
    backgroundBurnWorker.ReportProgress(0, _burnData);
}

Formatting/Erasing RW Discs

burnmedia_format.png

I format the disc in the MainForm's backgroundFormatWorker_DoWork which is the DoWork event for the BackgroundWorker backgroundFormatWorker.

C#
private void backgroundFormatWorker_DoWork(object sender, DoWorkEventArgs e)
{
    MsftDiscRecorder2 discRecorder = null;
    MsftDiscFormat2Erase discFormatErase = null;

    try
    {
        //
        // Create and initialize the IDiscRecorder2
        //
        discRecorder = new MsftDiscRecorder2();
        var activeDiscRecorder = (string)e.Argument;
        discRecorder.InitializeDiscRecorder(activeDiscRecorder);

        //
        // Create the IDiscFormat2Erase and set properties
        //
        discFormatErase = new MsftDiscFormat2Erase
            {
                Recorder = discRecorder,
                ClientName = ClientName,
                FullErase = !checkBoxQuickFormat.Checked
            };

        //
        // Setup the Update progress event handler
        //
        discFormatErase.Update += discFormatErase_Update;

        //
        // Erase the media here
        //
        try
        {
            discFormatErase.EraseMedia();
            e.Result = 0;
        }
        catch (COMException ex)
        {
            e.Result = ex.ErrorCode;
            MessageBox.Show(ex.Message, "IDiscFormat2.EraseMedia failed",
                MessageBoxButtons.OK, MessageBoxIcon.Stop);
        }

        //
        // Remove the Update progress event handler
        //
        discFormatErase.Update -= discFormatErase_Update;

        //
        // Eject the media
        //
        if (checkBoxEjectFormat.Checked)
        {
            discRecorder.EjectMedia();
        }

    }
    catch (COMException exception)
    {
        //
        // If anything happens during the format, show the message
        //
        MessageBox.Show(exception.Message);
    }
    finally
    {
        if (discRecorder != null)
        {
            Marshal.ReleaseComObject(discRecorder);
        }

        if (discFormatErase != null)
        {
            Marshal.ReleaseComObject(discFormatErase);
        }
    }
}

History

  • March 21, 2008
    • Initial release
  • March 25, 2008 
    • Added the Visual Studio 2005 project
  • March 29, 2008 
    • Detects media type and size, supports multi-session
  • May 2, 2009 
    • Added support for Windows Feature Pack for Storage, and bug fixes
  • December 13, 2009 
    • Removed calls to the AcquireExclusiveAccess and ReleaseExclusiveAccess methods as they are not needed
    • Calls IDiscRecorder2.SupportedProfiles to enumerate supported disk types
  • March 2, 2010
    • Fixed some incorrect method parameters in the IDiscRecorder2Ex class
    • Fixed a potential threading issue
    • Added an icon
  • March 22, 2010
    • Fixed a very bad Windows XP bug found by Leroe where the MsftFileSystemImageClass was always unnecessarily using the IFileSystemImage3 Interface when just using the IFileSystemImage interface would suffice. Also did a little code refactoring.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Thank you for voting on my articles!

MCSD.NET in C#


Comments and Discussions

 
GeneralRe: Unable to cast COM object under vista Pin
chingyen8-Apr-10 16:03
chingyen8-Apr-10 16:03 
GeneralRe: Unable to cast COM object under vista Pin
chris insalaco4-Aug-10 12:30
chris insalaco4-Aug-10 12:30 
Generali am having this error Pin
adeel8418-Mar-10 2:13
adeel8418-Mar-10 2:13 
GeneralRe: i am having this error Pin
Eric Haddan18-Mar-10 4:28
Eric Haddan18-Mar-10 4:28 
GeneralRe: i am having this error Pin
adeel8418-Mar-10 21:29
adeel8418-Mar-10 21:29 
GeneralRe: i am having this error Pin
Eric Haddan19-Mar-10 3:43
Eric Haddan19-Mar-10 3:43 
GeneralException from HRESULT: 0xC0AA0002 Pin
Member 246993113-Mar-10 0:25
Member 246993113-Mar-10 0:25 
QuestionSilly question about SizeOnDisc property Pin
G_Hosa_Phat12-Feb-10 5:38
professionalG_Hosa_Phat12-Feb-10 5:38 
I'm developing a project for work (in VB.NET) in which I hope to include an automated burning operation for creating monthly archive DVD's. I've compiled this project to a DLL and included it in my project, and I've been able to successfully burn files using the code with a few very minor "tweaks" (I had to change the scope on a couple of things). However, I'm still having some difficulty in completing this because of some discrepancies I'm running into with regards to the SizeOnDisc property of the FileItem. Perhaps I'm just a bit dense having never really tried to work with this type of "mass archive automation" process before, but I'm hoping you can help me.

Here is the code I'm using to retrieve all of the files and organize them into however many discs will need to be burned. You'll note that I'm setting up a "priority list" at first because there is a certain order in which I want some of the data burned:
Private Function SetupDiscsToBurn(ByVal BurningPath As String) As ArrayList
    Dim FileItem As FileItem = Nothing
    Dim DirectoryItem As DirectoryItem = Nothing
    Dim TotalDataToBurn As Int64 = 0
    Dim CurrentImageSize As Int64 = 0
    Dim ArchiveDiscFileList As New ArrayList
    Dim ArchiveDiscs As New ArrayList
    Dim PriorityList As New ArrayList
    Dim DiscFull As Boolean = False

    ' *********************************************************************
    ' ** Setup a priority list to ensure that the archive folders are    **
    ' ** burned in order.                                                **
    ' *********************************************************************
    If Directory.Exists(strTempZipPath &amp; "FOLDER ONE") Then
        PriorityList.Add(strTempZipPath &amp; "FOLDER ONE")
    End If

    If Directory.Exists(strTempZipPath &amp; "FOLDER TWO") Then
        PriorityList.Add(strTempZipPath &amp; "FOLDER TWO")
    End If

    If Directory.Exists(strTempZipPath &amp; "FOLDER THREE") Then
        PriorityList.Add(strTempZipPath &amp; "FOLDER THREE")
    End If

    If File.Exists(strTempZipPath &amp; "FILE ONE.ZIP") Then
        PriorityList.Add(strTempZipPath &amp; "FILE ONE.ZIP")
    End If

    If File.Exists(strTempZipPath &amp; "FILE TWO.ZIP") Then
        PriorityList.Add(strTempZipPath &amp; "FILE TWO.ZIP")
    End If

    If File.Exists(strTempZipPath &amp; "FILE THREE.ZIP") Then
        PriorityList.Add(strTempZipPath &amp; "FILE THREE.ZIP")
    End If

    If File.Exists(strTempZipPath &amp; "FILE FOUR.ZIP") Then
        PriorityList.Add(strTempZipPath &amp; "FILE FOUR.ZIP")
    End If

    If File.Exists(strTempZipPath &amp; "FILE FIVE.ZIP") Then
        PriorityList.Add(strTempZipPath &amp; "FILE FIVE.ZIP")
    End If

    If Directory.Exists(strTempZipPath &amp; "FOLDER FOUR") Then
        PriorityList.Add(strTempZipPath &amp; "FOLDER FOUR")
    End If

    If PriorityList.Count &gt; 0 Then
        Do
            ' *********************************************************************
            ' ** If the cumulative size of the files added to the current disk   **
            ' ** will fill the disk, add the list to the ArchiveDiscs ArrayList, **
            ' ** clear the list and reset everything for the next batch of files **
            ' *********************************************************************
            If DiscFull Then
                ArchiveDiscs.Add(ArchiveDiscFileList)
                CurrentImageSize = 0
                ArchiveDiscFileList = New ArrayList
                DiscFull = False
            End If

            If InStr(UCase(PriorityList(0).ToString), ".ZIP") = 0 Then
                DirectoryItem = New DirectoryItem(PriorityList(0).ToString)

                If DirectoryItem.SizeOnDisc &lt; (TotalDiscSize - CurrentImageSize) Then
                    ' *********************************************************************
                    ' ** Add the DirectoryItem object to the ArrayList of MediaItem      **
                    ' ** objects for this disk, update the remaining space on the disk   **
                    ' ** and remove the directory from the PriorityList.                 **
                    ' *********************************************************************
                    ArchiveDiscFileList.Add(DirectoryItem)
                    CurrentImageSize = CurrentImageSize + DirectoryItem.SizeOnDisc
                    PriorityList.RemoveAt(0)
                ElseIf CurrentImageSize = 0 Then
                    ' *********************************************************************
                    ' ** If the entire directory is too large to fit on the current disk **
                    ' ** and the current disk is blank (always start a new directory on  **
                    ' ** a new disk if it doesn't fit on the previous), add each file to **
                    ' ** the ArchiveFileList seperately until the disk is full.          **
                    ' *********************************************************************
                    PriorityList.InsertRange(1, Directory.GetFiles(PriorityList(0).ToString))

                    ' *********************************************************************
                    ' ** Since we're not going to be adding the directory itself, we'll  **
                    ' ** need to reorganize the PriorityList a bit, then we can start    **
                    ' ** trying to add the files individually.                           **
                    ' *********************************************************************
                    For Each File As String In Directory.GetFiles(PriorityList(0).ToString)
                        FileItem = New FileItem(File)

                        ' *********************************************************************
                        ' ** Check each file's SizeOnDisc property to see if it will fit in  **
                        ' ** the leftover space on the current media.  If so, add it to the  **
                        ' ** of MediaItem objects for this disk, update the remaining space  **
                        ' ** on the disk and remove the item from the PriorityList.          **
                        ' *********************************************************************
                        If FileItem.SizeOnDisc &lt; (TotalDiscSize - CurrentImageSize) Then
                            ArchiveDiscFileList.Add(FileItem)
                            CurrentImageSize = CurrentImageSize + FileItem.SizeOnDisc
                            PriorityList.RemoveAt(1)
                        ' *********************************************************************
                        ' ** As soon as we hit a file that doesn't fit on the current disk,  **
                        ' ** consider the disk full and start a new one.                     **
                        ' *********************************************************************
                        Else
                            DiscFull = True
                        End If

                        FileItem = Nothing
                    Next

                    ' *********************************************************************
                    ' ** Remove the directory in which the files we're adding are        **
                    ' ** contained from the PriorityList so that when we loop again, we  **
                    ' ** don't get an endless loop of files added to the PriorityList.   **
                    ' *********************************************************************
                    PriorityList.RemoveAt(0)
                End If

                DirectoryItem = Nothing
            Else
                FileItem = New FileItem(PriorityList(0).ToString)

                If FileItem.SizeOnDisc &lt; (TotalDiscSize - CurrentImageSize) Then
                    ArchiveDiscFileList.Add(FileItem)
                    CurrentImageSize = CurrentImageSize + FileItem.SizeOnDisc
                    PriorityList.RemoveAt(0)
                Else
                    DiscFull = True
                End If

                FileItem = Nothing
            End If

        Loop Until PriorityList.Count = 0

        If CurrentImageSize &gt; 0 Then
            ArchiveDiscs.Add(ArchiveDiscFileList)
            ArchiveDiscFileList = Nothing
            CurrentImageSize = 0
        End If
    End If

    Return ArchiveDiscs
End Function


Okay, so everything appears to be working normally here, but the problem comes in when I try to actually burn the data collected by this function. For reference, The total available space on my DVD+R is 4,700,372,992 bytes.

I've been testing with a single folder with a huge amount of data and letting it build my disk images in this way, but I get some very odd results. Once the first image is all put together (93 files, altogether), I checked the value of the CurrentImageSize variable. The result is 4,698,685,440 bytes. Before I go ahead and let my application do the burning (just in case I run into some funky error I don't recognize), I open up Nero to try to burn the exact same data as is listed as being on my first disk image. I grab all 93 files and put them in Nero's list of files to burn and the UI shows that there's a little "overhang" of data beyond the "safe" capacity of the disk. I hit the burn button and it fails saying that there's too much data to be written to the disk.

The wierd thing is that the amount of data Nero shows is WAY off my calculation. I could understand if Nero needed a little extra space for it's own volume data, or something, but it's reporting that there's 4,925,685,760 bytes to be written to the disk. OMG | :OMG: That's approximately 225MB difference.

Okay, so I go check in Windows... I go to the temporary directory where the files are stored and check the combined size of the 93 files in question: 4,925,218,816 bytes. WTF | :WTF:

So, by now, I'm sure you can tell that my question is: "Why am I getting such disparate values?" I've even started walking through each of the 93 files, verifying and trying to manually calculate the individual files' sizes (SizeOnDisc). What's throwing me so far is that all of the application calculated values seem to be either equal to or larger than Windows is reporting, so I would think that we should be getting close to what Nero and Windows are coming up with.
AnswerRe: Silly question about SizeOnDisc property Pin
Eric Haddan12-Feb-10 6:29
Eric Haddan12-Feb-10 6:29 
GeneralRe: Silly question about SizeOnDisc property [modified] Pin
G_Hosa_Phat12-Feb-10 6:59
professionalG_Hosa_Phat12-Feb-10 6:59 
GeneralRe: Silly question about SizeOnDisc property Pin
Eric Haddan12-Feb-10 7:24
Eric Haddan12-Feb-10 7:24 
GeneralRe: Silly question about SizeOnDisc property Pin
G_Hosa_Phat12-Feb-10 8:03
professionalG_Hosa_Phat12-Feb-10 8:03 
GeneralRe: Silly question about SizeOnDisc property Pin
G_Hosa_Phat12-Feb-10 8:54
professionalG_Hosa_Phat12-Feb-10 8:54 
GeneralRe: Silly question about SizeOnDisc property Pin
Eric Haddan12-Feb-10 9:50
Eric Haddan12-Feb-10 9:50 
GeneralRe: Silly question about SizeOnDisc property Pin
G_Hosa_Phat12-Feb-10 10:22
professionalG_Hosa_Phat12-Feb-10 10:22 
GeneralRe: Silly question about SizeOnDisc property Pin
Kubin15-Feb-11 4:02
Kubin15-Feb-11 4:02 
GeneralDefinite threading bugs [modified] Pin
Mount24-Jan-10 1:40
Mount24-Jan-10 1:40 
GeneralRe: Definite threading bugs Pin
Eric Haddan25-Jan-10 14:52
Eric Haddan25-Jan-10 14:52 
GeneralRe: Definite threading bugs Pin
Mount28-Jan-10 1:11
Mount28-Jan-10 1:11 
GeneralWindows 7 Pin
mbks5022-Jan-10 6:42
mbks5022-Jan-10 6:42 
GeneralRe: Windows 7 Pin
Eric Haddan22-Jan-10 7:54
Eric Haddan22-Jan-10 7:54 
GeneralRe: Windows 7 Pin
mbks5023-Jan-10 10:43
mbks5023-Jan-10 10:43 
GeneralRe: Windows 7 Pin
Eric Haddan23-Jan-10 10:59
Eric Haddan23-Jan-10 10:59 
GeneralDVD Video Pin
mbks5011-Jan-10 14:39
mbks5011-Jan-10 14:39 
GeneralRe: DVD Video Pin
Eric Haddan12-Jan-10 9:43
Eric Haddan12-Jan-10 9:43 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.