Dando solución a una de las inquietudes más comunes de las personas en los foros he creado este post para mostrarles la funcionalidad de la clase ExpulsarCDRom para explicarles como se hizo.

CÓMO UTILIZAR ESTA CLASE?

Para utilizarla, las explicaciones sobran, este es un código de ejemplo que muestra como abrir todas las unidades de CDRom en el PC ,he utilizado linq y DriveInfo para obtener el listado de unidades de CDRom disponibles y seguidamente utilizo el método ExpulsarCDRom.Expulsar:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
static void Main(string[] args)
{
//Obtener la lista de dispositivos de CDRom
var CDRomDrives = from drive in System.IO.DriveInfo.GetDrives()
where drive.DriveType == System.IO.DriveType.CDRom
select drive;

//A cada uno de ellos hacerlo abrir
foreach (DriveInfo cdRom in CDRomDrives)
ExpulsarCDRom.Expulsar(cdRom.Name);
}
}

Esta es la implementación final de ExpulsarCDRom en C#

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//Constantes usadas en la API
/// <summary>Indica que se se hara lectura genérica del archvo</summary>
const uint GENERICREAD = 0x80000000;
/// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
const uint OPENEXISTING = 3;
/// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
const int INVALID_HANDLE = -1;

/// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
private static IntPtr fileHandle;
/// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
private static uint returnedBytes;

/// <summary>
/// esta función sirve para crear archivos pero también para abrir archivos existentes,
/// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema
/// global de archivos así que podemos llegar a ella desde este medio
/// </summary>
/// <returns>Puntero que sirve como manejador del archivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

/// <summary>
/// Cierra un manejador a un objeto,
/// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile
/// </summary>
/// <param name="driveHandle" />
/// <returns>Entero que indica la respuesta del proceso</returns>
[DllImport("kernel32", SetLastError = true)]
static extern int CloseHandle(IntPtr driveHandle);

/// <summary>
/// Nos permite enviar comandos de I/O a un dispositivo.
/// </summary>
/// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
uint IoControlCode,
IntPtr lpInBuffer,
uint inBufferSize,
IntPtr lpOutBuffer,
uint outBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);

/// <summary>
/// Expulsa el drive de acuerdo a la letra asignada
/// </summary>
/// <param name="driveLetter" />Letra del drive
public static void Expulsar(string driveLetter)
{
//Modificar el nombre de la unidad de acuerdo a como lo entiende el
//sistema de archivos
driveLetter = @"\\.\" + driveLetter.Substring(0, 2);
try
{
//Crea el puntero al archivo (dispositivo)
fileHandle = CreateFile(driveLetter, GENERICREAD, 0,
IntPtr.Zero, OPENEXISTING,
0, IntPtr.Zero);

//Si es una unidad valida
if (fileHandle.ToInt32() != INVALID_HANDLE)
{
//Intenta expulsar el dispositivo
DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,
IntPtr.Zero, 0, IntPtr.Zero, 0,
ref returnedBytes, IntPtr.Zero);
}
}
catch
{
//Sino lo pudo expulsar
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
finally
{
//Asegurarse de siempre cerrar el puntero del archvo
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}
//Constantes usadas en la API
/// <summary>Indica que se se hara lectura genérica del archvo</summary>
const uint GENERICREAD = 0x80000000;
/// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
const uint OPENEXISTING = 3;
/// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
const int INVALID_HANDLE = -1;

/// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
private static IntPtr fileHandle;
/// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
private static uint returnedBytes;

/// <summary>
/// esta función sirve para crear archivos pero también para abrir archivos existentes,
/// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema
/// global de archivos así que podemos llegar a ella desde este medio
/// </summary>
/// <returns>Puntero que sirve como manejador del archivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

/// <summary>
/// Cierra un manejador a un objeto,
/// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile
/// </summary>
/// <param name="driveHandle" />
/// <returns>Entero que indica la respuesta del proceso</returns>
[DllImport("kernel32", SetLastError = true)]
static extern int CloseHandle(IntPtr driveHandle);

/// <summary>
/// Nos permite enviar comandos de I/O a un dispositivo.
/// </summary>
/// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
uint IoControlCode,
IntPtr lpInBuffer,
uint inBufferSize,
IntPtr lpOutBuffer,
uint outBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);

/// <summary>
/// Expulsa el drive de acuerdo a la letra asignada
/// </summary>
/// <param name="driveLetter" />Letra del drive
public static void Expulsar(string driveLetter)
{
//Modificar el nombre de la unidad de acuerdo a como lo entiende el
//sistema de archivos
driveLetter = @"\\.\" + driveLetter.Substring(0, 2);
try
{
//Crea el puntero al archivo (dispositivo)
fileHandle = CreateFile(driveLetter, GENERICREAD, 0,
IntPtr.Zero, OPENEXISTING,
0, IntPtr.Zero);

//Si es una unidad valida
if (fileHandle.ToInt32() != INVALID_HANDLE)
{
//Intenta expulsar el dispositivo
DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,
IntPtr.Zero, 0, IntPtr.Zero, 0,
ref returnedBytes, IntPtr.Zero);
}
}
catch
{
//Sino lo pudo expulsar
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
finally
{
//Asegurarse de siempre cerrar el puntero del archvo
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}

Para Acceder a la versión en VB.NET pueden hacer la conversión del código anterior en este link:
http://www.developerfusion.com/tools/convert/csharp-to-vb/

COMO SE HACE?

Hay que utilizar la API de Windows

Si, sucede que abrir la puerta de la unidad de CDRom es una actividad altamente dependiente del sistema operativo pues requiere acceder al dispositivo y esto en cada sistema operativo se hace de manera diferente incluso puede ser diferente de un dispositivo a otro por lo que utilizar la API del sistema operativo nos cae bastante bien para no caer en complejidades exageradas.

Qué cosas hay que usar de la API?

Para poder abrir la unidad de CD necesitamos lo siguiente

  • CreateFile = esta función sirve para crear archivos pero también para abrir archivos existentes, así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema global de archivos así que podemos llegar a ella desde este medio.
  • CloseHandle = Cierra un manejador a un objeto, en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile .
  • DeviceIoControl = Nos permite enviar comandos de I/O a un dispositivo.

Para poder utilizar estos llamados a la API de Windows desde C# o VB necesitamos utilizar el atributo DLLImport que se encuentra definido en System.Runtime.InteropServices y que nos sirve para importar librerías creadas en código nativo.

Sin embargo no es tan sencillo como usar las funciones y ya, así que sino tienes experiencia en el uso de componentes nativos, puedes pegarte una pasada en internet para entender todo de manera más completa.

Por el momento basta con decir que debemos definir los tipos de dato y constantes que se utilizan en la API para así hacer un uso correcto de las funciones importadas.

Cómo funciona / Cómo Crearla.

Creamos una clase ExpulsarCDRom en la cual importaremos las funciones necesarias de la API de Windows:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class ExpulsarCDRom
{

/// <summary>
/// esta función sirve para crear archivos pero también para abrir archivos existentes,
/// así que se utilizará para abrir un archivo, la unidad del CD hace parte del sistema
/// global de archivos así que podemos llegar a ella desde este medio
/// </summary>
/// <returns>Puntero que sirve como manejador del archivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

/// <summary>
/// Cierra un manejador a un objeto,
/// en este caso el objeto será la unidad de CD que hemos accedido a través de CreateFile
/// </summary>
/// <param name="driveHandle" />
/// <returns>Entero que indica la respuesta del proceso</returns>
[DllImport("kernel32", SetLastError = true)]
static extern int CloseHandle(IntPtr driveHandle);

/// <summary>
/// Nos permite enviar comandos de I/O a un dispositivo.
/// </summary>
/// <returns>Indica si fue o no enviado el comando al dispositivo</returns>
[DllImport("kernel32", SetLastError = true)]
static extern bool DeviceIoControl(IntPtr driveHandle,
uint IoControlCode,
IntPtr lpInBuffer,
uint inBufferSize,
IntPtr lpOutBuffer,
uint outBufferSize,
ref uint lpBytesReturned,
IntPtr lpOverlapped);

}

Una vez hecho esto, continuamos creando las constantes que necesitamos para llamar las funciones y desde luego las variables que nos permitirán controlar la respuesta de cada una de ellas, así que agregamos:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ExpulsarCDRom
{

//Constantes usadas en la API
/// <summary>Indica que se se hara lectura genérica del archvo</summary>
const uint GENERICREAD = 0x80000000;
/// <summary>Indica que se debe abrir un archivo existente, no crear uno nuevo</summary>
const uint OPENEXISTING = 3;
/// <summary>Comando enviado al dispositivo para abrir la puerta</summary>
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// <summary>Indica que la operaciónj no finalizo adecuadamente </summary>
const int INVALID_HANDLE = -1;

/// <summary>Puntero que se usara para apuntar al archivo (unidad) de CDRom</summary>
private static IntPtr fileHandle;
/// <summary>Indica el número de bytes leidos cmo rspuesta de un proceso</summary>
private static uint returnedBytes;

...
...

Tenemos los preparativos para comenzar a codificar, creamos una función estática llamada ExpulsarUnidad la cual vemos a continuación y explicamos más abajo:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// Expulsa el drive de acuerdo a la letra asignada
/// </summary>
/// <param name="driveLetter" />Letra del drive
public static void Expulsar(string driveLetter)
{
//1. Modificar el nombre de la unidad de acuerdo a como lo entiende el
//sistema de archivos
driveLetter = @"\\.\" + driveLetter.Substring(0, 2);
try
{
//2.Crea el puntero al archivo (dispositivo)
fileHandle = CreateFile(driveLetter, GENERICREAD, 0,
IntPtr.Zero, OPENEXISTING,
0,IntPtr.Zero);

//3.Si es una unidad valida
if (fileHandle.ToInt32() != INVALID_HANDLE)
{
//4.Intenta expulsar el dispositivo
DeviceIoControl(fileHandle, IOCTL_STORAGE_EJECT_MEDIA,
IntPtr.Zero, 0, IntPtr.Zero, 0,
ref returnedBytes,IntPtr.Zero);
}
}
catch
{
//5.Sino lo pudo expulsar
throw new Exception(Marshal.GetLastWin32Error().ToString());
}
finally
{
//6.Asegurarse de siempre cerrar el puntero del archvo
CloseHandle(fileHandle);
fileHandle = IntPtr.Zero;
}
}

  1. Lo primero que hacemos es modificar el nombre del archivo que vamos a abrir, ya que por facilidad de consumo hemos creado la función para que reciba como parámetro el nombre de la unidad a abrir, pero este no es el nombre que entiende el sistema de archivos, así que modificamos levemente el nombre de la unidad para poder trabajar más cómodamente en adelante.
  2. Abrimos el archivo con ayuda de la función CreateFile de la API de Windows, lo cual nos devuelve el manejador del archivo. Los parámetros pasados son el nombre del archivo (la unidad de CD), la forma en que se abrirá el archivo, en este caso solo lectura, y le indicamos que el archivo ya existe es decir que no debería crear uno nuevo, los demás parámetros no los necesitamos por lo cual envió 0 o apuntador a cero.
  3. Verificamos que en efecto que el sistema haya podido abrir el archivo (es decir direccionar un manejador a la unidad de CD), si no pudo el valor devuelto será INVALID_HANDLE (una de nuestras constantes creadas) en cuyo caso no continuaremos con el programa y no haremos nada.
  4. Si la respuesta no fue INVALID_HANDLE procedemos a enviar un comando a la unidad de CDRom, esto lo hacemos con la función DeviceIoControl , pasándole como parámetros: el manejador del archivo que representa al dispositivo (filehandle), el comando IOCTL_STORAGE_EJECT_MEDIA (otra de nuestras constantes) y los demás parámetros que nuevamente no representan importancia para nuestro caso.
  5. Nos aseguramos de hacer un control básico de las excepciones que se pueden presentar .
  6. Y siempre, sin importar lo que suceda, intentamos cerrar el manejador al archivo y establecemos ese manejador en cero.

Para desarrolladores en Windows Vista y posteriores (sistema seguros)

Deben crear un manifestó en su aplicación, pues por defecto UAC requiere elevar privilegios para que este código funcione, entonces en su proyecto agreguen un nuevo manifestó que indique elevar privilegios para efectuar esta operación.

Obtenido de: http://juank.black-byte.com/c-abrir-puerta-cd-rom/

Anuncios