Introduction
Hello and welcome to the second installment of this series. Now that we have the Windows API functions needed to read and write to a CD and its pits, we need the physical CD functions. This is what you’ll be doing today.
If you haven’t checked out Part 1, “Creating a CD Player in C#, Part 1: The Windows Functions,” of this series yet, I suggest you do so now.
Add a class to your project and name it CDDrive.cls.
Add the next namespaces and class to it.
using System;
using System.Runtime.InteropServices;
public class clsCDBuffer
{
byte[] arrBuffer;
int intPos = 0;
public clsCDBuffer(byte[] aBuffer)
{
arrBuffer = aBuffer;
}
public void ReadData(object sender, DataReadEventArgs e)
{
Buffer.BlockCopy(e.Data, 0, arrBuffer, intPos,
(int)e.DataSize);
intPos += (int)e.DataSize;
}
}
This will keep the data that was read.
Inherit from IDisposable and add the following variables to your CDDrive class.
public class CDDrive : IDisposable
{
private IntPtr ptrCD;
private bool blnTOC = false;
private Win32API.CDROM_TOC TOC = null;
private char chrDrive = '';
private DeviceChangeNotificationWindow dnNotify = null;
public event EventHandler Inserted;
public event EventHandler Removed;
protected const int NSECTORS = 13;
protected const int CB_CDDASECTOR = 2368;
protected const int CB_CDROMSECTOR = 2048;
protected const int CB_QSUBCHANNEL = 16;
protected const int CB_AUDIO = (CB_CDDASECTOR -
CB_QSUBCHANNEL);
public CDDrive()
{
TOC = new Win32API.CDROM_TOC();
ptrCD = IntPtr.Zero;
}
public void Dispose()
{
Close();
GC.SuppressFinalize(this);
}
Add the main events to deal with the opening and closing of the CD drive.
public bool Open(char drive)
{
Close();
if (Win32API.GetDriveType(drive + ":\") ==
Win32API.DriveTypes.DRIVE_CDROM)
{
ptrCD = Win32API.CreateFile("\\.\" + drive + ':',
Win32API.GENERIC_READ, Win32API.FILE_SHARE_READ,
IntPtr.Zero, Win32API.OPEN_EXISTING, 0,
IntPtr.Zero);
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
chrDrive = drive;
dnNotify = new DeviceChangeNotificationWindow();
dnNotify.DeviceChange += new
DeviceChangeEventHandler(DeviceChange);
return true;
}
else
{
return true;
}
}
else
{
return false;
}
}
public void Close()
{
UnLock();
if (dnNotify != null)
{
dnNotify.DestroyHandle();
dnNotify = null;
}
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
Win32API.CloseHandle(ptrCD);
}
ptrCD = IntPtr.Zero;
chrDrive = '';
blnTOC = false;
}
private void OnInserted()
{
if (Inserted != null)
{
Inserted(this, EventArgs.Empty);
}
}
private void OnRemoved()
{
if (Removed != null)
{
Removed(this, EventArgs.Empty);
}
}
private void DeviceChange(object sender,
DeviceChangeEventArgs e)
{
if (e.Drive == chrDrive)
{
blnTOC = false;
switch (e.ChangeType)
{
case DeviceChangeEventType.DeviceInserted:
OnInserted();
break;
case DeviceChangeEventType.DeviceRemoved:
OnRemoved();
break;
}
}
}
public bool UnLock()
{
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
uint Dummy = 0;
Win32API.PREVENT_MEDIA_REMOVAL pmr = new
Win32API.PREVENT_MEDIA_REMOVAL();
pmr.PreventMediaRemoval = 0;
return Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_STORAGE_MEDIA_REMOVAL, pmr,
(uint)Marshal.SizeOf(pmr), IntPtr.Zero, 0,
ref Dummy, IntPtr.Zero) != 0;
}
else
{
return false;
}
}
public bool Opened
{
get
{
return ((int)ptrCD != -1) && ((int)ptrCD != 0);
}
}
public bool Load()
{
blnTOC = false;
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
uint Dummy = 0;
return Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_STORAGE_LOAD_MEDIA, IntPtr.Zero, 0,
IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0;
}
else
{
return false;
}
}
public bool Eject()
{
blnTOC = false;
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
uint Dummy = 0;
return Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_STORAGE_EJECT_MEDIA, IntPtr.Zero, 0,
IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0;
}
else
{
return false;
}
}
public bool Ready()
{
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
uint Dummy = 0;
if (Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_STORAGE_CHECK_VERIFY, IntPtr.Zero, 0,
IntPtr.Zero, 0, ref Dummy, IntPtr.Zero) != 0)
{
return true;
}
else
{
blnTOC = false;
return false;
}
}
else
{
blnTOC = false;
return false;
}
}
public uint TrackSize(int track)
{
uint Size = 0;
ReadTrack(track, null, ref Size, null);
return Size;
}
public int GetTracks()
{
if (blnTOC)
{
return TOC.LastTrack - TOC.FirstTrack + 1;
}
else return -1;
}
Add the code to read whatever is on the inserted CD.
protected bool ReadSector(int sect, byte[] bBuffer,
int iSectors)
{
if (blnTOC && ((sect + iSectors) <=
EndSector(TOC.LastTrack)) && (bBuffer.Length >=
CB_AUDIO * iSectors))
{
Win32API.RAW_READ_INFO rri = new
Win32API.RAW_READ_INFO();
rri.TrackMode = Win32API.TRACK_MODE_TYPE.CDDA;
rri.SectorCount = (uint)iSectors;
rri.DiskOffset = sect * CB_CDROMSECTOR;
uint uiRead = 0;
if (Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_CDROM_RAW_READ, rri,
(uint)Marshal.SizeOf(rri), bBuffer, (uint)iSectors *
CB_AUDIO, ref uiRead, IntPtr.Zero) != 0)
{
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
public int ReadTrack(int itrack, byte[] Data,
ref uint DataSize, uint StartSecond, uint SecondsRead,
CdReadProgressEventHandler ProgressEvent)
{
if (blnTOC && (itrack >= TOC.FirstTrack) &&
(itrack <= TOC.LastTrack))
{
int StartSect = StartSector(itrack);
int EndSect = EndSector(itrack);
if ((StartSect += (int)StartSecond * 75) >= EndSect)
{
StartSect -= (int)StartSecond * 75;
}
if ((SecondsRead > 0) && ((int)(StartSect +
SecondsRead * 75) < EndSect))
{
EndSect = StartSect + (int)SecondsRead * 75;
}
DataSize = (uint)(EndSect - StartSect) * CB_AUDIO;
if (Data != null)
{
if (Data.Length >= DataSize)
{
clsCDBuffer BufferFiller = new clsCDBuffer(Data);
return ReadTrack(itrack, new
CdDataReadEventHandler(BufferFiller.ReadData),
StartSecond, SecondsRead, ProgressEvent);
}
else
{
return 0;
}
}
else
{
return 0;
}
}
else
{
return -1;
}
}
public int ReadTrack(int itrack, byte[] Data,
ref uint DataSize, CdReadProgressEventHandler
ProgressEvent)
{
return ReadTrack(itrack, Data, ref DataSize, 0, 0,
ProgressEvent);
}
public int ReadTrack(int itrack, CdDataReadEventHandler
DataReadEvent, uint StartSecond, uint SecondsRead,
CdReadProgressEventHandler ProgressEvent)
{
if (blnTOC && (itrack >= TOC.FirstTrack) &&
(itrack <= TOC.LastTrack) && (DataReadEvent
!= null))
{
int StartSect = StartSector(itrack);
int EndSect = EndSector(itrack);
if ((StartSect += (int)StartSecond * 75) >= EndSect)
{
StartSect -= (int)StartSecond * 75;
}
if ((SecondsRead > 0) && ((int)(StartSect +
SecondsRead * 75) < EndSect))
{
EndSect = StartSect + (int)SecondsRead * 75;
}
uint Bytes2Read = (uint)(EndSect - StartSect) *
CB_AUDIO;
uint BytesRead = 0;
byte[] Data = new byte[CB_AUDIO * NSECTORS];
bool Cont = true;
bool ReadOk = true;
if (ProgressEvent != null)
{
ReadProgressEventArgs rpa = new
ReadProgressEventArgs(Bytes2Read, 0);
ProgressEvent(this, rpa);
Cont = !rpa.CancelRead;
}
for (int sector = StartSect; (sector < EndSect) &&
(Cont) && (ReadOk); sector += NSECTORS)
{
int Sectors2Read = ((sector + NSECTORS) < EndSect)
? NSECTORS : (EndSect - sector);
ReadOk = ReadSector(sector, Data, Sectors2Read);
if (ReadOk)
{
DataReadEventArgs dra = new
DataReadEventArgs(Data, (uint)(CB_AUDIO *
Sectors2Read));
DataReadEvent(this, dra);
BytesRead += (uint)(CB_AUDIO * Sectors2Read);
if (ProgressEvent != null)
{
ReadProgressEventArgs rpa = new
ReadProgressEventArgs(Bytes2Read,
BytesRead);
ProgressEvent(this, rpa);
Cont = !rpa.CancelRead;
}
}
}
if (ReadOk)
{
return (int)BytesRead;
}
else
{
return -1;
}
}
else
{
return -1;
}
}
public int ReadTrack(int itrack, CdDataReadEventHandler
DataReadEvent, CdReadProgressEventHandler ProgressEvent)
{
return ReadTrack(itrack, DataReadEvent, 0, 0,
ProgressEvent);
}
public static char[] DriveLetters()
{
string res = "";
for (char c = 'C'; c <= 'Z'; c++)
{
if (Win32API.GetDriveType(c + ":") ==
Win32API.DriveTypes.DRIVE_CDROM)
{
res += c;
}
}
return res.ToCharArray();
}
protected int StartSector(int track)
{
if (blnTOC && (track >= TOC.FirstTrack) &&
(track <= TOC.LastTrack))
{
Win32API.TRACK_DATA td = TOC.TrackData[track - 1];
return (td.Address_1 * 60 * 75 + td.Address_2 * 75 +
td.Address_3) - 150;
}
else
{
return -1;
}
}
protected int EndSector(int track)
{
if (blnTOC && (track >= TOC.FirstTrack) &&
(track <= TOC.LastTrack))
{
Win32API.TRACK_DATA td = TOC.TrackData[track];
return (td.Address_1 * 60 * 75 + td.Address_2 * 75 +
td.Address_3) - 151;
}
else
{
return -1;
}
}
public bool Refresh()
{
if (Ready())
{
return ReadTOC();
}
else
{
return false;
}
}
protected bool ReadTOC()
{
if (((int)ptrCD != -1) && ((int)ptrCD != 0))
{
uint BytesRead = 0;
blnTOC = Win32API.DeviceIoControl(ptrCD,
Win32API.IOCTL_CDROM_READ_TOC, IntPtr.Zero, 0, TOC,
(uint)Marshal.SizeOf(TOC), ref BytesRead,
IntPtr.Zero) != 0;
}
else
{
blnTOC = false;
}
return blnTOC;
}
}
Add another class to handle all the events of the CD, and add the following code:
internal enum DeviceChangeEventType { DeviceInserted, DeviceRemoved };
public class DataReadEventArgs : EventArgs
{
private byte[] bData;
private uint uiSize;
public DataReadEventArgs(byte[] data, uint size)
{
bData = data;
uiSize = size;
}
public byte[] Data
{
get
{
return bData;
}
}
public uint DataSize
{
get
{
return uiSize;
}
}
}
public class ReadProgressEventArgs : EventArgs
{
private uint uiToRead;
private uint uiRead;
private bool bCancel = false;
public ReadProgressEventArgs(uint bytestoread, uint bytesread)
{
uiToRead = bytestoread;
uiRead = bytesread;
}
public uint BytesRead
{
get
{
return uiRead;
}
}
public bool CancelRead
{
get
{
return bCancel;
}
set
{
bCancel = value;
}
}
}
internal class DeviceChangeEventArgs : EventArgs
{
private DeviceChangeEventType dType;
private char cDrive;
public DeviceChangeEventArgs(char drive,
DeviceChangeEventType type)
{
cDrive = drive;
dType = type;
}
public char Drive
{
get
{
return cDrive;
}
}
public DeviceChangeEventType ChangeType
{
get
{
return dType;
}
}
}
public delegate void CdDataReadEventHandler(object sender,
DataReadEventArgs ea);
public delegate void CdReadProgressEventHandler(object sender,
ReadProgressEventArgs ea);
internal delegate void DeviceChangeEventHandler(object sender,
DeviceChangeEventArgs ea);
internal enum DeviceType : uint
{
DBT_DEVTYP_OEM = 0x00000000,
DBT_DEVTYP_DEVNODE = 0x00000001,
DBT_DEVTYP_VOLUME = 0x00000002,
DBT_DEVTYP_PORT = 0x00000003,
DBT_DEVTYP_NET = 0x00000004
}
internal enum VolumeChangeFlags : ushort
{
DBTF_MEDIA = 0x0001,
DBTF_NET = 0x0002
}
internal struct DEV_BROADCAST_HDR
{
public uint dbch_size;
public DeviceType dbch_devicetype;
uint dbch_reserved;
}
[StructLayout(LayoutKind.Sequential)]
internal struct DEV_BROADCAST_VOLUME
{
public uint dbcv_size;
public DeviceType dbcv_devicetype;
uint dbcv_reserved;
uint dbcv_unitmask;
public char[] Drives
{
get
{
string drvs = "";
for (char c = 'A'; c <= 'Z'; c++)
{
if ((dbcv_unitmask & (1 << (c - 'A'))) != 0)
{
drvs += c;
}
}
return drvs.ToCharArray();
}
}
public VolumeChangeFlags dbcv_flags;
}
internal class DeviceChangeNotificationWindow : NativeWindow
{
public event DeviceChangeEventHandler DeviceChange;
const int WS_EX_TOOLWINDOW = 0x80;
const int WS_POPUP = unchecked((int)0x80000000);
const int WM_DEVICECHANGE = 0x0219;
const int DBT_APPYBEGIN = 0x0000;
const int DBT_APPYEND = 0x0001;
const int DBT_DEVNODES_CHANGED = 0x0007;
const int DBT_QUERYCHANGECONFIG = 0x0017;
const int DBT_CONFIGCHANGED = 0x0018;
const int DBT_CONFIGCHANGECANCELED = 0x0019;
const int DBT_MONITORCHANGE = 0x001B;
const int DBT_SHELLLOGGEDON = 0x0020;
const int DBT_CONFIGMGAPI32 = 0x0022;
const int DBT_VXDINITCOMPLETE = 0x0023;
const int DBT_VOLLOCKQUERYLOCK = 0x8041;
const int DBT_VOLLOCKLOCKTAKEN = 0x8042;
const int DBT_VOLLOCKLOCKFAILED = 0x8043;
const int DBT_VOLLOCKQUERYUNLOCK = 0x8044;
const int DBT_VOLLOCKLOCKRELEASED = 0x8045;
const int DBT_VOLLOCKUNLOCKFAILED = 0x8046;
const int DBT_DEVICEARRIVAL = 0x8000;
const int DBT_DEVICEQUERYREMOVE = 0x8001;
const int DBT_DEVICEQUERYREMOVEFAILED = 0x8002;
const int DBT_DEVICEREMOVEPENDING = 0x8003;
const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
const int DBT_DEVICETYPESPECIFIC = 0x8005;
public DeviceChangeNotificationWindow()
{
CreateParams Params = new CreateParams();
Params.ExStyle = WS_EX_TOOLWINDOW;
Params.Style = WS_POPUP;
CreateHandle(Params);
}
private void OnCDChange(DeviceChangeEventArgs ea)
{
if (DeviceChange != null)
{
DeviceChange(this, ea);
}
}
private void OnDeviceChange(DEV_BROADCAST_VOLUME DevDesc,
DeviceChangeEventType EventType)
{
if (DeviceChange != null)
{
foreach (char ch in DevDesc.Drives)
{
DeviceChangeEventArgs a = new
DeviceChangeEventArgs(ch, EventType);
DeviceChange(this, a);
}
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DEVICECHANGE)
{
DEV_BROADCAST_HDR head;
switch (m.WParam.ToInt32())
{
case DBT_DEVICEARRIVAL:
head = (DEV_BROADCAST_HDR)Marshal.PtrToStructure
(m.LParam, typeof(DEV_BROADCAST_HDR));
if (head.dbch_devicetype ==
DeviceType.DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME DevDesc =
(DEV_BROADCAST_VOLUME)Marshal.PtrToStructure
(m.LParam, typeof(DEV_BROADCAST_VOLUME));
if (DevDesc.dbcv_flags ==
VolumeChangeFlags.DBTF_MEDIA)
{
OnDeviceChange(DevDesc,
DeviceChangeEventType.DeviceInserted);
}
}
break;
case DBT_DEVICEREMOVECOMPLETE:
head = (DEV_BROADCAST_HDR)Marshal.PtrToStructure
(m.LParam, typeof(DEV_BROADCAST_HDR));
if (head.dbch_devicetype ==
DeviceType.DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME DevDesc =
(DEV_BROADCAST_VOLUME)Marshal.PtrToStructure
(m.LParam, typeof(DEV_BROADCAST_VOLUME));
if (DevDesc.dbcv_flags == VolumeChangeFlags.DBTF_MEDIA)
{
OnDeviceChange(DevDesc,
DeviceChangeEventType.DeviceRemoved);
}
}
break;
}
}
base.WndProc(ref m);
}
Conclusion
In the next installment, we will finalize this series. Until then, stay in practise!