MSDN Magazine > Issues and Downloads > 2004 > April >  C++ Q&A: CD Burning and Device Discovery with I...
C++ Q&A
CD Burning and Device Discovery with IMAPI
Paul DiLascia

Code download available at: CQA0404.exe (149 KB)
Browse the Code Online

Q I read your January 2004 column about getting the drive letter and about using the GetDriveType function to find out if a disk drive is a CD (DRIVE_CDROM), but how can I know if the CD drive is a recording device? Also, how do I write files to a recordable CD? Windows® XP lets you drag and drop files using Windows Explorer to write files to a CD. Can you tell me if there is a way I can do this from my program without requiring my users to purchase third-party software such as Nero or Roxio? Are there any MFC classes that do this?
Q I read your January 2004 column about getting the drive letter and about using the GetDriveType function to find out if a disk drive is a CD (DRIVE_CDROM), but how can I know if the CD drive is a recording device? Also, how do I write files to a recordable CD? Windows® XP lets you drag and drop files using Windows Explorer to write files to a CD. Can you tell me if there is a way I can do this from my program without requiring my users to purchase third-party software such as Nero or Roxio? Are there any MFC classes that do this?
Bill Fenton

A MFC doesn't have any CD-burning classes, but Windows XP provides built-in support for writing to CD. If all you want to do is copy some files and folders, you can use the shell's ICDBurn interface. If you want to record audio or have more detailed control over recorders, there's a special API, which I'll discuss shortly.
A MFC doesn't have any CD-burning classes, but Windows XP provides built-in support for writing to CD. If all you want to do is copy some files and folders, you can use the shell's ICDBurn interface. If you want to record audio or have more detailed control over recorders, there's a special API, which I'll discuss shortly.
ICDBurn has three methods. HasRecordableDrive scans the system for a CD drive with write capability, returning TRUE if one is found. GetRecorderDriveLetter retrieves the drive letter of a CD drive which has been marked as write enabled. And finally, Burn instructs Windows to copy data from the "staging area" to a writable CD. The staging area is a special folder that's usually "%userprofile%\Local Settings\Application Data\Microsoft\CD Burning", but you should query for the exact name by calling SHGetFolderPath with the special code CSIDL_CDBURN_AREA. You should always use SHGetFolderPath because users can manually change the location of their burn folder in the registry or by using a tool like PowerTools/TweakUI. For example, on my machine, I set my burn path to "C:\TEMP\Burning".
I wrote a little class called CCDBurn to encapsulate ICDBurn. The constructor calls CoCreateInstance with CLSID_CDBurn, so you can just instantiate and go:
CCDBurn burner;
if (!burner.HasRecordableDrive()) {
   printf("Oops—No recordable drive!\n");
} else {
   CString dl = 
       burner.GetRecorderDriveLetter();
   printf("Default Recorder drive letter = 
       %s\n", (LPCTSTR)dl);
}
The drive letter is the one that has Enable CD recording on this drive checked in the drive's Recording Properties (see Figure 1). Only one drive can have this property set at any given time. Assuming HasRecordableDrive returns TRUE—that is, assuming the computer has at least one recordable CD drive—all you have to do to write files to it is copy the files to the staging area, then call Burn. Since I'm so nice, I added another method, GetBurnFolderPath, that calls SHGetSpecialFolderPath to get the burn folder path in a CString, as shown in the following line of code:
CString path = burner.GetBurnFolderPath();
Figure 1 CD Drive Recording Properties 
What if you want to record music, find other recorders (there can be more than one), or get detailed information such as the make and model or whether it's a CD-R or CD-RW? For that, Windows XP has IMAPI. That's Image Mastering API, not to be confused with MAPI, the Messaging API used for e-mail. Gosh, it's hard to keep up with all the acronyms! IMAPI provides COM interfaces to query recorders and write data or the latest Radiohead tunes to your shiny plastic disc. Figure 2 gives the big picture overview.

Interface System Description
ICDBurn shell Simple interface for writing files to CD. Burn method copies staging area to CD.
IDiscMaster IMAPI Controls an IMAPI session. Open, close, enumerate, and select recorders. Burn data or music.
IDiscRecorder IMAPI Provides access to a single CD recorder connected to the system.
IRedbookDiscMaster IMAPI Provides access to audio (redbook) data format.
IJolietDiscMaster IMAPI Provides access to file system (Joliet) data format.
IDiscMasterProgressEvents IMAPI Interface you can implement to receive feedback about burn progress.
Since coping with COM can cause great consternation, I went all out and wrote a little class library, IMAPITools, to encapsulate most of the nasties. I also wrote a program, CDINFO, that shows how to use it. CDINFO displays information about CD recorders in a console window. You can download the source code from the link at the top of this article. Figure 3 shows it running. CDINFO shows that my home computer has two recorders, a Sony and a Pioneer, and my default drive is at K:. CDINFO shows how to get information about recorders, but it doesn't actually burn data. Recording isn't hard once you get the hang of IMAPI, but it's still too much for one little C++ column so I'll touch on this subject only briefly after describing the basics.
Figure 3 CDINFO 
First, CDINFO creates a CCDBurn object to display the drive letter and burn path. Next, it creates a CDiscMaster to open an IMAPI session:
  CDiscMaster dm; // create IDiscMaster
  if (!dm.Open()) {
     printf("Oops: ...");
     return;
  }
CDiscMaster encapsulates the first and main IMAPI interface, IDiscMaster. Its constructor calls CoCreateInstance to create the Microsoft MSDiscMasterObj object and get its IDiscMaster interface. IDiscMaster (see Figure 4) has methods to enumerate formats and recorders, select the active recorder, and so on. The underlying object also implements two other interfaces: IJolietDiscMaster and IRedbookDiscMaster (see Figure 5).

IDiscMaster Method Description
Open Opens an IMAPI object
EnumDiscMasterFormats Retrieves a format enumerator
GetActiveDiscMasterFormat Retrieves the currently selected recorder format
SetActiveDiscMasterFormat Sets a new active recorder format
EnumDiscRecorders Retrieves a recorder enumerator
GetActiveDiscRecorder Retrieves the active disc recorder format
SetActiveDiscRecorder Selects a new active disc recorder
ClearFormatContent Clears the contents of an unburned image
ProgressAdvise Registers for progress notifications
ProgressUnadvise Cancels progress notifications
RecordDisc Burns the staged image to the active recorder
Close Closes the interface
IDiscRecorder Method Description
Init Initializes the object for an underlying device
GetRecorderGUID Retrieves the underlying device GUID
GetRecorderType Identifies the device as CD-R or CD-RW
GetDisplayNames Retrieves a name suitable for GUI display
GetBasePnPID Returns an identifier unique to the device class
GetPath Returns an OS path to the device
GetRecorderProperties Retrieves a pointer to the IPropertyStorage interface for the recorder
SetRecorderProperties Sets properties for the recorder
GetRecorderState Checks if the recorder is ready to burn
OpenExclusive Opens a device for exclusive use
QueryMediaType Identifies the type of media in the recorder
QueryMediaInfo Retrieves media properties
Eject Ejects a recorder's tray, if possible
Erase Erases CD-RW media, if possible
Close Closes a recorder after exclusive access
Figure 5 MSDiscMasterObj Interfaces 
CDs come in two basic flavors: CD-Audio for audio and CD-ROM for files. The respective formats are known as Redbook and Joliet. Redbook is the original music format designed by Philips and Sony in 1980 as the "red book" standard; Joliet is a CD-ROM file format designed by Microsoft as an extension of the ISO-9660 format. Microsoft designed Joliet about the time of Windows 95 in order to extend ISO-9660 to allow long file names and deeper directory hierarchies.
The history of CDs is actually quite fascinating. CD-ROMs became successful on the coattails of the music industry. The volume of music sales drove the manufacturing cost down to a point where it became affordable to use CDs for data. The key factor was that CDs, like LPs, can be stamped. An entire disc is recorded in a single instant, which makes it cheap to produce in large quantities. The fixed cost of a stamping facility was quite high (in the millions) but justified by this new profitability. Once the facilities were already built, it cost nothing extra to stamp a few CD-ROMs now and then, so CD-ROMs piggybacked on music sales. Now, of course, anyone can buy a recorder for $100—but CD-ROMs still suffer from their audio heritage in the form of slow performance because data is stored in a single linear groove (like LPs) that winds around the disc. A different optical technology, called magneto-optical disc, uses a track/sector format to provide much faster access.
OK, so much for the history lesson. Back to reality: to find out which formats your recorder supports, you can call IDiscMaster::EnumDiscMasterFormats. This requires all the suffering of COM enumerators, so I encapsulated it in a simple method, CDiscMaster::GetSupportedFormats, that returns the formats as an array of IID, as shown here:
const MAXNFORMATS = 2;
IID fmts[MAXNFORMATS];
int nFormats = dm.GetSupportedFormats(fmts,MAXNFORMATS);
The fmts array will contain the IIDs identifying the formats, IID_IRedbookDiscMaster and IID_IJoiletDiscMaster, and you'll never have to deal with IEnumDiscMasterFormats. You can use the interface IDs to QueryInterface your IDiscMaster or call Get/SetActiveDiscMasterFormat. Practical programmers may rightly wonder why the designers of IMAPI chose such a complex API to obtain the supported formats when there are only two. A single enum code provides more than adequate bandwidth to pass this information. Only the friendly Redmondtonians know the answer. Perhaps they were expecting someone to write an audio recorder in Visual Basic®? In any case, if you use IMAPITools, you can forgo COM enumerators.
Once you've opened a session, you can query individual recorders. Once again, IMAPI uses COM to enumerate recorders and once again I've hidden the mechanics, this time using an enumerator class to make life swell:
CDiscRecorder dr;
CDiscRecorderIterator itr(dm); // dm=CDiscMaster
while (itr.Next(dr)) {
 // do something
}
Pretty easy, huh? Each time you call Next, the iterator fetches the next recorder into CDiscRecorder. CDiscRecorder encapsulates the other big IMAPI interface, IDiscRecorder, which represents a recordable CD device. IDiscRecorder provides methods to open the recorder, query its type (CD-R or CD-RW) and path name, get device-specific properties, eject the CD, and so on. Figure 4 lists the IDiscRecorder methods. CDINFO shows how to use CDiscRecorder to get all sorts of information about recorders. The code is mostly straightforward so I'll spare you the details. Download the source if you want the full scoop.
One thing I left out of CDINFO is code to actually burn data, so let me at least give you a brief overview of how you'd do it. To write to disc, you need either IJolietDiscMaster or IRedbookDiscMaster, which you get by QueryInterface-ing your IDiscMaster. Or you can use IMAPITools:
dm.SetActiveDiscRecorder(dr); // select recorder
CJolietDiscMaster jdm(dm); // get joliet interface
Now jdm holds the IJolietDiscMaster interface and you can call any of the IJolietDiscMaster methods. AddData is the one to write data; it requires a COM IStorage pointer, which is a pain for simple purposes like files but provides utmost generality since your data can come from anywhere in the known universe as long as you have an IStorage for it. Recording audio is similar, except you use IRedbookDiscMaster and AddAudioTrackBlocks to add raw audio data (44.1 KHz, 16-bit RAW, same as in WAV files). You can create multiple audio tracks with Create/CloseAudioTrack. AddData and AddAudioTrackBlocks don't actually write to disc; they write to the staging area. To really move the bits, you have to call RecordDisc, as shown here:
BOOL bSimulate=FALSE;
BOOL bEjectAfterBurn=TRUE;
dm.RecordDisc(bSimulate, bEjectAfterBurn);
dm.Close();
You can call RecordDisc with bSimulate=TRUE to simulate the burn without actually writing. Windows goes through its entire pre-flight checklist and burning conniptions, doing everything it would normally do except actually writing. This lets you test and debug your software without running through a pile of CDs or waiting for hours while the laser burns the tiny plastic pits.
Well!—That was quite a whirlwind tour of IMAPI, but it should be enough to get you started. Most of you will probably never need to call IMAPI unless you're writing a sophisticated backup program or audio recorder. For the everyday purpose of copying files, all you need is ICDBurn. But even if all you want to do is display a list of recordable drives, you'll need IMAPI. In that case I encourage you to download CDINFO and IMAPITools, which provide more details in the form of working code. And if the code doesn't work, then I must not have written it—Happy programming!

Send your questions and comments for Paul to  cppqa@microsoft.com.


Paul DiLascia is a freelance writer, consultant, and Web/UI designer-at-large. He is the author of Windows++: Writing Reusable Windows Code in C++ (Addison-Wesley, 1992). Paul can be reached at http://www.dilascia.com.

Page view tracker