Burn CDs in C++ and C# With ICDBurn - ICDBurn in C and C (
Page 2 of 4 )
ICDBurn and C
Developers typically access ICDBurn and other COM interfaces from C++, and .NET languages such as C# or VB.NET. Although the
C language isn't used as much, who knows if you'll ever have to access ICDBurn from C. For this reason, my first ICDBurn
demo, cburnd, demonstrates C-based access to this COM interface. Listing 1 presents cburnd's source
code.
I developed Listing 1 using Borland C++ 5.5.1. Despite its age (this C++ compiler dates back to 2000), Borland C++ 5.5.1 is
free to download and is still very useful. For information on how to download, install, and use this product, check out my
InformIT article, Build Screensavers with a Custom
Screensavers Library in Borland C++.
ADVERTISEMENT
Following comments, #include preprocessor instructions, and a #define _UNICODE instruction that
selects the wide-character version of SHGetFolderPath() when not commented out, Listing 1 specifies
#define CSIDL_CDBURN_AREA 0x3b. This instruction is present because none of Borland C++ 5.5.1's header files
specify a CSIDL constant for the staging area -- version 5.5.1 predates IMAPI.
ICDBurn's binary implementation is accessed at the source code level via a pair of structs: ICDBurn and
ICDBurnVtbl. ICDBurn provides a solitary lpVtbl member that initializes to the address
of the implementation's array of function pointers. The prototypes for these function pointers are specified by
ICDBurnVtbl.
The ordering of function pointer prototypes within ICDBurnVtbl isn't arbitrary: It must match the ordering of
the actual pointers within the COM interface, and begin with those pointers specified by base interfaces. Because IUnknown is
ICDBurn's only base interface, ICDBurnVtbl begins with IUnknown's QueryInterface(),
AddRef(), and Release() function pointer prototypes.
Note
Each function pointer prototype includes the __stdcall keyword, which signifies that ICDBurn's functions obey
the same calling convention as the Win32 API. Arguments are pushed onto the stack in a right-argument-to-left-argument
sequence. Also, the called function takes on the task of cleaning up the stack.
After initializing COM via a call to CoInitialize(), cburnd creates an instance of the shell
extension class in terms of the ICDBurn interface by invoking CoCreateInstance(). This function requires the
GUIDs for both the shell extension class and ICDBurn, and these GUIDs are made available to CoCreateInstance()
via CLSID and IID structure pointers.
Assuming that CoCreateInstance() succeeds, it initializes pICDBurn to point to the
lpVtbl pointer to the array of ICDBurn function pointers. As a result, ICDBurn's functions are called via two
levels of indirection: pICDBurn->lpVtbl->. Also, pICDBurn's pointer value is passed to the
function as its first argument.
Unlike SHGetFolderPath(), which returns an array of wide or non-wide characters (depending on the presence or
absence of the #define _UNICODE directive, shown earlier in the source code),
GetRecorderDriveLetter() always returns an array of wide characters. Also, cburnd passes a
NULL argument to Burn() to indicate that there is no parent window.
After calling the three ICDBurn-specific functions and outputting appropriate success or failure messages,
cburnd explicitly releases the shell extension class instance by invoking
pICDBurn->lpVtbl->Release(pICDBurn). This program then terminates COM by invoking
CoUninitialize().
Note
If you'd like to learn more about how a COM object is accessed from the C language, I recommend checking out Jeff Glatt's
excellent COM in plain C article, which is hosted by
The Code Project.
I used Borland C++ 5.5.1's bcc32.exe program to compile and link the demo. For example, assuming that
cburnd.c is stored in a c:\borland\bcc55\projects\cburnd directory, and assuming that this
directory is current, bcc32 -I..\..\include -L..\..\lib cburnd.c creates a cburnd.exe file in the
current directory.
When I run cburnd.exe on my platform, I observe the following output:
SHGetFolderPath return value = C:\Documents and Settings\Jeff Friesen\Local Settings\
Application Data\Microsoft\CD Burning
HasRecordableDrive return value = 1
GetRecorderDriveLetter return value = D:\
Figure 1: You must copy files and/or directories to the staging area before you can use the wizard.
Figure 1 reveals that you can't interact with the wizard until at least one file or directory has been placed in the staging
area. For example, you can accomplish this task by activating a desktop shortcut's context menu and selecting the Send
To menu item, further selecting the CD burner from the resulting popup menu. In response, you'll observe a notification
window similar to that shown in Figure 2.
Figure 2: XP presents a notification window via the system tray when content is placed in the staging area.
ICDBurn and C++
Accessing ICDBurn from C++ is similar to accessing this interface from C. Unlike C, however, you don't pass a "this" pointer
to and you don't specify the "virtual table" pointer when invoking the interface's functions. Perhaps the biggest difference
involves using a class to specify the interface. Listing 2 presents C++ source code to a cppburnd demo that
reveals these and other differences.
Listing 2's ICDBurn class declaration equates to the pair of structs presented in Listing 1. The compiler
automatically creates a function pointer array for all class functions marked with the virtual keyword, and this
array begins with functions inherited from the IUnknown base class, which is declared via one of Borland C++
5.5.1's header files.
Note
Each function is marked "pure virtual" via the =0 assignment to signify that ICDBurn is abstract
(an interface).
Other differences between Listings 2 and 1 include my arbitrary use of STDMETHODCALLTYPE (which is equivalent to
__stdcall), and not requiring CLSID_CDBurn and IID_ICDBurn to be prefixed with
& when passed to CoCreateInstance(). This isn't necessary because the C++ version of this
function takes advantage of references instead of pointers for these arguments.
Once again, I used Borland C++ 5.5.1's bcc32.exe program to compile and link the demo. For example, assuming
that cppburnd.cpp is stored in a c:\borland\bcc55\projects\cppburnd directory, and assuming that
this directory is current, bcc32 -I..\..\include -L..\..\lib cppburnd.cpp creates a cppburnd.exe
file in the current directory.
When I run cppburnd.exe on my platform, I observe the following output -- there's at least one file in the
staging area:
SHGetFolderPath return value = C:\Documents and Settings\Jeff Friesen\Local Settings\
Application Data\Microsoft\CD Burning
HasRecordableDrive return value = 1
GetRecorderDriveLetter return value = D:\
Figure 3: The wizard initially prompts the user for a suitable label, using the current date as the default.