Go to content Go to navigation Go to search

Como abrir la puerta del cd rom desde C# y VB.NET

June 13th, 2010 by JuanK

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:

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#

//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:

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:

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:

/// <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.

 

Hay más para decir al respecto, por lo cual a algunos no les funcionará, pero esperemos las preguntas :P

 

Happy Learning!





Bookmark and Share

Originally posted 2009-02-08 15:03:50.

El XNA Content Pipeline

June 13th, 2010 by JuanK

Que es el Content Pipeline?

El content pipeline es una API que permite a los desarrolladores y diseñadores incorporar contenidos multimedia en los proyectos creados con XNA framework, estos contenidos multimedia son por ejemplo:

  • imágenes
  • sonidos
  • contenido 3d
  • efectos

Por defecto el content pipeline soporta una amplia gama de formatos de archivo diferentes los cuales son usualmente usados en la industria de los videojuegos, el content pipeline (en adelante CPL) facilita el acceso a estos archivos y brinda un interfaz de acceso unificada que permite acceder a dichos recursos desde cualquier objeto utilizado dentro del juego sin necesidad de hacer uso de múltiples referencias cruzadas lo cual desde luego va en favor de la independencia de cada componente lo cual en un juego generalmente es una tarea muy difícil de hacer. Sin embargo las capacidades del CPL podrían verse limitadas por la cantidad de archivos que soporta puesto que es muy común que en la industria de los videojuegos se usen formatos de archivo independientes de acuerdo a las necesidades particulares de cada proyecto, es aquí donde reside una de las mas importantes características del CPL… es extensible.

El CPL incorpora un marco de trabajo que permite fácilmente incorporar soporte a diferentes tipos de archivo e incluso extender la funcionalidad de un tipo de archivo ya soportado.

Como Funciona el Content PipeLine?

Lo primero ha tener en cuenta son los elementos con los cuales trabaja:

Namespace: Microsoft.Xna.Framework.Content.Pipeline
ContentImporter
ContentProcessor

Namespace: Microsoft.Xna.Framework.Content
ContentTypeReader

Namespace: Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compile
ContentTypeWriter

El camino comienza con el ContentImporter este es el encargado de realizar lectura física del archivo a importar y colocar la información en un objeto capaz de almacenarla. Seguidamente la información colocada en el objeto es pasada a través de un ContentProcessor el cual se encarga de realizar transformaciones en la información cargada en el paso anterior. El ContentTypeWriter se encarga de escribir ese objeto en un archivo con el formato del CPL. Todos los pasos anteriores suceden en tiempo de compilación, el ultimo paso sucede en tiempo de ejecución cuando el juego ya esta ejecutándose, es allí donde el ContentTypeReader lee los archivos creados por el CPL para cargar en memoria el recurso almacenado. Debido a que se debe adicionar una referencia a estas clases en el CPL es necesario que los tres primeros objetos existan compilados en una librería dinámica, mientras que el ultimo puede hacer parte el juego directamente.



Como hacer que el Content Pipeline soporte un archivo diferente?

Normalmente con tan solo el ContentImportery el ContentProcessor es suficiente para cargar un tipo de archivo ya que basicamente lo que se hace es utilizar estos objetos para convertir los datos del archivo en uno de los tipos de dato ya soportados por el CPL, sin embargo si se requiere información adicional que no es contenida en el tipo soportado es entonces necesario que el desarrollador establezca la forma en que el CPL escribe esos datos en un archivo del CPL y la forma en que el CPL debe cargarlos una vez se solicite desde el juego, para ello son las clases ContentTypeWriter y ContentTypeReaderLa buena noticia es que para adicionar soporte a un nuevo tipo de archivo estas clases proveen los mecanismos necesarios para implementarlo de manera relativamente fácil, la mala noticia es que todas son clases abstractas así que la mayoría del trabajo lo debe hacer uno mismo. :P

A continuación un ejemplo de Implementación de un nuevo tipo de archivo para el CPL.
Supongamos existe un tipo de arhivo bmp2 dicho archivo posee la información habitual de un archivo bmp más tres datos muy importantes que requerimos (todos los campos son un ejemplo):

  • Color de Máscara
  • Número de Colores
  • Prioridad

La tarea nro 1 crear un ContentImporter. Hay que recordar que ContentImporter es una clase abstracta asi que se debe crear una clase que herede de ContentImporter, adicionalmente esta clase es genérica por lo cual debemos pasar el parámetro del tipo de dato que se soportara para importar.

public class BMP2Importer : ContentImporter 
{ }

pero un momento… de donde salio BMP2Content ? bueno dado que se esta incorporando un nuevo tipo de archivo es necesario que crear un objeto contenedor de los datos que se cargan desde ese archivo, teniendo en cuenta que el formato bmp2 lo usaremos para crear texturas 2D lo mejor que se puede hacer es crear una clase que herede de un contenedor existente, para este caso Texture2DContent :

using Microsoft.Xna.Framework; 
using Microsoft.Xna.Framework.Graphics; 
using Microsoft.Xna.Framework.Content.Pipeline; 
using Microsoft.Xna.Framework.Content.Pipeline.Graphics; 
 
public class BMP2Content : Texture2DContent 
{
    public BMP2Content(): base() 
    {} 
    
    private Color maskColor; 
    public Color MaskColor 
    { 
        get { return maskColor; } 
        set { maskColor = value; } 
    } 
    
    private int numColores; 
    public int NumColores 
    { 
        get { return numColores; } 
        set { numColores = value; } 
    } 
    
    private byte prioridad; 
    public byte Prioridad 
    { 
        get { return prioridad; } 
        set { prioridad = value; } 
    } 
}

Este contenedor permite manipular los datos desde su cargue hasta su transformacion y escritura en un archivo del content pipeline, sin embargo se requiere de una estructura adicional para manipularlo, porque? bueno el contenedor puede ser realmente cualquier estructura pero se requiere que sea liviana puesto que la cantidad de recursos que tengamos puede incrementar dramaticamente el tiempo que se invcierta en la compilacion del proyecto ya que cada uno de los recursos pasara por un importer, un processor y un writer, ya cuando se va a utilizar esa informacion en un objeto util para el juego si debemos disponer de toda la infromacion necesaria.

Para este ejemplo, debido a que el archivo de tipo bmp2 va a ser usado como textura se pueden usar dos opciones, una es crear una clase de textura nueva (poco recomendable) y la otra es crear una clase que herede de un tipo de textura ya existente, para el caso lo que mas nos conviene es crear una clase que herede de Texture2D.

public class BMP2Texture2D : Texture2D 
{ 
    public BMP2Texture2D(GraphicsDevice device, int height, 
                         int with, int numberLevels, 
                         ResourceUsage usage, SurfaceFormat format) : 
                            base(device, with, height, 
                                 numberLevels, usage, format) 
    {} 
    
    public BMP2Texture2D(GraphicsDevice device, int height, int with, 
                         int numberLevels, ResourceUsage usage, 
                         SurfaceFormat format, ResourceManagementMode resourceManagementMode) : 
                            base(device, with, height, 
                                 numberLevels, usage, 
                                 format,resourceManagementMode) 
    { } 
    
    private Color maskColor; 
    public Color MaskColor 
    { 
        get { return maskColor; }
        set { maskColor = value; } 
    } 
    
    private int numColores; 
    public int NumColores 
    { 
        get { return numColores; } 
        set { numColores = value; } 
    } 
    
    private byte prioridad; 
    public byte Prioridad 
    { 
        get { return prioridad; } 
        set { prioridad = value; } 
    } 
}

Esta clase ya trae todo lo que necesita una textura 2d más las cosas propias del formato bmp2 de el ejemplo.
Ahora si se puede crear el ContentImporter indicando el tipo de contenido a almacenar.

[ContentImporter(".bmp2", DisplayName = "Imagen BMP2", DefaultProcessor = "Procesador para imagenes BMP2")] 
public class BMP2Importer : ContentImporter<bmp2content> 
{ 
    public override BMP2Content Import(string filename, ContentImporterContext context) 
    { 
        BPM2 miBmp = new BMP2(filename); 
        //Este objeto es de ayuda para recuperar la informacion de color y llevarla facilmente al 
        //MipmapChain del objeto textureContent PixelBitmapContent<rgb24> pixelHelper; 
        //Array de textura a retornar BMP2Content textureContent; 
        //Array de bytes para capturar la informacion del archivo JKI byte[] byteArray; 
        //Inicializa un nuevo contenedor de datos de pixeles 
        pixelHelper = new PixelBitmapContent</rgb24><rgb24>(miBmp.Width,miBmp.Height); 
        //Cargar la informacion de bytes para el archivo 
        byteArray = miBmp.GetBytes(); 
        //Envia los datos de bytes al pixel helper 
        pixelHelper.SetPixelData(byteArray); 
        //Inicializa un nuevo contenedor para la textura 
        textureContent = new BMP2Content(); 
        //Carga la informacion de la imagen en la textura 
        textureContent.Mipmaps = new MipmapChain(pixelHelper); 
        //Guarda el color de mascara de la imagen 
        textureContent.MaskColor = new Color(miBmp.ColorMascara.R,miBmp.ColorMascara.G, miBmp.ColorMascara.B); 
        //Establecer el numero de colores 
        textureContent.NumColores = miBmp.Palette.Count; 
        textureContent.Prioridad = 0; 
        return textureContent; 
    } 
} 
</rgb24></bmp2content>

La primera linea es un atributo de la clase, la cual le permitira al Content Pipeline identificar que esta es un ContentImporter y le proporcionara informacion adicional acerca de que informacion debe mostrar desde el IDE de Visual Studio respecto a los archivos que este ContentImporter es capaz de utilizar asi como un nombre descriptivo de la funcionalidad. Seguidamente lo que se hace cargar desde archvo un objeto BMP2 el cual desde luego es un objeto que ya es capaz de cargar un archivo bmp2, el objeto pixelHelper es utilizado como una ‘Helper Class’ ya que si bien no se necesita de manera directa, si es un excelente atajo para poder crear un MipmapChain el cual es requerido para cargar en un objeto Texture2DContent con la informacion de la imagen que se requiere, finalmente en la parte inferior del método se asignan la informacion adicional que se requiere.

Una vez se ha importado la informacion desde el archivo esta debe pasar por el processor, el procesor se debe hacer para que funcione igual que funciona un procesor para una imagen BMP normal asi que se puede reutilizar un processor que ya este creado en XNA y eso es todo ya que sobre el resto de la informacion no se requiere hacer ninguna modificacion. El processor en este caso es bastante sencillo:

[ContentProcessor(DisplayName = "Procesador para imagenes BMP2")] 
class BMP2Processor : ContentProcessor<bmp2content , BMP2Content> 
{ 
    public override BMP2Content Process(BMP2Content input, ContentProcessorContext context) 
    { 
        input[i] = (BMP2Content)context.Convert<texturecontent ,TextureContent>( 
                         (TextureContent)input[i], "SpriteTextureProcessor"); 
        return input; 
    } 
}
</texturecontent></bmp2content>

El atributo inicial tiene la misma funcionalidad que en el importer y es proveer una pequeña descripcion del ContentProcessor para que aparezca en el IDE de Visual Studio. Se llama al metodo context.Convert indicandole que debe usar el builting processor SpriteTextureProcessor, esto hara que la informacion inherente a una textura 2d sea procesada de la manera habitual . Finalmente se procede a crear el ContentTypeWriter.

[ContentTypeWriter] 
public class BMP2ContentWriter : ContentTypeWriter<bmp2content> 
{
    protected override void Write(ContentWriter output, BMP2Content value) 
    { 
        //Buffer para la informacion de la imagen 
        byte[] pixelData; 
        //Escribe alto y ancho 
        output.Write(value.Mipmaps[0].Height); 
        output.Write(value.Mipmaps[0].Width); 
        //Obtiene el contenido de la imagen 
        pixelData = value.Mipmaps[0].GetPixelData(); 
        //Escribe el tamaño de datos de la imagen 
        output.Write(pixelData.Length); 
        //Escribe la informacion de la imagen 
        output.Write(pixelData); 
        //Escribe el color de mascara de la imagen 
        output.WriteObject<color>(value.MaskColor); 
    } 
    
    public override string GetRuntimeReader(TargetPlatform targetPlatform) 
    { 
        return typeof(BMP2ContentReader).AssemblyQualifiedName; 
    } 
} 
</color></bmp2content>

El atributo inicial tiene la misma funcionalidad que en el importer. El método write lo que hace es escribir en el archivo del CPL la informacion relacionada con el archivo ya previamente cargado en el BMP2Content, el objeto output recibido como parametro posee metodos para escribir a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework, el método GetRuntimeReader Almacena informacion que le indica el CPL en tiempo dejecucion que clase debe instanciar para poder convertir un archivo del CPL en el objeto que se desea, es decir indica cual implementacion de ContentTypeReader debe utilizarce para tal fin. Esta es la implementacion de ContentTypeReader para leer en tiempo de ejecucion lo que el ContentTypeWriter ha guardado en tiempo de compilacion, el objeto input recibido como parametro posee metodos para leer a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework.

public class BMP2ContentReader : ContentTypeReader<bmp2texture2d> 
{ 
    protected override BMP2Texture2D Read(ContentReader input, BMP2Texture2D existingInstance) 
    { 
        //Se obtiene el dispositivo grafico 
        GraphicsDevice graphics = ((IGraphicsDeviceService) 
            input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService))).GraphicsDevice; 
        //Inicializa la instancia actual, lee el ancho y alto de la imagen 
        existingInstance = new BMP2Texture2D(graphics, input.ReadInt32(), input.ReadInt32(), 
                               1, ResourceUsage.AutoGenerateMipMap, SurfaceFormat.Color); 
        //Establece el contenido de la imagen 
        //lee el tamaño de la informacion en bytes a leer 
        //y lee la informacion en bytes 
        existingInstance[i].SetData<byte>(input.ReadBytes(input.ReadInt32())); 
        //Establece el color de mascara 
        existingInstance[i].maskColor = input.ReadObject<color>(); 
        return existingInstance; 
    }
} 
</color></byte></bmp2texture2d>

Eso es todo, para hacerlo funcionar, se requiere compilar estas clases en una dll la cual se debe adicionar al content pipeline, para hacer eso se debe ir a las propiedades del proyecto y luego a la ultima pestaña la cual desde luego se llama Content Pipeline, desde alli presionar el boton Add y seleccionar la dll creada. Para que desde el juego se peuda cargar la informacion se debe incluir referencia por lo menos al ensamblado donde se compilo el ContentTypeReader. En adelante tan solo se accede al recurso a travez de un content manager como se hace de manera habitual:

ContentManager content; 
… 
… 
… 
BMP2Texture myTexture = content.Load<bmp2texture>(@"myImage.bmp2"); 
</bmp2texture>

Espero les sea de utilidad. Saludos.

Juan Carlos Ruiz Pacheco
Ingeniero de Sistemas

Bookmark and Share

Originally posted 2007-09-17 23:41:48.

C# – Consumir un XML o un RSS alojado en la web de manera sencilla

June 13th, 2010 by JuanK

Muchas veces necesitamos leer contenidos RSS o XML desde la web, y lo necesitamos de manera rápida, ligera y fácil de usar.

Buscando a través de la internet se pueden conseguir varias soluciones de varios sabores diferentes.

 

En este artículo enseñare a crear un componente capaz de leer cualquier XML que se encuentre en la web con el fin de acceder a su información de manera sencilla y de paso explicaré que hacer cuando ese XML sea un RSS para sacarle mayor provecho.

 

El problema lo podemos fraccionar en 2 partes

  1. Obtener el XML o RSS de la web
  2. Acceder a esa información por medio de un componente conocido, un Dataset

OBTENER EL XML O RSS DE LA WEB

Para lograr este objetivo se hace necesario hacer uso de un objeto HttpWebRequest para crear un Request a la URL donde se encuentra alojado el XML y luego capturar la respuesta (Response).

public static DataSet GetXMLDataSet(string URL)
{
    HttpWebRequest xmlRequest = (HttpWebRequest)WebRequest.Create(URL);
    WebResponse xmlResponse = xmlRequest.GetResponse();  
}

 

ACCEDER A ESA INFORMACION POR MEDIO DE UN COMPONENTE CONOCIDO, UN DATASET

Aunque ya tenemos el WebResponse este de por si no nos permite un acceso ‘multiuso’ a la información que contiene, pero ya que sabemos que nuestro WebResponse contiene información XML podemos aprovecharnos de eso para crear un Dataset. El Dataset se puede crear a partir de un Stream así que haremos uso del método GetResponseStream para obtenerlo, creamos un DataSet vacio y luego lo llenamos con ReadXml.

public static DataSet GetXMLDataSet2(string URL)
{
    HttpWebRequest xmlRequest = (HttpWebRequest)WebRequest.Create(URL);
    WebResponse xmlResponse = xmlRequest.GetResponse();
    Stream responseStream = xmlResponse.GetResponseStream();
 
    DataSet xmlData = new DataSet();
    xmlData.ReadXml(responseStream);
    return xmlData;
}

Reduciendo un poco el código queda:

public static DataSet GetXMLDataSet(string URL)
{
    HttpWebRequest xmlRequest = (HttpWebRequest)WebRequest.Create(URL);
    DataSet xmlData = new DataSet();
    xmlData.ReadXml(xmlRequest.GetResponse().GetResponseStream());
    return xmlData;
}

 

Ya estuvo, ahora es tiempo de sacarle provecho al DataSet.

 

NOTA ADICIONAL

Algunos RSS tienen estructuras un poco más complejas que pueden traer problemas al momento de crear el DataSet, usualmente este problema se manifiesta produciendo una excepción System.Data.DuplicateNameException , para evitar este problema es necesario indicarle al DataSet cual es la estructura correcta del RSS, para ello utilizamos ReadXmlSchema cargando el esquema XSD que describe la estructura de RSS 2.0, para obtenerlo basta con ir a esta página y bajarlo: RSS 2.0 Schema, más específicamente este link rss-2_0.xsd .

Lo adicionamos al proyecto, en mi caso lo he adicionado como recurso incrustado, el código incluyendo esta variante para el caso de RSS sería más o menos de esta forma:

/// <summary>
/// Se conecta a una URL que representa un archivo XML y convierte la información en un DataSet</summary>
/// <param name="URL" />URL del xml publicado en la web</param>
/// <returns>Dataset que representa los datos XML</returns>
public static DataSet GetXMLDataSet(string URL)
{
    HttpWebRequest xmlRequest = (HttpWebRequest)WebRequest.Create(URL);
    DataSet xmlData = new DataSet();
    xmlData.ReadXmlSchema(new XmlTextReader(Resources.rss_2_0, XmlNodeType.Document,null));
    xmlData.ReadXml(xmlRequest.GetResponse().GetResponseStream(), XmlReadMode.IgnoreSchema);
    return xmlData;
}
Bookmark and Share

« Previous Entries Next Entries »