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 3 - 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#
( Page 3 of 4 )

ICDBurn and C#

In contrast to C and C++, accessing ICDBurn from C# involves COM Interop (that part of the CLR that allows COM and .NET objects to interact with each other). This technology is visible in Listing 3's csburnd.cs source code via the System.Runtime.InteropServices namespace, the ICDBurn interface definition, and more.

Listing 3: csburnd.cs

// csburnd.cs

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

class CSburnD
{
    [DllImport("shfolder.dll")]
    static extern int SHGetFolderPath(IntPtr hwndOwner, int nFolder, 
                                      IntPtr hToken, int dwFlags, 
                                      StringBuilder pszPath);

    const int CSIDL_CDBURN_AREA = 0x3B;

    const int SHGFP_TYPE_CURRENT = 0;

    public static void Main(string[] args)
    {
        StringBuilder szPath = new StringBuilder(1024);
        if (SHGetFolderPath((IntPtr)0, CSIDL_CDBURN_AREA, (IntPtr)0, 
            SHGFP_TYPE_CURRENT, szPath) != 0)
            Console.WriteLine ("SHGetFolderPath() failure");
        else
            Console.WriteLine("SHGetFolderPath return value = "+szPath);

        Guid CLSID_CDBurn = new Guid("fbeb8a05-beee-4442-804e-409d6c4515e9");

        Type t = Type.GetTypeFromCLSID(CLSID_CDBurn);
        if (t == null)
        {
            Console.WriteLine("ICDBurn not supported by OS");
            return;
        }

        ICDBurn iface = (ICDBurn)Activator.CreateInstance(t);
        if (iface == null)
        {
            Console.WriteLine("Unable to obtain interface");
            return;
        }

        bool hasRecorder = false;
        iface.HasRecordableDrive(ref hasRecorder);
        Console.WriteLine("HasRecordableDrive return value = "+hasRecorder);

        if (hasRecorder)
        {
            StringBuilder driveLetter = new StringBuilder(4);
            iface.GetRecorderDriveLetter(driveLetter, 4);
            Console.WriteLine("GetRecorderDriveLetter return value = "+
                              driveLetter);
            iface.Burn((IntPtr) 0);
        }
    }
}

[ComImport]
[Guid("3d73a659-e5d0-4d42-afc0-5121ba425c8d")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICDBurn
{
    void GetRecorderDriveLetter([MarshalAs(UnmanagedType.LPWStr)]
                                StringBuilder pszDrive, uint cch);
    void Burn(IntPtr hwnd);
    void HasRecordableDrive(ref bool HasRecorder);
}
ADVERTISEMENT

Before looking at how ICDBurn is accessed via COM Interop, let's look at how the staging area's location is obtained. Because I couldn't locate a staging area System.Environment.SpecialFolder enumeration item for use with C#'s System.Environment.GetFolderPath() method, Listing 3 relies on .NET's Platform Invoke technology to access SHGetFolderPath().

Platform Invoke allows managed code to call DLL-implemented unmanaged functions. This is indicated in Listing 3 via a declaration prefixed with the DllImport attribute, to identify the function's DLL (shfolder.dll). Because SHGetFolderPath() is a Win32-exported function, it's marked static and extern.

Regarding SHGetFolderPath()'s arguments, Platform Invoke's marshalling converts C#'s IntPtr type to the unmanaged HWND and HANDLE types, C#'s int type to the unmanaged int type, and C#'s StringBuilder to the unmanaged LPTSTR type. StringBuilder is needed because C# strings are immutable.

To access ICDBurn from C# source code, a .NET definition for this COM interface needs to be included in the C# build. One way to obtain this definition involves working with the .NET SDK's command-line-based Type Library Importer tool (TlbImp.exe). Because this tool requires a type library for ICDBurn, and because I don't have one available for this COM interface, I've manually specified this definition.

I've specified ICDBurn's definition as a C# interface (via the interface keyword), declaring the three interface member functions in the same order as they appear in the ICDBurn COM interface. I've also prefixed this interface with the ComImport, Guid, and InterfaceType attributes:

  • ComImport indicates that the attributed ICDBurn C# interface type was previously defined in COM.
  • Guid identifies ICDBurn's GUID.
  • InterfaceType indicates that the managed interface is IUnknown-only when exposed to COM. .NET automatically inserts IUnknown's three functions into the C# interface.

When invoking a COM interface's functions from C#, the CLR must map C# types to COM interface types. For the ICDBurn interface's Burn() and HasRecordableDrive() function declarations, the CLR maps IntPtr to HWND, and the single-byte bool to the four-byte BOOL -- ref causes the Boolean argument to be passed by reference.

For ICDBurn's GetRecorderDriveLetter() function declaration, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDrive tells the CLR to map pszDrive's StringBuilder type (needed because C# strings are immutable) to the LPWSTR type required by the actual function.

Now that ICDBurn has been defined as ICDBurn, this interface's functions can be accessed via COM Interop by obtaining a System.Guid for the shell folder extension class, by passing this object to System.Type's GetTypeFromCLSID() method to get the type associated with this class identifier, and by passing this object to System.Activator's CreateInstance() method.

Rather than download and install the freely available Microsoft Visual C# 2008 Express Edition to compile and build this demo, I used Microsoft Visual C# 2005 Express Edition, which I previously installed, to perform these tasks. When I run the demo 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 = True
GetRecorderDriveLetter return value = D:\

Figure 4: The wizard's second window lets you know if you've forgotten to insert a CD.



 
 
>>> 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.