10
\$\begingroup\$

I really liked the close tray program that CD-ROM drivers programs used to include in MS-DOS days. Since I live in a place where even getting an internship is impossible, I've decided to learn by myself.

I implemented this "Hello World" C# script to do that with a twist. It will check the optical drive status and if open, it will close it, otherwise it will tell you that a disk is present.

Note, I went through Stack Overflow and couldn't find a way to programmatically find if the tray is open. Another note, it will not close the tray on most laptops where such mechanism does not exist.

And finally, I used an online code beautifier for indentation.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using System.Management;

class Program { 
[DllImport("winmm.dll")] protected static extern int mciSendString(string Cmd, StringBuilder StrReturn, int ReturnLength, IntPtr HwndCallback);

  static void Main(string[] args) {

  ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT MediaLoaded FROM Win32_CDROMDrive");
  ManagementObjectCollection moc = searcher.Get();
  var enumerator = moc.GetEnumerator();
  if (!enumerator.MoveNext()) throw new Exception("No elements");
  ManagementObject obj = (ManagementObject) enumerator.Current;
  bool status = (bool) obj["MediaLoaded"];

  if (!status) {
    MessageBox.Show("The drive is either open or empty", "Optical Drive Status");
    mciSendString("set cdaudio door closed", null, 0, IntPtr.Zero);
  }
  else MessageBox.Show("The drive is closed and contains an optical media", "Optical Drive Status");
 }
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ Have you tested it on a computer with more than one CD drive? \$\endgroup\$ – user253751 Aug 25 '20 at 10:35
  • \$\begingroup\$ Excellent suggestion, I tried to connect my old IDE DVD to my PC to test it, but sadly Windows 10 is seeing it as a USB device, oddly PCs with a single optical drive are less common let alone two, but your comment is legit and hopefully I (or a kind soul) will be able to see the outcome. \$\endgroup\$ – user10191234 Aug 25 '20 at 16:59
11
\$\begingroup\$

Note that the Visual Studio C# code editor has an integrated code beautifier. Depending on your setup, it can be executed with different shortcut keys. Call it from the menu for the first time, so that you can see the active shortcut keys (it is Ctrl-E-D for me). Menu: Edit > Advanced > Format Document.

By using a technique called LINQ (Language INtegrated Query), you can get the management object easier. It uses extension methods from the namespace System.Linq. Therefore, you must include a using System.Linq; at the top of your code. Then you can query with

ManagementObject managementObject = moc.FirstOrDefault();
if (managementObject == null) {
     // Handle error (output text or throw exception)
     Console.WriteLine("Could not find a management object!");
} else {
     ...
}

This FirstOrDefault extension method calls GetEnumerator and MoveNext internally and returns null if no object is available.

Do not give it the name obj. Almost everything is an object, so this name is not very informative.

Exceptions are a complex matter. It raises a lot of questions like "should I throw an exception or output a message to the user?", "should I create my own exception types?", "should I log the exception?" etc., etc. There is no single best answer to these questions. Therefore, I content myself with giving you a link: C# Exception Handling Best Practices.

If you intend to reuse this functionality, you could encapsulate it into your own class:

public enum OperationResult
{
    Failure,
    NoDriveFound,
    DriveIsOpenOrEmpty,
    MediaIsLoaded
}

public class OpticalDriveCloser
{
    public OperationResult CloseFirst()
    {
        ...
    }

    public OperationResult[] CloseAll()
    {
        ...
    }
}

It returns an operation status as enumeration type and lets the calling application decide how to proceed. I.e. it does neither call Console.WriteLine nor throw exceptions and focuses on pure, non-UI logic. A console application will have a different UI-logic than a windows forms application or a web application.

\$\endgroup\$
6
\$\begingroup\$

There's quite a bit to unpack here. I'm just going to line-by-line it and then show the final version at the end:

[DllImport("winmm.dll")] protected static extern int mciSendString(string Cmd, StringBuilder StrReturn, int ReturnLength, IntPtr HwndCallback);

Couple things with this one: 1. I don't see why this needs to be protected. Especially in a "main" class like this. 2. The parameter names are not C# idiomatic: capitalization rules and Hungarian notation is not desired.

ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT MediaLoaded FROM Win32_CDROMDrive");

This is an IDisposable-implementing class and it should be wrapped in a using construct.

ManagementObjectCollection moc = searcher.Get();

This is also an IDisposable-implementing class and should also be wrapped in a using construct.

var enumerator = moc.GetEnumerator();

Okay, GetEnumerator() and MoveNext() are what as known as mechanism domain (focuses on HOW something gets done) constructs. Most folks never need to be at this level. There are many abstractions that exist that better represent the business domain (focuses on WHAT needs to be done). LINQ is a fantastic way to do this sort of abstraction.

if (!enumerator.MoveNext()) throw new Exception("No elements");

I do not see any benefit to throwing an exception here. What is the consumer supposed to do with it?

bool status = (bool) obj["MediaLoaded"];

Naming (along with off-by-one errors and cache invalidation) is one of the hardest problems in computing. If the variable indicates that media is loaded, name it just like the WMI object.

MessageBox.Show("The drive is either open or empty", "Optical Drive Status");

Don't mix your business logic (determine if the door needs closing, then close) it with UI logic. Same goes for the other MessageBox.Show() call.

if (!status) {

Minor, but when you have an if..else construct, it's easier to read a positive test versus a negative test.

Ok, let's take these notes and put together a possible enhancement:

     [DllImport("winmm.dll")] private static extern int mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr callback);

    static void Main(string[] args) {
         if (CloseMedia() {
             MessageBox.Show("The drive is closed and contains an optical media", "Optical Drive Status");
         }
         else {
             MessageBox.Show("The drive is either open or empty", "Optical Drive Status");
         }
    }

    static bool CloseMedia()
    {
        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT MediaLoaded FROM Win32_CDROMDrive"))
        using (ManagementObjectCollection moc = searcher.Get())
        {
            var managementObjects = moc.Cast<ManagementObject>();

            if (managementObjects.Any())
            {
                ManagementObject obj = (ManagementObject)managementObjects.First();
                bool mediaLoaded = (bool) obj["MediaLoaded"];

                if (mediaLoaded) {
                    return false;
                }
                else {
                    mciSendString("set cdaudio door closed", null, 0, IntPtr.Zero);
                    return true;
                }
            }
        }

        return false;
    }
\$\endgroup\$
2
  • 7
    \$\begingroup\$ I mostly disagree with your first point. When using a native function I prefer to keep the import as close to the win32 original, with all its grossness in casing and Hungarian notation as possible, to make it easier for maintainers to find documentation if needed. (Because the types are inevitably mutated from their C equivalents, I want the names the same.) I'll often add an idiomatic C# wrapper function if I'm calling it from multiple places. For single uses I'm ambivalent on if a clean looking wrapper function is worth the mental price in adding an indirections worth of complexity. \$\endgroup\$ – Dan Is Fiddling By Firelight Aug 25 '20 at 3:00
  • \$\begingroup\$ Great answer, especially the walkthrough, but a note, if the drive is open it will close and gives me "The Drive is closed and contains an optical media" even if it is empty. \$\endgroup\$ – user10191234 Sep 2 '20 at 9:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.