Techniques - DevSource
DevSource: Microsoft Developer Resource DevSource Home Sponsored by Microsoft Home Add Ons Architecture Languages Techniques Using VS Forums
Home arrow Techniques arrow Page 2 - Burn CDs in C++ and C# With ICDBurn
Burn CDs in C++ and C# With ICDBurn
By Jeff Friesen

Rate This Article: Add This Article To:

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.

Listing 1: cburnd.c

// cburnd.c

// C-based ICDBurn demonstration (Borland C++ 5.5.1).

//#define _UNICODE

#include <stdio.h>
#include <shlobj.h>
#include <tchar.h>

#define CSIDL_CDBURN_AREA 0x3b

typedef struct
{
   const struct ICDBurnVtbl *lpVtbl;
}
ICDBurn;

typedef struct ICDBurnVtbl
{
   HRESULT (__stdcall *QueryInterface) (ICDBurn *this, const IID *riid,
                                        void **ppd);
   ULONG (__stdcall *AddRef) (ICDBurn *this);
   ULONG (__stdcall *Release) (ICDBurn *this);

   HRESULT (__stdcall *GetRecorderDriveLetter) (ICDBurn *this, LPWSTR pszDrive,
                                                UINT cch);
   HRESULT (__stdcall *Burn) (ICDBurn *this, HWND hwnd);
   HRESULT (__stdcall *HasRecordableDrive) (ICDBurn *this, BOOL *pfHasRecorder);
}
ICDBurnVtbl;

CLSID CLSID_CDBurn =
{
   0xfbeb8a05, 0xbeee, 0x4442, { 0x80, 0x4e, 0x40, 0x9d, 0x6c, 0x45, 0x15,
   0xe9 }
};

IID IID_ICDBurn =
{
   0x3d73a659, 0xe5d0, 0x4d42, {0xaf, 0xc0, 0x51, 0x21, 0xba, 0x42, 0x5c,
   0x8d }
};

void main (void)
{
   HRESULT hr;
   ICDBurn *pICDBurn;
   TCHAR szPath [MAX_PATH];

   if (FAILED (SHGetFolderPath (NULL, CSIDL_CDBURN_AREA, NULL,
                                SHGFP_TYPE_CURRENT, szPath)))
       fprintf (stderr, "cburnd: SHGetFolderPath() failure\n");
   else
       _tprintf (_T ("SHGetFolderPath return value = %s\n"), szPath);

   if (FAILED (CoInitialize (NULL)))
   {
       fprintf (stderr, "cburnd: CoInitialize() failure\n");
       return;
   }

   hr = CoCreateInstance (&CLSID_CDBurn, NULL, CLSCTX_INPROC_SERVER,
                          &IID_ICDBurn, (LPVOID *) &pICDBurn);
   if (SUCCEEDED (hr))
   {
       WCHAR driveLetter [4];
       BOOL hasRecorder = FALSE;

       if (FAILED (pICDBurn->lpVtbl->HasRecordableDrive (pICDBurn,
                                                         &hasRecorder)))
           fprintf (stderr, "cburnd: HasRecordableDrive() failure\n");
       else
           printf ("HasRecordableDrive return value = %d\n", hasRecorder);

       if (hasRecorder)
       {
           if (FAILED (pICDBurn->lpVtbl->GetRecorderDriveLetter (pICDBurn,
                                                                 driveLetter,
                                                                 4)))
               fprintf (stderr, "cburnd: GetRecorderDriveLetter() failure\n");
           else
               wprintf (L"GetRecorderDriveLetter return value = %s\n",
                        driveLetter);

           if (FAILED (pICDBurn->lpVtbl->Burn (pICDBurn, NULL)))
               fprintf (stderr, "cburnd: Burn() failure\n");
       }

       pICDBurn->lpVtbl->Release (pICDBurn);
   }
   else
       fprintf (stderr, "cburnd: CoCreateInstance() failure\n");

   CoUninitialize ();
}
Note
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: cppburnd.cpp

// cppburnd.cpp

// C++-based ICDBurn demonstration (Borland C++ 5.5.1).

//#define _UNICODE

#include <iostreams.h>
#include <shlobj.h>

#define CSIDL_CDBURN_AREA 0x3b

class ICDBurn : public IUnknown
{
   public:

   virtual HRESULT STDMETHODCALLTYPE
     GetRecorderDriveLetter (LPWSTR pszDrive, UINT cch) = 0;

   virtual HRESULT STDMETHODCALLTYPE
     Burn (HWND hwnd) = 0;

   virtual HRESULT STDMETHODCALLTYPE
     HasRecordableDrive (BOOL *pfHasRecorder) = 0;
};

CLSID CLSID_CDBurn =
{
   0xfbeb8a05, 0xbeee, 0x4442, { 0x80, 0x4e, 0x40, 0x9d, 0x6c, 0x45, 0x15,
   0xe9 }
};

IID IID_ICDBurn =
{
   0x3d73a659, 0xe5d0, 0x4d42, { 0xaf, 0xc0, 0x51, 0x21, 0xba, 0x42, 0x5c,
   0x8d }
};

void main (void)
{
   HRESULT hr;
   ICDBurn *pICDBurn;
   TCHAR szPath [MAX_PATH];

   if (FAILED (SHGetFolderPath (NULL, CSIDL_CDBURN_AREA, NULL,
                                SHGFP_TYPE_CURRENT, szPath)))
       cerr << "cppburnd: SHGetFolderPath() failure" << endl;
   else
       wcout << L"SHGetFolderPath return value = " << szPath << endl;

   if (FAILED (CoInitialize (NULL)))
   {
       cerr << "cppburnd: CoInitialize() failure" << endl;
       return;
   }

   hr = CoCreateInstance (CLSID_CDBurn,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_ICDBurn,
                          (LPVOID *) &pICDBurn);
   if (SUCCEEDED (hr))
   {
       WCHAR driveLetter [4];
       BOOL hasRecorder = FALSE;

       if (FAILED (pICDBurn->HasRecordableDrive (&hasRecorder)))
           cerr << "cppburnd: HasRecordableDrive() failure" << endl;
       else
           cout << "HasRecordableDrive return value = " << hasRecorder << endl;

       if (hasRecorder)
       {    
           if (FAILED (pICDBurn->GetRecorderDriveLetter (driveLetter, 4)))
               cerr << "cppburnd: GetRecorderDriveLetter() failure" << endl;
           else
               wcout << L"GetRecorderDriveLetter return value = " << driveLetter
                     << endl;

           if (FAILED (pICDBurn->Burn (NULL)))
               cerr << "cppburnd: Burn() failure" << endl;
       }

       pICDBurn->Release ();
   }
   else
       cerr << "cppburnd: CoCreateInstance() failure" << endl;

   CoUninitialize ();
}

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.



 
 
>>> More Techniques Articles          >>> More By Jeff Friesen
 



Microsoft's Future: A Chat With Their CTO, Barry Briggs

Play Video >

All Videos >

Julia explores the Robotics Studio!

Read now >

Messages to Bill Gates!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.