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.