Archivo

Archive for 28 marzo 2010

Obtener información de una unidad de disco


.NET Framework pone a nuestra disposición una clase útil que nos servirá para obtener información relacionada con una unidad de disco. Estamos hablando de DriveInfo, ubicada en el espacio de nombres System.IO.

Comencemos con el constructor. Éste toma una cadena de texto como único parámetro, que representa la unidad de disco a la que se refiere. Ojo, porque lanza una excepción (ArgumentException) si se pasa un nombre de unidad inválido. Tradicionalmente usaríamos algo así:

 

try
{
    DriveInfo drive = new DriveInfo("C");
    ...hacer algo...
}
catch (ArgumentException e)
{
    Console.WriteLine("Formato inválido de unidad.");
}

 

Una alternativa, claro está, es listar las unidades de disco duro existentes. Para ello contamos con el método estático DriveInfo.GetDrives, que nos devuelve un array de objetos DriveInfo. Algo así:

DriveInfo[] drives = DriveInfo.GetDrives();
foreach (DriveInfo drive in drives)
{
    ...hacer algo...
}

 

Fuera del método estático GetDrives, y de los métodos estándares que hereda de Object y el ISerializable.GetObjectData que implementa (en efecto la clase hereda de ISerializable), no expone método alguno (quizás solo mencionar que la sobrecarga de ToString devuelve el nombre de la unidad). Pero sí tiene un conjunto de propiedades interesantes.

En primer lugar tenemos la propiedad DriveFormat. Esta nos devuelve el valor con el nombre del formato que tiene la unidad. Posibles valores son NTFS y el FAT32. Adicionalmente, tenemos la propiedad DriveType, que nos devuelve una enumeración de tipo DriveType con los siguientes posibles valores: Unknown (no se pudo determinar el tipo), NoRootDirectory (no he podido averiguar para qué sirve), Removable (la unidad es un dispositivo extraíble como un USB o un Flash), Fixed (lo contrario a Removable), Network (se trata de una unidad mapeada a un directorio en la red), CDRom (la unidad es un dispositivo óptico como una unidad de CD o de DVD) y finalmente Ram (indicando que la unidad se utiliza para memoria volátil).

Tenemos también las propiedades Name, RootDirectory y VolumeLabel, que nos devuelven el nombre de la unidad (por ejemplo, C:\, D:\, E:\, etc.), el directorio raíz (que como hablamos de una unidad de disco que por definición es raíz, prácticamente hablamos del mismo valor que la propiedad Name) y finalmente la etiqueta con la que hemos designado a nuestra unidad (los nombres son editables y pueden tener frases como “Disco personal”, “Disco de respaldo”, “CD-ROM”, etc.), respectivamente.

Con respecto al espacio y tamaño del disco, tenemos las siguientes propiedades. TotalSize devuelve el tamaño total de la unidad, en bytes. Por su parte TotalFreeSpace devuelve el espacio libre, mientras que AvailableFreeSpace devuelve el espacio libre disponible. La diferencia entre estas dos últimas es porque, por ejemplo, un disco duro puede tener 1 terabyte de espacio total, pero se comerá alrededor de 70 gigabytes para la tabla de direcciones por lo que solo estará disponible alrededor de 930 gigas. Usualmente utilizaremos AvailableFreeSpace cuando queramos ver si un archivo cabe o no en una unidad determinada.

Hay que tener un poco de cuidado, ya que las propiedades AvailableFreeSpace, DriveFormat, DriveType, TotalFreeSpace, TotalSize y VolumeLabel pueden generar una excepción de tipo IOException si la unidad produce algún error inesperado, usualmente que todavía no está disponible (por ejemplo si es un dispositivo extraíble). Para ello, siempre hay que revisar que la propiedad IsReady es true antes de llamar a estas otras propiedades, y en sistemas críticos deberíamos poner un bloque try-catch alrededor de estas llamadas.

El siguiente ejemplo muestra el empleo básico de la clase DriveInfo.

using System;
using System.IO;

...

try
{
    DriveInfo drive = new DriveInfo("C");
    if (drive.IsReady)
    {
        Console.WriteLine("Espacio disponible: {0}\n", drive.AvailableFreeSpace);
        Console.WriteLine("Formato: {0}\n", drive.DriveFormat);
        Console.WriteLine("Tipo: {0}\n", drive.DriveType);
        Console.WriteLine("Nombre: {0}\n", drive.Name);
        Console.WriteLine("Raíz: {0}\n", drive.RootDirectory);
        Console.WriteLine("Espacio total disponible: {0}\n", drive.TotalFreeSpace);
        Console.WriteLine("Tamaño total: {0}\n", drive.TotalSize);
        Console.WriteLine("Etiqueta: {0}\n", drive.VolumeLabel);
    }
    else
    {
        Console.WriteLine("La unidad \"C\" no está disponible en este momento.")
    }
} 
catch (ArgumentException e)
{
    Console.WriteLine("La unidad \"C\" no existe o es inválida. {0}", e.Message);
}
catch (IOException e)
{
    Console.WriteLine("Se ha producido un error en el sistema de entrada/salida. {0}", e.Message);
}
Categorías:.NET Framework, Apunte, C# Etiquetas:

Mirada al Visual C++ 2010


En estos días Jonathan Wood creó un video en el que hace un sumario rápido sobre las nuevas características de Visual C++ 2010. El vídeo aquí.

El vídeo resulta interesante, desde luego. En éste, Wood afirma que Microsoft ha decidido retomar el camino del código nativo y su plataforma Win32 – MFC. Se muestra complacido con ello, y comenta que los principales cambios son a la librería MFC así como al lenguaje mismo.

Las siguientes son las nuevas características.

  1. Mejoras a la librería MFC. Estas incluyen la incorporación de diversos controles como el Ribbon de la Fluent UI de Microsoft Office, mejores barras de herramientas, controles como botones con imágenes y cajas de texto con botón incluido, paneles adheribles, barra de Outlook y muchos más.
  2. Un diseñador para poder crear Ribbons. Así como diseñamos menús o barras de herramientas, ahora podemos diseñar ribbons.
  3. Un manejador de estilos para que el tema de la ventana se vea normal, como el de Office (en azul, negro, gris), como el de Visual Studio o como el de Windows 7.
  4. MFC Restart Manager. Un administrador para que las aplicaciones se recuperen (y recuperen documentos) cuando una aplicación se termina inesperadamente, guardando el documento sin salvar. Similar a como funciona Word o Visual Studio.
  5. El class wizard ha regresado.
  6. Nueva clase: CTaskDialog que se puede utilizar en lugar de los mensajes comunes disparados con AfxMessageBox. Solo funciona para Windows Vista en adelante.
  7. Actualizaciones al lenguaje C++: deducción de tipo de dato (“auto”), obtención del tipo de dato de una expresión (“decltype”), funciones lambda, referencias a un r-valor (valor asignado), puntero nulo (“nullptr”)

Aunque los avances presentados son importantes, también hay cosas que decir al respecto. En mi opinión:

  1. Las mejoras a MFC son importantes desde luego. El ribbon y demás componentes del Fluent UI son aire fresco para MFC, que no había sufrido una actualización significativa desde MFC 6.0. Además pone a MFC como la única plataforma de desarrollo soportada por Microsoft para desarrollar con el Fluent UI (ni Windows Forms ni Windows Presentation Foundation lo soportan). Pero estas actualizaciones salieron en el Visual C++ 2008 Feature Pack. Es decir, ya estaban disponibles como instalación aparte del VC2008, y para el 2010 solo las distribuyen “out-of-the-box”. Así que en otras palabras, ésto no es algo nuevo.
  2. El diseñador de ribbons está supremo. Antes era muy complicado ir creando los ribbons, con este diseñador la verdad que sí se lucieron estos tíos. ¡Gracias y enhorabuena!
  3. El manejador de estilos no es nuevo, también viene con el Feature Pack antes mencionado. Pero sí lo actualizaron para incluir el tema de Windows 7, lo cual es bueno, porque además soportan el utilizar el ribbon de Windows 7 (aquel que viene cuadradito, más minimalista, que se puede ver en mspaint o wordpad de W7).
  4. El Restart Manager de MFC supone un gran alivio cuando uno tiene que recuperar documentos. Un servicio más para nuestros usuarios de forma fácil.
  5. El class wizard nunca debió irse en primer lugar. En lo personal no me gusta utilizarlo, pero sé que hay mucha gente que sí lo utiliza. Luego entonces nunca debió desaparecer.
  6. Con Visual C++ 2008 podíamos utilizar un control nativo de Vista para hacer esto. Esta clase encapsula de mejor forma dicho control y lo hace extremadamente útil, ya que al usuario hay que proveerlo con mucha información cuando tiene que hacer decisiones importantes. Buen punto.
  7. Estas actualizaciones no son otra cosa que la incorporación de algunos puntos de C++0x. Está bien que lo hagan, pero el draft de C++0x todavía no queda completamente aprobado, así que tendremos que esperar a otra liberación de Visual C++ para poder tener la versión final de la nueva versión de C++. Sin embargo, bienvenido sean “auto” y “decltype” ya que ayudarán mucho en lo que a MFC se refiere.

Al final me parece que Visual C++ todavía queda a deber. Es cierto que el Feature Pack es algo muy bueno, solo que es una lástima que sigan sin cambiar muchas cosas de MFC que tienen pendientes. En lo personal me gustaría ver una buena librería para utilizar servicios web (sé que existe la Windows Web Services API pero me gustaría ver más integración con Visual C++) y una buena librería para XML (sé que existe MS-XML como componentes COM, pero distan mucho de ser fácilmente utilizables). ¿O qué tal mejorar las clases de ODBC para quitarle varias de las limitantes que tienen? Además muchos de las nuevas características son refritos del Feature Pack. Y por no mencionar que quitaron el soporte ISAPI para desarrollar sitios web con C++, ATL Server. Ojalá lo vuelvan a habilitar.

Pero al menos vamos por buen camino. Espero fervientemente que para la próxima versión Microsoft siga impulsando el desarrollo nativo, de la misma forma en que lo hacía cuando salió Visual C++ 6.

Categorías:C++, Noticias, Visual Studio Etiquetas:

Especialización parcial de plantillas


Un tema que me gustaría abordar es el de la especialización parcial de plantillas. Es algo sencillo, pero si no se tiene en cuenta, se puede caer en errores al leer un código. Consideremos el siguiente código:

template<class T>
T suma(const T& x, const T&amp; y)
{
  return x + y;
}

 

Evidentemente la función toma dos valores del mismo tipo y regresa la suma. Evidentemente el tipo de dato debe tener sobrecargado el operador de adición. Así, las siguientes llamadas funcionan de maravilla.

int i = 5, j = 4;
float f = 4.2, g = 3.7;

cout << suma(i, j) << endl;
cout << suma(f, g) << endl;

 

Sabemos de antemano que el compilador creará dos versiones de la función suma con el tipo de dato entero y otro para un número de punto flotante. Ahora supongamos que queremos pasarle como parámetros dos cadenas de texto. Y queremos -por supuesto- que la semántica sea diferente: en lugar de regresarnos la suma de las dos cadenas (lo cuál de antemano sería imposible ya que no hay un operador de adición sobrecargado) nos regrese la concatenación de la primera y la segunda cadena. ¿Cómo le hacemos?

Bueno, empleamos la especialización parcial de la plantilla. Es decir, basados en el concepto de que el compilador crea una versión de la plantilla para cada tipo de dato empleado, nos adelantamos y le decimos al compilador que cuando los parámetros sean char*, se comporte de diferente forma.

template<>
T suma<char*>(const T x, const T y)
{
  strcat(x, y);
  return x;
}

 

Lo anterior es la llamada especialización parcial. Nos adelantamos y le decimos al compilador que con char* no se meta, eso lo arreglamos nosotros. Por supuesto, el compilador hará su trabajo con respecto a los otros tipos de dato.

Por supuesto, lo anterior es cierto también para las clases. Más aún, en una clase podemos hacer la especialización parcial a nivel de toda la clase o sólo de una función. Incluso, podemos hacer cosas como lo siguiente:

template<class T>
class C
{
  public:
      static const bool m_bEsEspecializada = false;
};

template<>
class C<char>
{
  public:
      static const bool m_bEsEspecializada = true;
};

void foo()
{
  C<bool> c1;
  C<char> c2;

  cout << boolalpha;
  cout << c1.m_bEsEspecializada << endl;
  cout << c2.m_bEsEspecializada << endl;
}

 

Por supuesto, el primer valor mostrado en pantalla sería false mientras que el segundo sería true.

La especialización parcial de plantillas es muy potente ya que nos permite crear versiones diferentes para diferentes tipos de datos. Es, digámoslo de alguna manera, una forma de sobrecarga de funciones y clases a nivel de metaprogramación. Pero por supuesto sin emplear herencia y a niveles más amplios: el polimorfismo no nos deja cambiar valores de variables y además aplica a todo tipo de dato.

La especialización parcial de plantillas se emplea en muchos lugares. Por ejemplo, la clase std::numeric_limits la emplea para establecer las propiedades de los diferentes tipos de datos numéricos. Además, se emplea bastante en metaprogramación, como el concepto desarrollado por Andrei Alexandrescu de "clases basadas en políticas", tema que trataré más adelante.

Por supuesto, no deberíamos abusar del concepto, ya que cada especialización hace que se genere una nueva "versión" de la función o de la clase, y pues esto aumenta el tamaño de nuestro binario final.

Ya por último, dejo el código de un programilla que utiliza este concepto para realizar sumas de objetos (enteros, reales, etc.) y que cuando el tipo de dato es una cadena de texto (char*) entonces se devuelva una cadena concatenada.


#include <cstring>
#include <string>
#include <iostream>

using namespace std;

template<class T>
class operacion
{
    public:
        T suma(const T& t1, const T& t2)
        {
            return t1 + t2;
        }
};

template<>
class operacion<char*>
{
    public:
        char* suma(char* t1, char* t2)
        {
            return strcat(t1, t2);
        }
};

int wmain(int argc, wchar_t* argv[])
{
    operacion<int> op_int;
    int rs_int = op_int.suma(10, 5);
    cout << "Sumando enteros: 10 + 5 = " << rs_int << endl;

    operacion<float> op_flt;
    float rs_flt = op_flt.suma(1.8F, 7.4F);;
    cout << "Sumando reales: 1.8 + 7.4 = " << rs_flt << endl;

    char hola[50] = "Hola ";
    operacion<char*> op_str;
    char* res_sz = op_str.suma(hola, "Mundo");
    cout << "Sumando cadenas de texto: Hola + Mundo = " << res_sz << endl;

    cout << "Terminando..." << endl;
    cin >> string();

    return 0;
}


Categorías:Apunte, C++, WIN-32 Etiquetas: ,

Cómo obtener el número serial del disco duro (y más)


En ocasiones tenemos que obtener cierta información del sistema que parecería imposible a primera vista. El jefe nos dice: “necesitamos que obtengas el número de disco duro sobre el que corre la aplicación para poderlo usar al generar la licencia al cliente”. Entonces nos quedamos con cara de ¿¡¿what?!? y empezamos a dar vueltas en nuestro cubículo. Y al final resulta que es muy fácil obtener dicha información.

Microsoft cuenta con una herramienta para acceder a informacón de administración en un ambiente empresarial (redes, servidores, etc). Ésta recibe el nombre de WMI: Windows Management Instrumentation [1]. WMI es una implementación de Web-Based Enterprise Management, el cual es una iniciativa de la industria, y que utiliza el estándar Common Information Model para representar sistemas y aplicaciones, redes y dispositivos, de forma local o incluso de forma remota.

Para el caso que nos ocupa, WMI nos es de utilidad. Ya lo habrás adivinado, si no, no lo hubiera mencionado, ¿verdad?

En el caso de C# y el .NET Framework, la funcionalidad de WMI se encuentra contenida en el espacio de nombres System.Management, y hay que referenciar a la librería System.Management.dll. La mecánica para consultar información es sencilla:

  1. Crear un objeto de tipo ManagementObject pasándole como parámetro la consulta a realizar.
  2. Descargar la información desde WMI ejecutando el método Get.
  3. Recorrer la colección Properties del objeto para obtener los resultados.
    A continuación te presento un programita que hace exactamente lo anterior.
    
    using System;
    using System.IO;
    using System.Management;
    
    namespace Fermasmas.Wordpress.Com
    {
      class Program
      {
        static void Main(string[] args)
        {
          int count = 0;
    
          DirectoryInfo currentDir = new DirectoryInfo(Environment.CurrentDirectory);
          string path = string.Format("win32_logicaldisk.deviceid=\"{0}\"",
            currentDir.Root.Name.Replace("\\", ""));
          ManagementObject disk = new ManagementObject(path);
          disk.Get();
    
          foreach (PropertyData property in disk.Properties)
          {
            string name = property.Name.PadRight(25);
            string value = (property.Value ?? string.Empty)
              .ToString().PadRight(25);
            Console.WriteLine("Nombre: {0} Valor: {1}", name, value);
            if ((++count % 10) == 0)
            {
              Console.WriteLine("Presione una tecla para continuar o [ESC] para cancelar... ");
              ConsoleKeyInfo key = Console.ReadKey(true);
              if (key.Key == ConsoleKey.Escape)
                break;
            }
          }
    
          Console.WriteLine("Presione una tecla para terminar... ");
          Console.ReadKey(true);
        }
      }
    }
    
    

La cadena de texto que le paso al constructor representa una clase WMI, y el ya mencionado método Get descarga los datos desde WMI. De ahí, todo es pan comido: solo es cuestión de iterar sobre las propiedades y obtener la que nos interesa. De hecho, si ya sabes cual es el nombre de la propiedad que buscas, basta con invocar al indexador del ManagementObject. Por ejemplo, para ver el número serial del disco, basta con ejecutar este código:

DirectoryInfo currentDir = new DirectoryInfo(Environment.CurrentDirectory);
string path = string.Format("win32_logicaldisk.deviceid=\"{0}\"", 
    currentDir.Root.Name.Replace("\\", ""));
ManagementObject disk = new ManagementObject(path);
disk.Get();

string serial = disk["VolumeSerialNumber"].ToString();

Algunas referencias a continuación.

Referencias
  1. Acerca de WMI
  2. Cómo utilizar WMI
  3. Algunos ejemplos de WMI
Categorías:.NET Framework, C#, Cómo hacer Etiquetas:

Cómo detectar fugas de memoria con Visual C++


Fugas de memoria (memory leaks), cosas horribles del mundo, uno de los más grandes enemigos a los que el programador de C++ se puede enfrentar.

Todos las conocemos, ¿verdad? Ubicas memoria dinámica y olvidas desubicarla:

int main()
{
  int* p = new int();

  return 0;
  // no se elimina p con un delete, así que
  // aquí hay una fuga de memoria
}

 

Obvio el caso anterior difícilmente ocurriría en la vida real. Era un ejemplo simple. En esa vida real cosas mucho más complicadas pueden ocurrir. Y encontrar en dónde se encuentra la fuga es una tarea titánica.

Visual C++ provee algunas herramientas para poder detectar fugas de memoria. Cuando corremos nuestro programa en modo de depuración.

En primer lugar, cuando trabajamos con ATL o MFC, tenemos una macro llamada DEBUG_NEW que sirve para dar información sobre el archivo y línea donde se invoca la creación dinámica. Para ello tendríamos que hacer algo como:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

e incluirlo en todos los archivos. DEBUG_NEW lleva un registro de todos los objetos creados de forma dinámica, con el nombre del archivo y la línea de donde fueron instanciados. Cuando el programa termina, se llama al Object Dump de todos los objetos (en el caso de aquellos que deriven de CObject, se manda a la versión de CObject::Dump), lo que hace que se muestre información en la ventana de "Output" del IDE de Visual C++. Cuando hay un memory leak, así se presentará.

memoryleak

En la figura anterior les pongo un ejemplo. Corrí mi programa y al cerrarlo me detectó una fuga de memoria. En el output podemos ver los "dumps" o volcado de memoria que hace el IDE (en mi caso, Visual C++ 9). En particular, notamos que el primer volcado me lo redirecciona al archivo lookupoptions.cpp. Esto es gracias a que emplée DEBUG_NEW como indiqué anteriormente. Por ello, al hacer doble click me lleva directamente a donde se ubicó el elemento.

Por supuesto el saber dónde se generó el objeto es una parte del problema. Pero no siempre resulta obvio. Por ejemplo, en la figura anterior se crea un objeto con new y se agrega a una colección. Ésta se usa a lo largo de la existencia de una ventana hija y posteriormente la colección se encarga de eliminar todos los objetos que contenga. Por lo que la eliminación del objeto está en otra parte del código, posiblemente sin estar relacionada. Si el problema no es ubicar el objeto en memoria, sino desubicarlo.

Para poder detectar esto, normalmente tenemos que saber bajo qué condiciones se está creando nuestro objeto. En el ejemplo anterior, el IDE marca la clase que genera el objeto, pero no marca bajo qué condición se crea (i.e. qué otro objeto mandó crear la colección antes mencionada). ¿Cómo podríamos saberlo? Un breakpoint sería buena opción. Pero mejor aún, un breakpoint bajo las condiciones en las que se da la fuga. Pues bien, MFC / Visual C++ provée un mecanismo para hacer esto.

Cada objeto que se crea de forma dinámica tiene un ID, que representa su número de creación. Cuando se hace el volcado de la memoria para detectar la fuga, se imprime este ID entre llaves. En la figura anterior se muestran encerradas en un círculo rojo. En este caso, el objeto fue el 27620° en ser creado. Por otro lado, existe una función, _CrtSetBreakAlloc que toma como parámetro un número que es precisamente el número de objeto creado (en el caso del ejemplo anterior, 27620). Esta función hace que cuando se vaya a crear el objeto cuyo número es el del parámetro (i.e. 27620) se mande llamar a AfxDebugBreak, lo cuál actúa como un breakpoint. En ese momento, podemos revisar el stack y demás variables para saber cuál es el entorno y contexto de la creación de mi objeto, facilitando la caza del bug.

Luego entonces, basta mandar llamar a _CrtSetBreakAlloc al mero inicio del programa (main si estamos en consola, WinMain si es una aplicación para Windows, y CWinApp::InitInstance si trabajamos con MFC.

Me pareció interesante, ya que recientemente tuve este problema y pude resolverlo de forma fácil así. MSDN tiene éste artículo que viene con técnicas más avanzadas para detectar fugas de memoria, y éste artículo que habla sobre lo que les acabo de comentar.

Espero esto ayude a eliminar esas cosas horribles llamadas Fugas de Memoria.

Categorías:C++, Cómo hacer, Visual Studio Etiquetas:

Cómo modificar la configuración de un programa


Una de las cosas interesantes que nos proporciona el .NET Framework es la capacidad de crear y persistir archivos de configuración de forma fácil y rápida. Gracias a esto, ya no tenemos que crear nuestras propias clases para leer archivos INI o archivos del registro de Windows.

Cuando creamos un archivo de configuración y creamos elementos, éstos tienen un alcance: de usuario y de aplicación. Los de aplicación, a diferencia de los de usuario, no se pueden modificar. Lo malo del asunto es que hay configuraciones que necesariamente son de aplicación –como por ejemplo, las conexiones a base de datos. Así pues, surge la duda: ¿cómo cambiarlos programáticamente?

Al final de esta entrada muestro el código de un programa que muestra cómo hacer esto. Dicho programa luce así:

Config

El programa tiene tres partes importantes. La primera, es el archivo de configuración. Dentro de /Properties, se encuentran dos archivos: Settings.settings y Settings.Designer.cs. No hay mucho que decir: ambos archivos los genera el propio Visual C#. Baste decir que Settings.Designer.cs contiene una clase generada automáticamente, llamada Settings, y que deriva de System.Configuration.ApplicationSettingsBase. Esta clase, un singleton, nos permite acceder a los valores de configuración, y cambiar aquellos cuyo alcance sea de usuario (el VC# nos genera los métodos de forma automática).

La segunda parte importante es la clase Config. Ésta hereda de Form, y por lo tanto es una ventana. Contiene un control PropertyGrid al que le decimos que muestre el contenido de Settings.Default. Este control nos permitirá editar aquellas propiedades que sean de lectura y escritura. Si corres el programa, notarás que hay ocho propiedades. Las propiedades "BaseDeDatos", "Password", "Servidor" y "Usuario" son propiedades de lectura y escritura (por ende, con alcance de usuario), y por lo tanto se pueden modificar. Las propiedades "BaseDeDatosApp", "ServidorApp" y "UsuarioApp" son sólo de lectura (y por ende alcance de aplicación), por lo que no se pueden modificar. Finalmente, la propiedad "CnnStr" representa una cadena de conexión a datos y se guarda de forma diferente, además de ser de solo lectura y con alcance de aplicación (y sin posibilidad de poder cambiar este comportamiento). Puedes jugar con estos valores: cuando presiones el botón OK, se guardarán los cambios y saldrás de la aplicación. Al entrar, podrás ver los nuevos valores.

La tercera parte importante está dentro de la clase Config. Notarás que hay una caja de texto que reza: "Connection String:". Si escribes algo ahí, y luego pulsas el botón "Cambiar conexión", se ejecutará un procedimiento para modificar la configuración de dicha propiedad. Este procedimiento es el importante cuando se requiere cambiar valores de configuración cuyo alcance es de usuario. De hecho, lo importante sucede en unas cuántas líneas de código: entre la línea 30 y 35 del archivo Config.cs. Reproduzco esa línea:

Assembly assembly = Assembly.GetExecutingAssembly();
string path = assembly.Location;

Configuration config = ConfigurationManager.OpenExeConfiguration(path);
config.ConnectionStrings.ConnectionStrings["Config.Properties.Settings.CnnStr"].ConnectionString = _cnnstrText.Text;
config.Save(ConfigurationSaveMode.Modified);

Lo que aquí sucede es lo siguiente. Primero, obtenemos la ubicación del ensamblado que está ejecutando el código (esto es, del archivo config.exe). Esto lo hacemos por un problema que se explica adelante. Segundo, obtenemos la configuración para dicho archivo. Tercero, buscamos la sección de conexiones a base de datos (config.ConnectionStrings) y obtenemos la colección de conexiones de datos (config.ConnectionStrings.ConnectionStrings, lo sé, algo raro). En dicha colección buscamos la que corresponde a nuestra propiedad de configuración (en este caso,config.ConnectionStrings.ConnectionStrings["Config.Properties.Settings.CnnStr"] y establecemos la propiedad ConnectionString con el valor deseado. Por último, guardamos los cambios.

La clave de todo esto, por supuesto, es la llamada a ConfigurationManager.OpenExeConfiguration. A partir de ahí, solo hay que navegar entre las secciones. En este ejemplo, queríamos cambiar la conexión a la base de datos, pero lo mismo se puede hacer para cualquier otra sección. Ya dependerá de ti.

Ya por último, mencionar que si hacemos ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None) en lugar de como lo hicimos, tendremos un desagradable problema al querer ejecutar el código desde el Visual C#. Esto es debido a que el VC# genera, aparte del ejecutable en cuestión, otro ejecutable llamado vshost (usualmente [TuApp].vshost.exe), desde el cuál se lanza la depuración del programa. Al iniciarse el programa, pues, la aplicación genera el archivo [TuApp].exe.config, donde guarda las configuraciones por default. Pero como se ejecuta desde el vshost, entonces se creará el archivo [TuApp].vshost.exe.config, y ahí es donde la puerca tuerce el rabo, porque el código de ejemplo modificará a [TuApp].exe.config. Pero tú seguirás viendo [TuApp].vshost.exe.config. Por el contrario, si vas a tu carpeta Debug y haces doble clic sobre [TuApp].exe, todo funcionará a las mil maravillas. Así las cosas, la forma en la que llamamos a OpenExeConfiguration le da la vuelta a este problema.

Bueno, pues eso es todo.

Código

Config.cs
using System;
using System.Reflection;
using System.Configuration;
using System.Windows.Forms;
using Config.Properties;

namespace Config
{
    public partial class Config : Form
    {
        public Config()
        {
            InitializeComponent();
        }

        private void Config_Load(object sender, EventArgs e)
        {
            _configGrid.SelectedObject = Settings.Default;
            _cnnstrText.Text = Settings.Default.CnnStr;
        }

        private void _okButton_Click(object sender, EventArgs e)
        {
            Settings.Default.Save();
            Close();
        }

        private void _cnnstrButton_Click(object sender, EventArgs e)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            string path = assembly.Location;
            
            Configuration config = ConfigurationManager.OpenExeConfiguration(path);
            config.ConnectionStrings.ConnectionStrings["Config.Properties.Settings.CnnStr"].ConnectionString = _cnnstrText.Text;
            config.Save(ConfigurationSaveMode.Modified);

            DialogResult result;
            result = MessageBox.Show("Los cambios se harán patentes al reiniciar el programa. ¿Desea reiniciarlo ahora?", Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question);
            if (result == DialogResult.Yes)
                Application.Restart();

        }
    }
}

Config.designer.cs
namespace Config
{
    partial class Config
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this._configGrid = new System.Windows.Forms.PropertyGrid();
            this._tituloLabel = new System.Windows.Forms.Label();
            this._okButton = new System.Windows.Forms.Button();
            this._cnnstrLabel = new System.Windows.Forms.Label();
            this._cnnstrText = new System.Windows.Forms.TextBox();
            this._cnnstrButton = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // _configGrid
            // 
            this._configGrid.Location = new System.Drawing.Point(12, 25);
            this._configGrid.Name = "_configGrid";
            this._configGrid.Size = new System.Drawing.Size(318, 258);
            this._configGrid.TabIndex = 0;
            // 
            // _tituloLabel
            // 
            this._tituloLabel.AutoSize = true;
            this._tituloLabel.Location = new System.Drawing.Point(12, 9);
            this._tituloLabel.Name = "_tituloLabel";
            this._tituloLabel.Size = new System.Drawing.Size(305, 13);
            this._tituloLabel.TabIndex = 1;
            this._tituloLabel.Text = "A continuación se muestra las propiedades del objeto Settings. ";
            // 
            // _okButton
            // 
            this._okButton.Location = new System.Drawing.Point(255, 333);
            this._okButton.Name = "_okButton";
            this._okButton.Size = new System.Drawing.Size(75, 23);
            this._okButton.TabIndex = 2;
            this._okButton.Text = "OK";
            this._okButton.UseVisualStyleBackColor = true;
            this._okButton.Click += new System.EventHandler(this._okButton_Click);
            // 
            // _cnnstrLabel
            // 
            this._cnnstrLabel.AutoSize = true;
            this._cnnstrLabel.Location = new System.Drawing.Point(12, 286);
            this._cnnstrLabel.Name = "_cnnstrLabel";
            this._cnnstrLabel.Size = new System.Drawing.Size(94, 13);
            this._cnnstrLabel.TabIndex = 3;
            this._cnnstrLabel.Text = "Connection String:";
            // 
            // _cnnstrText
            // 
            this._cnnstrText.Location = new System.Drawing.Point(15, 302);
            this._cnnstrText.Name = "_cnnstrText";
            this._cnnstrText.Size = new System.Drawing.Size(315, 20);
            this._cnnstrText.TabIndex = 4;
            // 
            // _cnnstrButton
            // 
            this._cnnstrButton.Location = new System.Drawing.Point(142, 333);
            this._cnnstrButton.Name = "_cnnstrButton";
            this._cnnstrButton.Size = new System.Drawing.Size(107, 23);
            this._cnnstrButton.TabIndex = 5;
            this._cnnstrButton.Text = "Cambiar conexión";
            this._cnnstrButton.UseVisualStyleBackColor = true;
            this._cnnstrButton.Click += new System.EventHandler(this._cnnstrButton_Click);
            // 
            // Config
            // 
            this.AcceptButton = this._okButton;
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(342, 368);
            this.Controls.Add(this._cnnstrButton);
            this.Controls.Add(this._cnnstrText);
            this.Controls.Add(this._cnnstrLabel);
            this.Controls.Add(this._okButton);
            this.Controls.Add(this._tituloLabel);
            this.Controls.Add(this._configGrid);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
            this.MaximizeBox = false;
            this.MinimizeBox = false;
            this.Name = "Config";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "Configuración";
            this.Load += new System.EventHandler(this.Config_Load);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.PropertyGrid _configGrid;
        private System.Windows.Forms.Label _tituloLabel;
        private System.Windows.Forms.Button _okButton;
        private System.Windows.Forms.Label _cnnstrLabel;
        private System.Windows.Forms.TextBox _cnnstrText;
        private System.Windows.Forms.Button _cnnstrButton;
    }
}


Program.cs
using System;
using System.Windows.Forms;

namespace Config
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Properties.Settings.Default.Save();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Config());
        }
    }
}

Settings.designer.cs
namespace Config.Properties 
{    
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "9.0.0.0")]
    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 
	{
        private static Settings defaultInstance = new Settings(); 
        
        public static Settings Default {
            get { return defaultInstance; }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("")]
        public string Usuario {
            get { return ((string)(this["Usuario"])); }
            set { this["Usuario"] = value; }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("")]
        public string Password {
            get {
                return ((string)(this["Password"]));
            }
            set {
                this["Password"] = value;
            }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("")]
        public string BaseDeDatos {
            get { return ((string)(this["BaseDeDatos"])); }
            set { this["BaseDeDatos"] = value; }
        }
        
        [global::System.Configuration.UserScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("")]
        public string Servidor {
            get { return ((string)(this["Servidor"])); }
            set { this["Servidor"] = value; }
        }
        
        [global::System.Configuration.ApplicationScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("fgomez")]
        public string UsuarioApp {
            get { return ((string)(this["UsuarioApp"])); }
        }
        
        [global::System.Configuration.ApplicationScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("midb")]
        public string BaseDeDatosApp {
            get { return ((string)(this["BaseDeDatosApp"])); }
        }
        
        [global::System.Configuration.ApplicationScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.DefaultSettingValueAttribute("192.168.0.28")]
        public string ServidorApp {
            get { return ((string)(this["ServidorApp"])); }
        }
        
        [global::System.Configuration.ApplicationScopedSettingAttribute()]
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
        [global::System.Configuration.SpecialSettingAttribute(global::System.Configuration.SpecialSetting.ConnectionString)]
        [global::System.Configuration.DefaultSettingValueAttribute("server=epeople28; database=sternpluspos; user=fgomez")]
        public string CnnStr {
            get { return ((string)(this["CnnStr"])); }
        }
    }
}

Settings.settings
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Config.Properties" GeneratedClassName="Settings">
  <Profiles />
  <Settings>
    <Setting Name="Usuario" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="Password" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="BaseDeDatos" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="Servidor" Type="System.String" Scope="User">
      <Value Profile="(Default)" />
    </Setting>
    <Setting Name="UsuarioApp" Type="System.String" Scope="Application">
      <Value Profile="(Default)">fgomez</Value>
    </Setting>
    <Setting Name="BaseDeDatosApp" Type="System.String" Scope="Application">
      <Value Profile="(Default)">midb</Value>
    </Setting>
    <Setting Name="ServidorApp" Type="System.String" Scope="Application">
      <Value Profile="(Default)">192.168.0.28</Value>
    </Setting>
    <Setting Name="CnnStr" Type="(Connection string)" Scope="Application">
      <DesignTimeValue Profile="(Default)">&lt;?xml version="1.0" encoding="utf-16"?&gt;
&lt;SerializableConnectionString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"&gt;
  &lt;ConnectionString&gt;server=epeople28; database=sternpluspos; user=fgomez&lt;/ConnectionString&gt;
&lt;/SerializableConnectionString&gt;</DesignTimeValue>
      <Value Profile="(Default)">server=epeople28; database=sternpluspos; user=fgomez</Value>
    </Setting>
  </Settings>
</SettingsFile>
app.settings
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <configSections>
        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="Config.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
        </sectionGroup>
        <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
            <section name="Config.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
        </sectionGroup>
    </configSections>
    <connectionStrings>
        <add name="Config.Properties.Settings.CnnStr" connectionString="server=epeople28; database=sternpluspos; user=fgomez" />
    </connectionStrings>
    <userSettings>
        <Config.Properties.Settings>
            <setting name="Usuario" serializeAs="String">
                <value />
            </setting>
            <setting name="Password" serializeAs="String">
                <value />
            </setting>
            <setting name="BaseDeDatos" serializeAs="String">
                <value />
            </setting>
            <setting name="Servidor" serializeAs="String">
                <value />
            </setting>
        </Config.Properties.Settings>
    </userSettings>
    <applicationSettings>
        <Config.Properties.Settings>
            <setting name="UsuarioApp" serializeAs="String">
                <value>fgomez</value>
            </setting>
            <setting name="BaseDeDatosApp" serializeAs="String">
                <value>midb</value>
            </setting>
            <setting name="ServidorApp" serializeAs="String">
                <value>192.168.0.28</value>
            </setting>
        </Config.Properties.Settings>
    </applicationSettings>
</configuration>
Categorías:.NET Framework, C#, Cómo hacer Etiquetas:

Convertir un número a representación binaria


Un problema que suele ser recurrente para los estudiantes es el saber cómo convertir un número en su representación binaria. Cabe recordar que un número, por definición, es binario. ¿Qué hace este ejemplo? Pues muestra su representación en unos y ceros, pero en una cadena de texto.

La clase que presento a continuación, binario, es una plantilla que toma cualquier tipo de dato que se desee convertir. La clase presenta un solo miembro privado, _t, de tipo parametrizado, cuyo valor será convertido a una cadena de texto con su representación binaria. A su vez, la clase tiene tres constructores: un constructor por default, un constructor copia y un constructor que inicializa el valor. La clase también tiene un método para obtener el valor y uno para establecerlo, por si no se quiere utilizar el constructor.

Pero lo verdaderamente interesante pasa en el método convertir. Este método hace tres cosas. En primer lugar, toma los bytes del objeto a convertir (i.e. _t) y los guarda en un búfer de memoria. Luego, para cada byte del arreglo, va evaluando cada uno de los bits que lo conforman, y pregunta si el bit es uno o cero, e inserta una representación de texto en una variable, que al final tendrá el valor resuelto. Esto lo podemos ver en el siguiente fragmento:

while (actual != 0)
{
    if (actual & 1) 
        str.insert(0, "1");
    else
        str.insert(0, "0");

    actual >>= 1; 
}

La variable actual guarda el byte actual que se está evaluando. El if hace una conjunción a nivel de bits y pregunta si el bit de hasta la derecha está activo o no. Por ejemplo, suponiendo que actual sea un 3, cuyo binario es 1 1, entonces la operación sería:

    1 1
  & 0 1
 --------
    0 1

Lo cual regresaría una expresión verdadera. Ahora, si en lugar de 3 fuera un 4, tendríamos:

    1 0 0
  & 0 0 1
 ----------
    0 0 0

Lo cuál nos regresaría una expresión falsa. Finalmente, después del if, desplazamos el valor un bit a la derecha. Así, si teníamos un 1 1, desplazando a la derecha tenemos un 0 1; y si tenemos un 1 0 0, entonces ahora tendríamos un 0 1 0. Cuando ya no queden bits, todo se evaluará a cero y se saldrá del bucle.

He aquí el código completo de la clase.

using std::string;
using std::memcpy;

template<class T>
class binario
{
    private:
        T _t;

    public:
        binario()
        {
        }

        binario(const T& t)
        {
            _t = t;
        }

        binario(const binario& convertidor)
        {
            _t = convertidor._t;
        }

        virtual ~binario()
        {
        }

        const T& valor() const
        {
            return _t;
        }

        void valor(const T& t)
        {
            _t = t;
        }

        virtual string convertir() const
        {
            unsigned char* bufer;
            unsigned char actual;
            size_t size;
            string str;

            // convertimos los bytes que tenga el objeto a serializar
            // en un búfer de memoria, copiando byte por byte.
            size = sizeof(T);
            bufer = new unsigned char[size];
            memcpy(bufer, &_t, sizeof(T));

            // cada elemento del búfer es un byte, por lo que tendremos
            // que ir convirtiendo byte por byte. el bucle for siguiente
            // recorre todos los bytes en el búfer.
            for (size_t i = 0; i < size; i++)
            {
                // el byte actual
                actual = bufer[i];

                // un byte tiene, por lo general, ocho bits. lo que
                // hacemos en el siguiente bucle es simular que recorremos
                // bit por bit, para ver si es un uno o un cero. para esto
                // empleamos el operador lógico "y" a nivel de bit, y 
                // preguntamos si el bit de más a la derecha es un uno
                // (en el if). de ser así, lo guardamos en la cadena de
                // texto como "1", y si no, como "0". finalmente, 
                // desplazamos los bits del byte una posición a la
                // derecha, de tal suerte que nos deshacemos del bit
                // ya evaluado y procedemos a evaluar el siguiente bit.
                // cuando ya no queden bits, el byte tendrá un valor
                // de cero y salimos del bucle, para evaluar el 
                // siguiente byte.
                while (actual != 0)
                {
                    if (actual & 1) // ¿el bit de la derecha es un uno?
                        str.insert(0, "1");
                    else
                        str.insert(0, "0");

                    // desplazamos los bits un espacio a la derecha
                    // para deshacernos del bit actualmente evaluado
                    actual >>= 1; 
                }
            }

            delete [] bufer;
            
            return str;
        }
};

El siguiente código es un programita que utiliza dicha clase para obtener la representación binaria de cualquier argumento.


#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include "binario.h"

using std::cout;
using std::cin;
using std::endl;
using std::string;

struct persona
{
    char nombre[20];
    char puesto[20];
    int edad;
    double salario;
};

int main()
{
    binario<int> convEntero;
    binario<char> convCaracter;
    binario<double> convDecimal;
    binario<persona> convPersona;
    string comando;
    int rangoInicial;
    int rangoFinal;
    double decimal;
    Persona persona;

    cout << "***** Un rango de enteros *****" << endl;
    cout << "Escribe el rango inicial: ";
    cin >> rangoInicial;
    cout << "Escribe el rango final: ";
    cin >> rangoFinal;
    cout << endl;
    for (int i = rangoInicial; i <= rangoFinal; i++)
    {
        convEntero.valor(i);
        cout << " El valor para " << convEntero.valor() << " es: " << convEntero.convertir() << endl;
    }
    system("pause");
    cout << endl;

    cout << "***** Una cadena de texto *****" << endl;
    cout << "Escribe una cadena: " << endl;
    cin >> comando;
    for (size_t i = 0; i < comando.size(); i++)
    {
        convCaracter.valor(comando.at(i));
        cout << " " << convCaracter.convertir();
    }
    cout << endl;
    system("pause");
    cout << endl;

    cout << "***** Un valor decimal *****" << endl;
    cout << "Escribe un valor decimal: " << endl;
    cin >> decimal;
    convDecimal.valor(decimal);
    cout << convDecimal.convertir() << endl;
    cout << endl;
    system("pause");
    cout << endl;

    cout << "***** Una estructura plana cualquiera *****" << endl;
    strcpy(persona.nombre, "fernando");
    persona.edad = 27;
    strcpy(persona.puesto, "programador");
    persona.salario = 25751.75;
    convPersona.valor(persona);
    cout << convPersona.convertir() << endl;
    cout << endl;
    system("pause");
    cout << endl;

    return 0;
}


Categorías:C++, Cómo hacer, Independiente Etiquetas: