Creating a CD Player in C#, Part 2: The CD Functions

CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read