´

August 2009 - Posts

Conferencia – Silverlight como Plataforma de Desarrollo

Puedes ver el artículo original en mi blog:
http://juank.black-byte.com/eventos-silverlight-plataforma-desarrollo/

 

Fecha: Miércoles 2 de Septiembrea las 18:30 (-5 GMT)

Lugar: Auditorio Fundación Universitaria CAFAM
AK 68 N° 90-88 CAFAM Floresta, Bogotá – Colombia
¡¡¡..ENTRADA LIBRE…!!!!


Silverlight es una tecnología relativamente nueva en el mundo de las Aplicaciones Enriquecidas para Internet, la cual nos permite extender la funcionalidad de nuestras aplicaciones Web a través de un componente ligero, multi-plataforma y multi-navegador. En esta sesión podrás conocer cuáles son las características de la última versión de Silverlight que la hacen una excelente opción para construir este tipo de aplicaciones. Ven y conoce qué es Silverlight y cómo lo puedes utilizar como plataforma de desarrollo para aplicativos Web modernos.

Cómo obtener el SID de un usuario local? - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-obtener-sid-usuario-local/

 ---

Obtener el SID del usuario que se encuentra ejecutando la aplicación es bastante sencillo:

  1. Adicionar using a System.Security.Principal
  2. Instanciar un objeto WindowsIdentity
  3. Construirlo a partir de WindowsIdentity.GetCurret()
  4. Usar la propiedad Value

using System;
using System.Security.Principal;

class Program
{
static void Main(string[] args)
{
WindowsIdentity currentUser = WindowsIdentity.GetCurrent();
Console.WriteLine(currentUser.User.Value);
Console.ReadLine();
}
}

Sin embargo tratar de acceder a la información de los usuarios diferentes del logueado actualmente ( y sin usar impersonación ) puede ser un poco más complejo. Una alternativa viable es hacer uso de WMI.

  1. Adicionar la referencia a System.Management y el respectivo using
  2. A traves de WMI se debe hacer un query al objeto Win32_UserAccount
  3. Especificar el nombre del dominio, si es local es el nombre de la máquina el cual se peude obtener a traves de System.Environment
  4. Hacer una búsqueda sobre el query
  5. Usar el indizador accediento con la cadena "SID"

using System;
using System.Management;

class Program
{
static void Main(string[] args)
{
SelectQuery sQuery = new SelectQuery("Win32_UserAccount", "Domain='"
+ System.Environment.MachineName + "'");
ManagementObjectSearcher mSearcher = new ManagementObjectSearcher(sQuery);

Console.WriteLine("User Accounts");
Console.WriteLine("");
foreach (ManagementObject mObject in mSearcher.Get())
Console.WriteLine(mObject["SID"]);

Console.ReadLine();
}
}

Happy Learning!

Cómo encontrar los archivos pst de outlook? - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-pst-outlook/

 ---

Con ayuda de las facilidades de interoperabilidad que ofrece .Net Framework y desde luego con ayuda de Visual Studio este proceso es bastante sencillo.

El primer paso es desde luego agregar una referencia al objeto COM de Outlook para que Visual Studio nos importe el componente y esto nos permita acceder al objeto COM como lo haríamos normalmente con cualquiero otra librería para usar con el framework. El componente COM a que debemos acceder es (o equivalente de acuerdo a la versión de office instalada): 

image

Como buena práctica resulta importante colocarle un alias al namespace, por ejemplo:

using Outlook = Microsoft.Office.Interop.Outlook;

Los pasos importantes siguientes se resumen así:

  1. Instanciar un objeto de la aplicación (outlook)
  2. Obtener los stores (almacenamientos de datos) para la sesión actual
  3. Verificar que exista un FilePath, esto es necesario puesto que si el store esta en exchange el FilePath es nulo.
  4. Verificar que el FilePath termine en ".pst", pues algunos stores de caché tienen extensiones diferentes.
  5. El resto es maquillaje

using System;
using System.Text;
using Outlook = Microsoft.Office.Interop.Outlook;

class Program
{
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder();
Outlook._Application outlookObj = new Outlook.Application();

foreach (Outlook.Store store in outlookObj.Session.Stores)
if (store.FilePath != null && store.FilePath.EndsWith(".pst"))
sb.AppendLine(store.FilePath);

Console.WriteLine(sb.ToString());
Console.ReadLine();
}
}

Happy Learning!

Caracter de Salto de linea - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-caracter-salto-linea/

 ---

Un salto de linea en windows no es es realmente un caracter sino que realmente son dos caracteres el 13 y luego el caracter 10.  En detalle: \r = 13 = CR = Carriage return = Retorno de carro \n = 10 = LF = Line Feed       = Avance de linea

Mientras que en unix podria ser igual o diferente dependiendo de la configuracion.

La más usual es  que en unix sea solo CR, razon por la cual cuando abres en windows un archivo de texto creado en unix , ves que te sale todo en una sola linea ( falta el LF  - el salto de linea).

Como ves esto depende de la plataforma asi que lo ideal es que en nuestros programas que usen el CLR  es hacer uso de: System.Environment.NewLine

En las base de datos utiliza necesariamente CHR(13)+CHR(10)  ya que esto te dara compatibilidad con varias plataformas. Complemento: oscardo comenta en este hilo este aporte de gran utilidad, que puede ser útil cuando se esta trabajando con Web Mobile: char.ConvertFromUtf32(13) + char.ConvertFromUtf32(10)

Happy learning!

Optimización de Código - Cómo Convertir un Entero en Binario - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-entero-a-binario/ 

---

El título de este post debería ser realmente: "Cómo formatear una cadena para mostrar un entero en formato binario"... pero créanme que nadie lo buscaría así en un buscador :P.

Bien esto parece ser un problema recurrente alrededor de los foros en internet, existen muchas soluciones diferentes a este problema, incluso hay algunos lenguajes que ya incluyen soporte para hacer esto fácilmente,  el CLR no se caracteriza por tener una funcionalidad fácil de utilizar sin embargo en este artículo exploraremos varias soluciones (desde luego no todas )posibles hasta llegar a la solución ideal que espero les sea de provecho a todos.

Estas son las opciones que exploraremos, son básicamente 2.

La primera opción no requiere de mucho análisis pero tiene sus inconvenientes digamos menores. La segunda opción es la que más nos importa, aprenderemos a deducir el algoritmo a implementarlo y subsecuentemente lo iremos optimizando hasta lograr una versión digna de un excelente programador:

  1. Convertir con BitVector32 (mala solución pero rápida de implementar)
  2. Convertir creando un algoritmo eficiente
    • Utilizando string
    • Utilizando StringBuilder
    • Utilizando punteros con codigo inseguro

 

1.  CONVERTIR CON BITVECTOR32

Esta es la solución más rápida al problema, pero en mi opinión la menos profesional....

BitVector32 es una colección especializada que se encuentra en:

System.Collections.Specialized

Esta clase provee una estructura simple que almacena valores boléanos que representan un entero de 32 bit.  Que que?

Ok esta explicación no necesariamente es clara para todos, hagámoslo muy simple veamos un entero de 32 bits, imaginemos que en memoria se ve así:

0 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

Es decir cada casilla representa un bit, para completar así 32 bits.

Bien entonces esto a final de cuentas es una colección bits, es decir en cada 'celda' podemos tener dos valores posibles 1 o 0... true o false.

Bien eso es lo hace BitVector toma un entero y lo permite manejar como un array de booleanos.

El método ToString() permite convertir todo esto a 1s y 0s para poder ver los valores de cada 'celda' en su representación numérica, sin embargo esto tiene un inconveniente pues si utilizamos elObjetoBitVector.ToString() recibiremos algo así como esto:

BitVector32{00000000000000000000000001101111}

Nos sobra algo al principio y al final... a nosotros solo nos interesan los datos binarios así que la solución es hacer un substring de la parte de la cadena que nos interesa, finalmente el código quedaría así:


public static string EB_BitVector32(Int32 entero)
{
BitVector32 bv = new BitVector32(entero);
return bv.ToString().Substring(12, 32);
}

Esto nos produciría este resultado similar a:

00000000000000000000000001101111

 

2.  CONVERTIR CREANDO UN ALGORITMO EFICIENTE

Ahora si entremos en materia hagamos el algoritmo.

Una de las primeras ideas que a uno se le viene a la mente es utilizar el conocido algoritmo que nos permite convertir un numero decimal a base 2, para el que no lo recuerde o no lo conozca: es un algoritmo que toma como base divisiones anidadas del valor entre 2 y recupera los residuos. En este link tienen una explicación detallada al respecto: Decimal a Binario

Sin embargo dicho algoritmo puede ser ineficiente para nuestro gusto.

Retomemos conceptos como puntos clave que nos permitan llegar a un algoritmo óptimo:

  1. Básicamente un entero es un arreglo de 4 bytes (32 bits)
  2. Por cada bit se debe producir su equivalente como cadena ("0" , "1") según si el bit esta prendido(1) o apagado(0)
  3. Podemos acceder a todos los bit como un arreglo de boolean a través de BitVector32, pero en ese caso la solución sería aún más lenta que utilizando la propuesta inicial que usa BitVector32
  4. Hay que encontrar el mecanismo adecuado para recorrer los bit

De los conceptos clave tenemos que los dos primeros nos son útiles, el tercero no nos aporta nada nuevo y el cuarto es nuestra necesidad más inmediata: Averiguar como recorrer los bit sin necesidad de usar BitVector32.

Lo primero que me salta a la mente es usar operadores binarios especialmente corrimientos de bit (<<) y and (&) para las máscaras.

 

Veamos un poco de teoría acerca de lo que haremos.

  1. Crear un bucle e 32 ciclos
  2. Leemos siempre el valor del primer bit del entero de 32 bit, esto lo podemos hacer efectuando un and binario (&) del entero con un numero que tenga el primer bit prendido únicamente, es decir con : 0x80000000 (es decir 2147483648 )... porqué este número?  bueno coloquemos este 0x80000000 en nuestro arreglo de 32 bits:
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    8       0       0       0       0       0       0       0      

    Cada 4 bits representan uno de los dígitos hexa, de esta forma el 0x8 esta representado por los primeros 4 bits, en efecto 1000 = 0x8, así que si se fijan en nuestro arreglo de bits solo tenemos un bit prendido, de tal forma que cualquier cosa que haga & con 0x80000000 solo podría llegar a tener prendido el bit 1, así podemos hallar que valor tiene el bit 1 de un número dado ya que si ese bit esta en 0 al hacer and quedaría en 0, mientras que si esta en 1 al hacer and quedaría en uno nuevamente. Concretando, cuando hacemos un and entre dos enteros y uno de los enteros es 0x80000000 el resultado siempre será el valor del primer bit del otro entero. ( espero haber sido claro).
  3. En cada iteración corremos 1 bit posición a la izquierda (<< 1), de tal forma que el primer bit es reemplazado por el bit siguiente
  4. Se regresa al paso 2

IMPLEMENTACION CRUDA Utilizando String

Este es el código resultante de la implementación más simpe del algoritmo:


public static string EB_String(int entero)
{
//La máscara y el # de iteraciones
const uint mascara = 0x80000000;
const int iteraciones = 32;
//el contador y el resultado
int contador = 0;
string resultado = "";

//Se recorren los 32 bit
while (contador++ < iteraciones)
{
/*Si el entero and la mascara = 0 quiere decir
*que el bit 1 esta apagado*/
if ((entero & mascara) == 0)
resultado += "0";
else
resultado += "1";

/*correr un bit a la izquierda para poner
*el siguiente bit en la posicion del primero*/
entero = entero << 1;
}
return resultado;
}
 

OPTIMIZACIÓN DE CÓDIGO Utilizando StringBuilder

La implementación anterior es susceptible de una sencilla mejora utilizando StringBuilder ya que al ser string un tipo inmutable , las recurrentes concatenaciones generadas al utilizar masivamente el método sobrecargarían el GC, sin contar el overhead producido por las frecuentes reservas de memoria (1 adicional por cada cambio al string). StringBuilder se encuentra en el namespace System.Text.

Código optimizado utilizando StringBuilder:


public static string EB_StringBuilder(int entero)
{
//La máscara y el # de iteraciones
const uint mascara = 0x80000000;
const int iteraciones = 32;
//el contador y el resultado
int contador = 0;
StringBuilder resultado = new StringBuilder(iteraciones);

//Se recorren los 32 bit
while (contador++ < iteraciones)
{
/*Si el entero and la mascara = 0 quiere decir
*que el bit 1 esta apagado*/
if ((entero & mascara) == 0)
resultado.Append('0');
else
resultado.Append('1');

/*correr un bit a la izquierda para poner
*el siguiente bit en la posicion del primero*/
entero = entero << 1;
}
return resultado.ToString();
}

OPTIMIZACIÓN DE CÓDIGO Utilizando punteros - código unsafe

Aunque StringBuilder ya proporciona una mejora muy importante, dadas las características de nuestro problema(concatenación uno a uno) resulta mucho más idóneo generar código que utilice punteros. Es importante tener en cuenta que para que este código funcione se requiere modificar las propiedades del proyecto para que admita código unsafe.

Esta es la versión básica con manejo de apuntadores:

 


public static unsafe string EB_Unsafe(int entero)
{
const uint mascara = 0x80000000;
const int iteraciones = 32;

int contador = 0;

//Se reservan 32 posiciones y uno adicional para
//terminacion en null
char* resultado = stackalloc char[iteraciones + 1];
//puntero de trabajo
char* aux = resultado;

while (contador++ < iteraciones)
{
if ((entero & mascara) == 0)
*aux = '0';
else
*aux = '1';

//Mover el puntero una posicion dentro de la cadena
aux++;

entero = entero << 1;
}
return new string(resultado);
}

Esta es la versión final de "bonus" totalmente optimizada para velocidad de procesamiento, es levemente diferente de la anterior para realizar la menor cantidad de validaciones posibles y para que ignore los 0s a la izquierda, lo cual la hace excepcionalmente rápida en la mayoría de los casos. Revísenla...:


public static unsafe string EB_Unsafe_Opt(int entero)
{
const uint mascara = 0x1;
const int iteraciones = 32;

char* resultado = stackalloc char[iteraciones + 1];
char* aux = resultado + iteraciones - 1;

do
{
if ((entero & mascara) == 0)
*(aux--) = '0';
else
*(aux--) = '1';

entero = entero >> 1;
} while (entero != 0);

return new string(++aux);
}

Si alguien tiene una solución mejor por favor no dude en compartirla!!

En unos días publicare un programa especialmente diseñado para medir la velocidad de cada una de estas alternativas... les adelanto que las diferencias de tiempo van casi hasta de 20 o 30 veces más velocidad.

 

Happy Learning!!!

OOOHH OLvidaba decirles Si quieren pueden usar:

System.Convert.ToString(elValor, 2);

jajajajaja, bueno el ejercicio que hicimos produce un resultado 30% más rápido. :P

Optimización de Código - Diferencia entre usar Convert y usar cast - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-optimizacion-codigo-diferencia-convert-toint32-cast/

 

 ---

Esta es una duda frecuente cuando quieres convertir de un tipo numérico a otro.

En ese caso hay diferencias importantes sobre todo en cuanto a velocidad de procesamiento.

Veamos 2 ejemplos.

 

1 - CONVERTIR DE TIPO DECIMAL (DECIMAL) A TIPO INT (INT32):

Si utilizas el operador de cast, es decir : (int)

Explicación básica:

El runtime lo que hará es simplemente tomar la parte entera del número descartando completamente la parte decimal.

Explicación más completa:

del tipo decimal es de 16 bytes mientras que el tipo int es de 4 bytes (casi siempre), así que lo que sucede es que el runtime toma la parte entera del tipo decimal y la coloca en los 4 bytes enteros, pero atención, eso solo siempre y cuando la parte entera del dato tipo decimal quepa en 2^32 , de lo contrario se genera una excepción de overflow.

 

Si utilizas el Convert.ToInt32

Explicación básica:

Cuando el método estático Convert.ToInt32()  (en adelante solo ToInt32) recibe como parámetro un tipo de dato Decimal hace más que solo retornar la parte entera, de hecho redondea el resultado al entero más cercano. Esto genera una sobrecarga adicional que lo hace mucho más lento.

Explicación más completa:

ToInt32 cuando recibe un tipo Decimal llama al método interno Decimal.FCallToInt32 que a su vez es un wrapper de un método implementado en código nativo que hace los cálculos necesarios para producir  un valor redondeado. Como se observa entonces hay invocación a 3 métodos como mínimo lo cual implica 3 cambios de contexto: el primero al llamar a ToInt32, luego al llamar a Decimal.FCallToInt32 y luego cuando este hace el wrapper a la función de código nativo, hasta allí ya es mucho más lento que simplemente usar el cast (int), ahora ,  la lógica usada internamente para convertir el tipo de 16 bytes al tipo de 4 bytes y tener en cuenta el redondeo y los casos especiales como por ejemplo cuando el parámetro es null, convierte a esta función en una muchísimas veces más lenta que solo hacer (int).

 

2 - CONVERTIR DE TIPO FLOAT (SINGLE) A TIPO INT (INT32):

Si utilizas el operador de cast, es decir : (int)

Explicación básica:

El runtime lo que hará es simplemente tomar la parte entera del número descartando completamente la parte decimal.

Explicación más completa:

del tipo float es de 4 bytes mientras que el tipo int también es de 4 bytes (casi siempre), así que lo que sucede es que el runtime toma la parte entera del tipo float y la coloca en los 4 bytes enteros.

Si utilizas el Convert.ToInt32

Explicación básica:

Cuando el método estático ToInt32() recibe como parámetro un tipo de dato Single (float) hace más que solo retornar la parte entera, de hecho redondea el resultado al entero más cercano. Esto genera una sobrecarga adicional que lo hace mucho más lento.

Explicación más completa:

ToInt32 cuando recibe un tipo Single (float) llama al método interno Convert.ToInt32 (si, de nuevo, pero llama al que recibe parámetro tipo double ya que la lógica para double es la misma que para Single (float)) que es finalmente quien resuelve la lógica para hacer el redondeo. Como se observa entonces hay invocación a 2 métodos mínimo lo cual implica 2 cambios de contexto: el primero al llamar a ToInt32(Single), luego al llamar a ToInt32(double) que al final es que tiene en cuenta el redondeo y los casos especiales como por ejemplo cuando el parámetro es null, convierte a esta función en una muchísimas veces más lenta que solo hacer (int) pero comparativamente con el caso de Decimal no es tan lenta.

Conclusión: Si necesitas velocidad siempre usa operadores de cast, ejemplo: (int); si requieres aproximación y que te tengan en cuenta los casos especiales usa siempre Convert.

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

Puedes ver el articulo original en  mi blog:

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

 ---

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


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

///
/// 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
///
/// Puntero que sirve como manejador del archivo
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

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


///
/// Nos permite enviar comandos de I/O a un dispositivo.
///
/// Indica si fue o no enviado el comando al dispositivo
[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);

///
/// Expulsa el drive de acuerdo a la letra asignada
///
/// 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
/// Indica que se se hara lectura genérica del archvo
const uint GENERICREAD = 0x80000000;
/// Indica que se debe abrir un archivo existente, no crear uno nuevo
const uint OPENEXISTING = 3;
/// Comando enviado al dispositivo para abrir la puerta
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// Indica que la operaciónj no finalizo adecuadamente
const int INVALID_HANDLE = -1;


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

///
/// 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
///
/// Puntero que sirve como manejador del archivo
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

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


///
/// Nos permite enviar comandos de I/O a un dispositivo.
///
/// Indica si fue o no enviado el comando al dispositivo
[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);

///
/// Expulsa el drive de acuerdo a la letra asignada
///
/// 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
{

///
/// 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
///
/// Puntero que sirve como manejador del archivo
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(string fileName,
uint desiredAccess,
uint shareMode,
IntPtr attributes,
uint creationDisposition,
uint flagsAndAttributes,
IntPtr templateFile);

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


///
/// Nos permite enviar comandos de I/O a un dispositivo.
///
/// Indica si fue o no enviado el comando al dispositivo
[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
/// Indica que se se hara lectura genérica del archvo
const uint GENERICREAD = 0x80000000;
/// Indica que se debe abrir un archivo existente, no crear uno nuevo
const uint OPENEXISTING = 3;
/// Comando enviado al dispositivo para abrir la puerta
const uint IOCTL_STORAGE_EJECT_MEDIA = 0x2D4808;
/// Indica que la operaciónj no finalizo adecuadamente
const int INVALID_HANDLE = -1;

/// Puntero que se usara para apuntar al archivo (unidad) de CDRom
private static IntPtr fileHandle;
/// Indica el número de bytes leidos cmo rspuesta de un proceso
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:


///
/// Expulsa el drive de acuerdo a la letra asignada
///
/// 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!

Como cambiar el texto de los botones de un MessageBox - C#

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-cambiar-texto-botones-messagebox/

 ---

Por razones que aún no me son del todo claras, el .NET Framework no tiene textos localizables para los MessageBox, razón por la cual no siempre se muestran en el lenguaje que necesitamos, sino que se muestran en el lenguaje del Framework instalado.

 

Puede que nuestra aplicación este en español pero probablemente nuestros MessageBox siempre salgan con los labels de los botones en ingles:

image

Además no siempre los labels de los batones tienen textos convenientes para nuestras aplicación.

El Framework no nos ofrece ninguna manera de modificar dichos textos más allá de las opciones predeterminadas, ¿Qué podemos hacer?

No es una tarea tan sencilla como uno se imaginaria inicialmente, requiere del uso de la API de Windows y un poco de ingenio.

He creado una utilidad que te ahorrará mucho trabajo y podrás cambiar los textos de los botones de manera muy sencilla tal como te lo muestra el siguiente ejemplo:


MsgBoxUtil.HackMessageBox("SI","NO", "CANCELAR");
MessageBox.Show("hola", "hola", MessageBoxButtons.YesNoCancel);

MsgBoxUtil.HackMessageBox("REINTENTAR", "CANCELAR");
MessageBox.Show("hola2", "hola2", MessageBoxButtons.RetryCancel);

MsgBoxUtil.HackMessageBox("Descartar", "Reintentar", "Ignorar");
MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);

MsgBoxUtil.UnHackMessageBox();

MessageBox.Show("Normal", "Normal", MessageBoxButtons.AbortRetryIgnore);

 

El código completo de la utilidad lo puedes descargar aquí: MsgBoxUtil.cs

 

Cómo se hace?

Bueno hay que hacer las cosas al derecho, primero lo primero.

Análisis

Planteamiento inicial

  • Para modificar los textos hay que encontrar la manera de llegar hasta los botones del MessageBox
  • Para llegar a los botones del MessageBox primero hay que llegar al MessageBox

 

Dado que el .NET Framework no nos permite manipular la ventana del MessageBox debemos recurrir a la API de Windows.

Para Windows, todo control que se dibuje en pantalla es una ventana ( si, así es), así que si tuviéramos una forma, esta es una ventana que contiene paneles, los panales son también ventanas que contienen por ejemplo GroupBox que a su vez son ventanas que contienen botones , que a su vez también son ventanas.

Con base a estos puntos entonces detallemos un poco más el planteamiento.

  • Se deben buscar las ventanas hijas de la forma actual
  • Cuando se encuentre la ventana del MessageBox se deben buscar los botones
  • Con base a la ventana del  MessageBox se deben buscar sus ventanas hijas
  • A las ventanas que sean botones se les debe cambiar el texto
  • Dado que el MessageBox es una clase estática, la podemos dar por creada desde un comienzo ( el CLR la creara a la primera referencia que se haga).

Referencias a la API de Windows

Dado que esta tarea requiere de la API de Windows, y de acuerdo a nuestro planteamiento detallado, estas son las funciones de la API que utilizaremos:

  • EnumThreadWindows: Ejecuta un proceso por cada una de las ventanas del hilo actual, según se le indique. Lo usaremos para encontrar cual es la ventana del MessageBox.
  • GetCurrentThreadId: Obtiene el id del Thread en ejecución.
  • GetClassName: Obtiene el nombre de la clase a partir de un handler. Se utilizara junto con EnumThreadWindows para determinar si la ventana procesada es un MessageBox, dado que se desconoce el nombre que tendrán las ventanas del MessageBox, se debe determinar utilizando el ClassName de las ventanas. Más adelante veremos como hallar el ClassName de un MessageBox.
  • EnumChildWindows: Con base al handler de una ventana recorre cada una de sus ventanas hijas y ejecuta un proceso. Solo enumera las ventanas de primer nivel. Lo usaremos para encontrar los botones del MessageBox.
  • SetWindowText: Establece el texto asociado a una ventana, lo usaremos para modificar el texto de los Botones.

 

Diseño

Tenemos una única clase, utilicemos algo sencillo que ilustre el proceso de manera general, con esto bastará.

Diseno

  • Iniciaremos el proceso con EnumThreadWindows al cual le pasaremos un delegado que internamente revisará cada ventana enumerada para determinar si es o no un MessageBox.
  • Sino es un MessageBox revisará la siguiente ventana enumerada hasta que lo encuentre. Sino encuentra es que no hay MessageBox.
  • Cuando encuentra el MessageBox verifica a través de sus ventanas hijas con EnumChildWindows al cual se se pasa un delegado que internamente revisará cada ventana enumerada para determinar si es o no un Botón.
  • Cuando encuentra que es un botón modificará el texto asociado.

 

Implementación

Lo primero que debemos hacer es preparar las funciones a las que accederemos a través de interoperabilidad:


public class MsgBoxUtil
{
#region Interoperabilidad
private delegate bool EnumWindowDelegate(IntPtr handler, IntPtr longPointer);

[DllImport("user32.dll")]
private static extern bool SetWindowText(IntPtr handler, string texto);

[DllImport("user32.dll")]
private static extern int GetClassName(IntPtr handler, StringBuilder nombre, int tamañoMaximo);

[DllImport("user32.dll")]
private static extern bool EnumChildWindows(IntPtr handler, EnumWindowDelegate callback, IntPtr longPointer);

[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int threadID, EnumWindowDelegate callback, IntPtr longPointer);

[DllImport("kernel32.dll")]
private static extern int GetCurrentThreadId();

#endregion Interoperabilidad
}

 

No hay nada nuevo en lo que acabamos de ver, son las funciones que hemos mencionado desde el comienzo,  es de destacar únicamente EnumWindowDelegate que es un delegado (puntero a una función) que debemos usar cuando llamemos a EnumChildWindows y a EnumThreadWindows.

 

HackMessageBox

Acto seguido crearemos el método HackMessageBox, el cual recibe como parámetro las cadenas de texto que representan los labels de los botones del MessageBox. Esta función guarda una referencia estática a ese array de textos para poderlos usar en otros lugares de la clase, también se determina si hay o no un windows form y de ser así inicia el procesamiento llamando asíncronamente a EnumThreadWindow, sin embargo no llamaremos directamente a EnumThreadWindows porque requiere demasiados parámetros lo cual nos complica un poco el paso de parámetros en el llamado asíncrono (BeginInvoke), en su lugar llamaremos a BuscaMessageBox que es una función sin parámetros que llamará internamente a EnumThreadWindows con los parámetros necesarios.Dado que BeginInvoque requiere enviar un delegado, debemos definirlo previamente.

 

En el llamado a EnumThreadWindows le debemos pasar el ThreadId el cual obtenemos con un llamado a GetCurrentThreadId, el segundo parámetro es una función ProcesaMessageBoxEnForms que se ejecutará cada por cada ventana que se enumere, el tercer parámetro no lo necesitamos así que será Zero.


private static string[] textoBotones;
private delegate void BuscarMsgBoxDelegate();

public static void HackMessageBox(params string[] textoBotones)
{
MsgBoxUtil.textoBotones = textoBotones;

if (Application.OpenForms.Count > 0)
Application.OpenForms[0].BeginInvoke(new BuscarMsgBoxDelegate(BuscaMessageBox));
}

private static void BuscaMessageBox()
{
EnumThreadWindows(GetCurrentThreadId(), ProcesaMessageBoxEnForms, IntPtr.Zero);
}

 

ProcesaMessageBoxEnForms

Revisemos ProcesaMessageBoxEnForms, como esta funcion es llamada por EnumThreadWindows, esta le pasa siempre como parámetro el handler de la ventana que se esta enumerando, así que con ese handler buscaremos el nombre de la clase de la ventana enumerada para así poder compararlo con el nombre de la clase de un MessageBox, sino es un MessageBox entonces se devuelve true y así EnumThreadWindows vuelve a llamar a ProcesaMessageBoxEnForms pero pasando como parámetro la siguiente ventana enumerada, el procedimiento se repite hasta que se logra hallar la ventana del MessageBox. En ese momento se establece la variable indiceTexto en 0, esta variable la utilizaremos para recorrer el array de textos de acuerdo a los botones que encontremos dentro del MessageBox.

Seguidamente llamamos a EnumChildWindows pasándole como parámetro el handle de la ventana que desde luego es el MessageBox que acabamos de encontrar, así que las ventanas que se enumeraran son las subventanas del MessageBox entre ellas... los botones. Como ya encontro el MessageBox y realizo el proceso de busqueda de los botones se retorna false, de tal forma que EnumThreadWindows no siga enumerando ventanas.


private static int indiceTexto;
private const string MBOX_CLASSNAME = "#32770";
private const int STRING_BUILDER_CAPACITY = 256;

private static bool ProcesaMessageBoxEnForms(IntPtr handler, IntPtr longPointer)
{
StringBuilder nombreClase = new StringBuilder(STRING_BUILDER_CAPACITY);
GetClassName(handler, nombreClase, nombreClase.Capacity);

if (nombreClase.ToString() != MBOX_CLASSNAME)
return true;
else
{
indiceTexto = 0;
EnumChildWindows(handler, CambiaTextoBotonMessageBox, IntPtr.Zero);
return false;
}
}

 

BREAK> Y de dónde saqué que MBOX_CLASSNAME = "#32770"?

Algunos, o espero que todos los que no sepan se estarán haciendo esa pregunta.

Hay que utilizar herramientas que nos permitan hacer ingeniería inversa, una de estas herramientas es Spy++ que viene incluida con Visual Studio, no haremos un curso completo de Spy++ pero veremos como usarlo para obtener el ClassName de un MessageBox.

  1. Abrir un MessageBox de un programa de .NET Framework
  2. Abrir Spy++
  3. Ahora en Spy++ presionaremos esta tecla Spy
  4. Esto hace que se despliegue el siguiente dialogo:Spy  2
  5. Allí damos click sostenido en la figura señalada en rojo y la arrastramos hasta la ventana del MessageBox, teniendo cuidado de no seleccionar los botones u otras figuras, solo el marco principal. Soltamos el click y damos OK.
  6. Se abre un cuadro de dialogo,vamos a la pestaña Class y allí esta!!! ClassName = #32770.

Spy  3

END BREAK>

CambiaTextoBotonMessageBox

Una vez se ha encontrado el MessageBox se hace el llamado a EnumChildWindows pasándole como parámetro el handler del MessageBox, el segundo parámetro es la función CambiaTextoBotonMessageBox, el tercer parámetro es Zero.

 

EnumChildWindows invoca a CambiaTextoBotonMessageBox por cada una de las ventanas enumeradas, internamente CambiaTextoBotonMessageBox revisa si la ventana procesada es de clase Button, si no lo es entonces se devuelve true para que EnumChildWindow envie la siguiente ventana a proceso, esto continua así hasta encontrar tantos botones como cadenas de texto se hallan enviado en HackMessageBox como parámetro, a cada uno de los botones encontrados se le asigna su cadena de texto en el orden correspondiente.


private const string BUTTON_CLASSNAME = "Button";

private static bool CambiaTextoBotonMessageBox(IntPtr handler, IntPtr longPointer)
{
StringBuilder nombreClase = new StringBuilder(STRING_BUILDER_CAPACITY);
GetClassName(handler, nombreClase, nombreClase.Capacity);

if (nombreClase.ToString() == BUTTON_CLASSNAME && indiceTexto < textoBotones.Length)
{
SetWindowText(handler, textoBotones[indiceTexto]);
indiceTexto++;
}
return true;
}

Ejemplo de uso

Listo ya la funcionalidad esta terminada, pero solo para utilizarla dentro de Windows Forms, ya que si lo tratamos de usar por consola fallará. Sin embargo eso es fácil de solucionar y lo haremos en un próximo artículo. Esta es la manera correcta de utilizarlo.


MsgBoxUtil.HackMessageBox("SI","NO", "CANCELAR");
MessageBox.Show("hola", "hola", MessageBoxButtons.YesNoCancel);

MsgBoxUtil.HackMessageBox("REINTENTAR", "CANCELAR");
MessageBox.Show("hola2", "hola2", MessageBoxButtons.RetryCancel);

MsgBoxUtil.HackMessageBox("Descartar", "Reintentar", "Ignorar");
MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);

 

Les dejo este MessageBox con Botones Personalizados:


MsgBoxUtil.HackMessageBox("Acepto", "Lo Pensare", "Olvidalo");
MessageBox.Show("hola3", "hola3", MessageBoxButtons.AbortRetryIgnore);

 

MsgBox2

Reversar el MessageBox a su estado original también es posible de hacer, pero lo dejaremos para el próximo artículo, mientras pueden intentarlo si quieren.

 

Cordial saludo a Todos.

C# - Cuando la precisión que da el StopWatch no es suficiente

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-medir-nanosegundos/

 ---

Medir tiempo en nanosegundos

Hola!

He observado que es muy frecuente cuando alguien quiere hacer una prueba de rendimiento (sobre todo a nivel académico) que la resolución que da el objeto StopWatch (System.Diagnostics.Stopwatch) de Milisegundos resulta no ser siempre suficiente.

En esos casos lo mejor es recurrir a las funciones de la API para crear algo más de acuerdo a nuestras necesidades, de tal forma que podamos medir el tiempo transcurrido con una precisión mayor a la que da  - por alguna razón – el objeto StopWatch, así que creare algo sencillo que permitirá lograr la precisión deseada, pero primero – como siempre –algo de teoría al respecto.

 

CÓMO CALCULAR EL TIEMPO

Para calcular el tiempo dentro de un computador debemos valernos de la información que nos brinda el procesador, como todos sabemos el procesador posee un atributo llamado frecuencia, el cual nos indica cuantos ciclos de reloj realiza el procesador cada segundo. De esta forma encontramos que hay procesadores 1 Ghz (un millon de ciclos de reloj por segundo) y hay de muchos diferentes valores.

Por otro lado un procesador posee un contador de ciclos es decir un registro el cual informa de cuantos ciclos ha procesado.

Así que tenemos dos fuentes de información que utilizaremos para calcular el tiempo transcurrido ya que si dividimos la cantidad de ciclos que han pasado de un momento a otro entre la frecuencia, obtendremos el tiempo transcurrido con una precisión bastante grande (double).

 

Tenemos:

Frecuencia= ciclos por segundo

Ticks= ciclos procesados de un instante a otro

Tiempo = Ticks / Frecuencia (   ciclos / ciclos por segundo  )

De tal forma que las unidades resultantes serán: segundos.

 

Que!!! segundos? si pero esos segundos están expresados con una gran precisión decimal por lo cual podemos llegar a la precisión de nanosegundos tan solo multiplicando por 1.000‘000.000 (mil millones), y con un tipo de dato double tenemos espacio mas que suficiente para manejar estas cifras.

 

DE DONDE OBTENEMOS LOS DATOS?

Para ello utilizaremos dos funciones de la API de Windows:

  • QueryPerformanceCounter: Retorna el valor almacenado en el registro contador de ciclos del procesador en un momento dado
  • QueryPerformanceFrequency: Retorna la velocidad del procesador

 

Como ven ya esta todo lo necesario, ahora la implementación.

 

IMPLEMENTACIÓN

Lo primero es poder hacer uso de las funciones API  para lo cual nos ayudaremos con DllImport, ya saben la mejor fuente para saber como hacer declaraciones de la API de manera rápida es: http://www.pinvoke.net/. Todo esto lo hare dentro de la clase NanoTemporizador

using System;
using System.Runtime.InteropServices;

public class Temporizador
{
///
/// Obtiene la frecuencia del procesador
///
/// variable donde retorna la frecuencia
/// True si el procesador tiene contador de frecuencia, false sino
[DllImport"kernel32.dll", SetLastError = true)]
static extern bool QueryPerformanceFrequency(out long frequency);

///
/// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
///
/// variable donde retorna el valor
/// True si todo salio OK, false sino
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
}

Ahora en el constructor de nuestra clase no haremos nada :). Vale la pena recordar que siempre se deben crear componentes eficientes – según yo :P – por lo que es mejor que tengamos un constructor estático ya que realmente la frecuencia del procesador no cambiara nunca, así que solo es necesario calcularla una sola vez para todas las instancias.

    /// Almacena la frecuencia del contador de alto rendimiento
private static long _frecuencia;

static NanoTemporizador()
{
if (!QueryPerformanceFrequency(out _frecuencia))
throw new NullReferenceException(
"Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch"
);
}

Para que funcione realmente como un contador de tiempo necesitamos poder establecer si el contador esta andando o no, para lo cual crearemos una propiedad. Adicionalmente en el método Start del temporizador vams a calcular el valor de contador actual y a cambiar el valor de nuestro indicador a true:

    /// Almacena el valor de conteo inicial
private long _conteoInicial;
/// Indica si ya se ha inicializado el timer
private bool _isRunning = false;
/// Indica si ya se ha inicializado el timer
public bool IsRunning { get { return _isRunning; } }

public void Start()
{
if (!_isRunning)
{
QueryPerformanceCounter(out _conteoInicial);
_isRunning = true;
}
}

De igual forma se establece el método Stop:

    /// Almacena el valor de conteo final
private long _conteoFinal;

public void Stop()
{
if (_isRunning)
{
QueryPerformanceCounter(out _conteoFinal);
_isRunning = false;
}
}

Finalmente se crea una propiedad a travez de la cual podamos hallar el valor en nanosegundos:

    ///Retorna la cantidad de nanosegundos contados
public double ElapsedNanoseconds
{
get
{
return (_conteoFinal - _conteoInicial) * 1000000000L
/ (double)_frecuencia;
}
}

 

Perfecto, eso es todo. Esta es la versión completa:

 
using System;
using System.Runtime.InteropServices;

public class NanoTemporizador
{
///
/// Obtiene la frecuencia del procesador
///
/// variable donde retorna la frecuencia
/// True si el procesador tiene contador de frecuencia, false sino
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool QueryPerformanceFrequency(out long frequency);

///
/// Obtiene l evalor actual del contador de alto rendimiento del ptrocesador
///
/// variable donde retorna el valor
/// True si todo salio OK, false sino
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool QueryPerformanceCounter(out long lpPerformanceCount);

/// Almacena la frecuencia del contador de alto rendimiento
private static long _frecuencia;

/// Almacena el valor de conteo inicial
private long _conteoInicial;
/// Almacena el valor de conteo final
private long _conteoFinal;

/// Indica si ya se ha inicializado el timer
private bool _isRunning = false;
/// Indica si ya se ha inicializado el timer
public bool IsRunning { get { return _isRunning; } }

/// Valor por el cual se multiplican segundos para pasarlos a nanosegundos
private const long NANOSEGUNDOS = 1000000000L;

/// Valor por el cual se multiplican segundos para pasarlos a milisegundos
private const long MILISEGUNDOS = 1000L;

static NanoTemporizador()
{
if (!QueryPerformanceFrequency(out _frecuencia))
throw new NullReferenceException(
"Este componente se hizo para utilizar contadores de alto rendimiento. Como no los hay mejor utiliza StopWatch."
);
}

/// Inicia el conteo del temporizador
public void Start()
{
if (!_isRunning)
{
QueryPerformanceCounter(out _conteoInicial);
_isRunning = true;
}
}

/// Detiene el conteo del temporizador
public void Stop()
{
if (_isRunning)
{
QueryPerformanceCounter(out _conteoFinal);
_isRunning = false;
}
}

///Retorna la cantidad de nanosegundos contados
public double ElapsedNanoseconds
{
get
{
return (_conteoFinal - _conteoInicial) * NANOSEGUNDOS
/ (double)_frecuencia;
}
}

///Retorna la cantidad de milisegundos contados
public double ElapsedMilliseconds
{
get
{
return (_conteoFinal - _conteoInicial) * MILISEGUNDOS
/ (double)_frecuencia;
}
}

///Retorna la cantidad de segundos contados
public double ElapsedSeconds
{
get
{
return (_conteoFinal - _conteoInicial) / (double)_frecuencia;
}
}
}

CÓMO USARLO?

Bien este es un ejemplo tontisimo, pero muy ilustrativo:


using System;

namespace Prueba
{
class Program
{
static void Main(string[] args)
{
NanoTemporizador temporizador = new NanoTemporizador();

Probador(temporizador, 1000);
Probador(temporizador, 1000);
Probador(temporizador, 5000);
Probador(temporizador, 2358);
Probador(temporizador, 3541);
Probador(temporizador, 10000);

Console.ReadLine();
}

private static void Probador(NanoTemporizador temporizador, int espera )
{
temporizador.Start();
System.Threading.Thread.Sleep(espera);
temporizador.Stop();

Console.WriteLine("Tiempo transcurrido: {0} ns> ", temporizador.ElapsedNanoseconds);
Console.WriteLine("Tiempo transcurrido: {0} ms> ", temporizador.ElapsedMilliseconds);
Console.WriteLine("Tiempo transcurrido: {0} sg> ", temporizador.ElapsedSeconds);
Console.WriteLine("=====================================================");
}
}
}

Hasta pronto.

C# – la palabra clave volatile, explicación y ejemplos

Puedes ver el articulo original en  mi blog:

http://juank.black-byte.com/c-explicacion-ejemplo-volatile/

------

La palabra clave volatile es una de esas palabras clave muy pocas veces comprendidas, la documentación presente en msdn permite concluir que hay que utilizarla siempre que se manejen hilos, pero esto no siempre es así. Sin embargo lograr identificar que es lo que hace realmente esta palabra clave es una labor complicada así que dedicaré este artículo a explorar esta funcionalidad y a crear un ejemplo práctico que permita entender su verdadera naturaleza.

 

La documentación msdn

En msdn encontramos la siguiente definición de la palabra clave volatile:

La palabra clave volatile indica que varios subprocesos que se ejecutan a la vez pueden modificar un campo. Los campos que se declaran como volatile no están sujetos a optimizaciones del compilador que suponen el acceso por un subproceso único. Esto garantiza que el valor más actualizado está en todo momento presente en el campo

 

información completa

 

Debemos resaltar dos aspectos importantes de ese texto:

  1. Se menciona que los campos volatile no son susceptibles de optimizaciones por parte del compilador. Cuales optimizaciones?
  2. Dice que esto garantiza que el valor más actualizado siempre esta presente en el campo. No se supone que esto es así siempre?

A continuación revisaremos estas dos preguntas.

 

Optimizaciones del compilador

Siempre que compilamos un programa hecho con C# el compilador se encarga de convertir ese código C# en código de lenguaje IL, bueno realmente en OpCodes de IL. Esto es así de sencillo, pero resulta que cuando compilamos nuestro código en la configuración release o más específicamente cuando se marca la casilla de Optimizar código en el proyecto (ver imagen) el  compilador realiza una revisión general del código par determinar que cosas puede hacer funcionar de una manera mejor a la que codificó el programador inicialmente o incluso como puede cambiar las cosas en el ejecutable que no están en manos del programador ni del propio lenguaje para que a la hora de ejecutarse el programa sea más eficiente.

image

Estas optimizaciones son calculadas por el compilador haciendo uso de un complejo juego de reglas algunas de las cuales dependen de la arquitectura del procesador ( x86, x64, IA64, etc) y algunas otras del modelo de memoria que se esta trabajando. Los modelos de memoria son un tema complejo incluso desde sus fundamentos si bien no es algo imposible de aprender, pero quien quiera puede profundizar un poco más al respecto puede leer el libro de Sistemas Operativos Modernos de Andrew Tanembaum y desde luego buscar referencias adicionales en internet.

 

Adicionalmente a las optimizaciones realizadas por el compilador de C# tenemos una segunda etapa de compilación y optimizaciones realizadas por el JIT al momento de ejecutarse el programa, las cuales tiene su propio conjunto de reglas algunas de las cuales también están influenciadas por la arquitectura del procesador y el modelo de memoria.

 

El Valor Actualizado

Parte de las optimizaciones realizadas por el compilador pueden evitar que en algún momento determinado los campos de memoria donde se encuentran las variables no sean actualizados con su valor más reciente, porque?

 

Cada vez que el procesador hace una operación necesita colocar el valor de las variables en uno de sus registros, de tal forma que si tenemos por ejemplo un bucle con una suma de esta manera:


        int i = 0;
        while (condicion)
        {
            i = i + 1;
        }

 

El procesador enviaría el valor de “i” a uno de sus registros y el valor ”1” en otro registro, al obtener el valor de la suma el procesador debe colocar este valor de nuevo en memoria en la variable “i” de tal forma que al continuar el bucle todo el proceso se repite.

 

Esta secuencia sin embargo puede ser optimizada por el compilador ya que se hace innecesario que el valor de “i” sea copiado de los registros de la CPU a la memoria y viceversa tantas veces como dure el bucle, así que el compilador genera un código ejecutable que permite que los cálculos sean hechos en su totalidad en los registros de la CPU y solo hasta salir del bucle estos datos serian copiados de nuevo a la memoria.

Este tipo de optimizaciones que usan ”cache”  trae una mejora considerable en la ejecución de procesos intensivos a nivel matemático y funcionan bastante bien en la mayoría de los escenarios.

 

Optimizaciones de cache y el problema de los hilos

Cuando el compilador esta realizando las optimizaciones analiza (de acuerdo a los estándares) todo lo que pueda hacer referencia a las variables en el contexto de ‘subproceso’ actual ( realmente en la mayoría de sistemas operativos no se ejecutan los procesos sino los subprocesos y se le llama proceso a un grupo de subprocesos que comparten la memoria ), así que con base a lo analizado realiza las optimizaciones, por ello el problema surge cuando un hilo trata de acceder a una variable que esta siendo modificada por un objeto que se ejecuta en un hilo diferente, ya que es posible que el hilo dueño de la variable se encuentre en  medio de un proceso optimizado para funcionar por cache en los registros y al no estar verificando el valor de la variable en memoria pues no vera los cambios realizados, de hecho los sobrescribirá al finalizar. Así que en escenarios donde existan múltiples hilos lo ideal es bloquear este tipo de optimizaciones y estar muy atento a los casos donde no sea conveniente inactivarlas pues hay casos para todo.

 

El Ejemplo

Bien después de la teoría veamos la práctica, veremos ejemplos donde volatile no sirve para nada ( y creo que son la mayoría de casos en el mundo real – al menos en CPUs x86 ya que en IA64 al parecer el tema es muy diferente), y finalmente un caso que de acuerdo a la teoría que acabamos de ver funcionara perfectamente desde que lo compiles para x86.

Sobra decir que para probar los ejemplos lo debes hacer con la versión Release del ejecutable.

Ejemplo 1

Este es el ejemplo típico, donde todos usamos volatile porque así lo dice la documentación pero la realidad es que en estos casos no sirve de NADA.


using System.Threading;
using System;

static class BackgroundTaskDemo
{
    //volatile
    static int i = 0;
    private static bool stopping = false;

    static void Main ()
    {
        new Thread(EfectuarTrabajo).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void EfectuarTrabajo()
    {  
        i++;
        Console.WriteLine("Valor de i="+i);
        Console.WriteLine("EfectuarTrabajo exit " + i);
    }
}

 

Podríamos ponerle a "i" como volatile pero no habría diferencia puesto que básicamente el código no necesita ni tiene como ser optimizado. Así que volatile no bloquearía ninguna optimización.

Ejemplo 2

Este es el ejemplo tampoco sirve de NADA, si bien en el bucle se ve la necesidad de optimizar el rendimiento del código evitando que se copie el valor de "i" o de "stopping" de los registros de la CPU a la memoria y viceversa resulta que al convertir "i" a string estamos forzando al compilador a que no utilice el cache dado que necesita el valor mas reciente de "i" en la memoria para poder llamar el método ToString() y al método WriteLine, adicionalmente al llamar a alguno de esos métodos se requiere colocar en los registros de la CPU los valores necesarios para cambiar de contexto y para poder ejecutar las operaciones internas de dichos métodos, así que no hay manera de optimizar nada.


using System.Threading;
using System;

static class BackgroundTaskDemo
{
    //volatile
    private static bool stopping = false;

    static void Main ()
    {
        new Thread(EfectuarTrabajo).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void EfectuarTrabajo()
    {  
        int i = 0;

        while (!stopping)
        {
            i++;
            Console.WriteLine("Valor de i=" + i.ToString()); 
        }
        Console.WriteLine("EfectuarTrabajo exit " + i);
    }
}

 

Nuevamente podríamos ponerle a "i" como volatile pero no habría diferencia puesto que básicamente el código no necesita ni tiene como ser optimizado. Así que volatile no bloquearía ninguna optimización.

Ejemplo 3

En este SI que funcionan las optimizaciones, ya que el compilador optimizara el uso de "i" para que se apoye en los registros del procesador de principio a fin, eso es perfecto!, pero por otro lado "stopping" también es optimizado, trayendo como resultado que siempre este en uno de los registros y por tanto nunca se entere en que momento “stopping” cambio de valor para que saliera del bucle.


using System.Threading;
using System;

static class BackgroundTaskDemo
{
    private static bool stopping = false;

    static void Main ()
    {
        new Thread(EfectuarTrabajo).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void EfectuarTrabajo()
    {
        int i = 0;
        Console.WriteLine("Valor de i=" + i.ToString());
        while (!stopping)
        {
            i++;
        }
        Console.WriteLine("Valor de i=" + i.ToString());
        Console.WriteLine("EfectuarTrabajo exit ");
    }
}

 

Al ejecutar la version release para x86 de este programa veremos como, aunque en codigo parece que todo saldra bien, al ejecutarse el programa este nunca terminara pues el hilo nunca se entera que "stopping" cambio su valor. En pantalla veriamos perpetuamente

Main Exit

Y nada más :(

 

Ese es el ejemplo perfecto de la funcionalidad de volatile, así que podemos ahora marcar el campo "stopping" como volatile, compilar y ejecutar la versión release... y AHORA SI FUNCIONA COMO DEBERIA DE SER!!!


using System.Threading;
using System;

static class BackgroundTaskDemo
{
    private volatile static bool stopping = false;

    static void Main ()
    {
        new Thread(EfectuarTrabajo).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void EfectuarTrabajo()
    {
        int i = 0;
        Console.WriteLine("Valor de i=" + i.ToString());
        while (!stopping)
        {
            i++;
        }
        Console.WriteLine("Valor de i=" + i.ToString());
        Console.WriteLine("EfectuarTrabajo exit ");
    }
}

Ahora si en pantalla veríamos

Main Exit
EfectuarTrabajo exit xxxxxxxx

:)

Ejemplo 4

Este es el mismo ejemplo un poco mas elaborado haciendo uso de algunas operaciones de System.Math las cuales son suceptibles de optimización:


using System.Threading;
using System;

static class BackgroundTaskDemo
{
    //volatile
    private  static bool stopping = false;

    static void Main ()
    {
        new Thread(EfectuarTrabajo).Start();
        Thread.Sleep(5000);
        stopping = true;


        Console.WriteLine("Main exit");
        Console.ReadLine();
    }

    static void EfectuarTrabajo()
    {  
        int i = 0;
        int j = 2;

        while (!stopping)
        {
            i++;
            j = 100 + (int)Math.Sin((double)i * 10d);
        }
        Console.WriteLine("EfectuarTrabajo exit " + i);
    }
}

Prueben quitando y poniendo volatile y verán la diferencia.

Eso es todo.

 

Hasta pronto.