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

Introduction

Well, we’re going to create a simple CD player in C#. Be warned, though, that this is a lot of work. Therefore, this series being divided into three parts.

In this part, I am only going to concentrate on most of the Windows API functions that can be used to create the various aspects of the CD player in the background.

Let’s create a project quickly! Remember: This project will be used in the next parts as well, so be careful where you store it.

Open Visual Studio and create a new C# Windows project. Design your form to look like Figure 1:

Design
Figure 1: Design

Add a class and name it something like WINAPI, and add the following namespaces.

using System;
using System.Runtime.InteropServices;

This tells the compiler that we will be making use of the built-in Windows functions.

Add all the constants and variables.

   public const uint IOCTL_CDROM_READ_TOC = 0x00024000;
   public const uint IOCTL_STORAGE_CHECK_VERIFY = 0x002D4800;
   public const uint IOCTL_CDROM_RAW_READ = 0x0002403E;
   public const uint IOCTL_STORAGE_LOAD_MEDIA = 0x002D480C;
   public const uint IOCTL_STORAGE_EJECT_MEDIA = 0x002D4808;

   public const uint GENERIC_READ = 0x80000000;
   public const uint FILE_SHARE_READ = 0x00000001;
   public const uint OPEN_EXISTING = 3;

      public byte TrackNumber;
      public byte Reserved1;
      public byte Address_0;
      public byte Address_1;
      public byte Address_2;
      public byte Address_3;

These hold the values to be used with the API functions; these will follow soon. Add the Enumerations and structs now:

   public enum DriveTypes : uint
   {
      DRIVE_UNKNOWN = 0,
      DRIVE_NO_ROOT_DIR,
      DRIVE_REMOVABLE,
      DRIVE_FIXED,
      DRIVE_REMOTE,
      DRIVE_CDROM,
      DRIVE_RAMDISK
   };
   public enum TRACK_MODE_TYPE { YellowMode2, XAForm2, CDDA }
   [StructLayout(LayoutKind.Sequential)]
   public class RAW_READ_INFO
   {
      public long DiskOffset = 0;
      public uint SectorCount = 0;
      public TRACK_MODE_TYPE TrackMode = TRACK_MODE_TYPE.CDDA;
   }

   [StructLayout(LayoutKind.Sequential)]
   public class CDROM_TOC
   {
      public ushort Length;
      public byte FirstTrack = 0;
      public byte LastTrack = 0;

      public TrackDataList TrackData;

      public CDROM_TOC()
      {
         TrackData = new TrackDataList();
         Length = (ushort)Marshal.SizeOf(this);
      }
   }
   [StructLayout(LayoutKind.Sequential)]
   public class PREVENT_MEDIA_REMOVAL
   {
      public byte PreventMediaRemoval = 0;
   }

   [StructLayout(LayoutKind.Sequential)]
   public struct TRACK_DATA
   {
      public byte Reserved;
      private byte BitMapped;
      public byte Control
      {
         get
         {
            return (byte)(BitMapped & 0x0F);
         }
         set
         {
            BitMapped = (byte)((BitMapped & 0xF0) |
               (value & (byte)0x0F));
         }
      }
      public byte Adr
      {
         get
         {
            return (byte)((BitMapped & (byte)0xF0) >> 4);
         }
         set
         {
            BitMapped = (byte)((BitMapped & (byte)0x0F) |
               (value << 4));
         }
      }

   [StructLayout(LayoutKind.Sequential)]
   public class TrackDataList
   {

   public const int MAXIMUM_NUMBER_TRACKS = 100;

   [MarshalAs(UnmanagedType.ByValArray, SizeConst =
         MAXIMUM_NUMBER_TRACKS * 8)]
      private byte[] Data;
      public TRACK_DATA this[int Index]
      {
         get
         {
            if ((Index < 0) | (Index >= MAXIMUM_NUMBER_TRACKS))
            {
               throw new IndexOutOfRangeException();
            }
            TRACK_DATA res;
            GCHandle handle = GCHandle.Alloc(Data,
               GCHandleType.Pinned);
            try
            {
               IntPtr buffer = handle.AddrOfPinnedObject();
               buffer = (IntPtr)(buffer.ToInt32() + (Index *
                  Marshal.SizeOf(typeof(TRACK_DATA))));
               res = (TRACK_DATA)Marshal.PtrToStructure(buffer,
                  typeof(TRACK_DATA));
            }
            finally
            {
               handle.Free();
            }
            return res;
         }
      }

And now, the methods:

   [System.Runtime.InteropServices.DllImport("Kernel32.dll")]
   public extern static DriveTypes GetDriveType(string sDrive);

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]
   public extern static int CloseHandle(IntPtr hObject);
   public const uint IOCTL_STORAGE_MEDIA_REMOVAL = 0x002D4804;

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]

   public extern static int DeviceIoControl(IntPtr hDevice,
        uint IoControlCode,
     IntPtr lpInBuffer, uint InBufferSize,
     IntPtr lpOutBuffer, uint nOutBufferSize,
     ref uint lpBytesReturned,
     IntPtr lpOverlapped);

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]
   public extern static int DeviceIoControl(IntPtr hDevice, uint
         IoControlCode,
     IntPtr InBuffer, uint InBufferSize,
     [Out] CDROM_TOC OutTOC, uint OutBufferSize,
     ref uint BytesReturned,
     IntPtr Overlapped);

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]
   public extern static int DeviceIoControl(IntPtr hDevice,
         uint IoControlCode,
     [In] PREVENT_MEDIA_REMOVAL InMediaRemoval, uint InBufferSize,
     IntPtr OutBuffer, uint OutBufferSize,
     ref uint BytesReturned,
     IntPtr Overlapped);

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]
   public extern static int DeviceIoControl(IntPtr hDevice,
         uint IoControlCode,
      [In] RAW_READ_INFO rri, uint InBufferSize,
      [In, Out] byte[] OutBuffer, uint OutBufferSize,
      ref uint BytesReturned,
      IntPtr Overlapped);

   [System.Runtime.InteropServices.DllImport("Kernel32.dll",
      SetLastError = true)]
   public extern static IntPtr CreateFile(string FileName,
         uint DesiredAccess,
      uint ShareMode, IntPtr lpSecurityAttributes,
      uint CreationDisposition, uint dwFlagsAndAttributes,
      IntPtr hTemplateFile);

Conclusion

In the next installment, we will get into the meat of the necessary code to read CD tracks and identify pits. 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