Erwin.Ried.cl / Desarrollos / Visual Studio /
Apología a las amadas y odiadas librerías DLL

Todo computín tendrá alguna vez un encuentro con estas nebulosas librerías, probablemente llegues a odiarlas pero lo cierto es que con Visual Studio es una experiencia increíble trabajar con ellas. Veamos paso a paso como erigir una librería que de paso nos servirá para gestionar otro de los espantajos conflictivos de Windows, como son los instaladores (19/01/2008 12:57 AM)


Tal como en los tiempos del antiguo, sucio y adorable (¡snif!) Visual Basic (no del maquillado y elegante C# al que llaman Visual Basic .NET ahora) crear librerías con Visual Studio (en particular con la versión 2005 o superior) es realmente sencillo y útil. Una librería encapsula una serie de elementos que podrán ser consumidos desde casi cualquier fuente, así podrías hacerte de una serie de librerías que usarás desde diferentes aplicaciones y podrías reutilizar código incluso más fácil, versátil y pulcramente que copiándolo y pegándolo directamente.

En resumen, una librería será un espacio de nombres, tal como el espacio que encapsula todos los elementos de nuestra aplicación principal, la librería contendrá sus propios elementos, clases, estructuras, enumeraciones y más.


1. El pretexto

El artículo actual tiene como fin mostrar la base de la creación y utilización de librerías DLL (bibliotecas de clases) sin embargo el pretexto de este tutorial será crear una aplicación que nos permita administrar nuestras aplicaciones instaladas (especialmente la lista de agregar o quitar programas).

La idea es crear un programa moderno que nos permita "quitar" elementos instalados con la seguridad de poder restaurarlos posteriormente. La aplicación simplemente ocultaría los registros que hacen referencia a las instalaciones a voluntad del usuario.



La librería administrará todas las acciones con el registro, proporcionando las listas de instaladores y las rutinas necesarias para ocultarlos y restaurarlos. De esta forma nuestra aplicación será solo una interfaz de la misma, para comprobarlo también implementaré una ejecutable de línea de comandos que hará uso de la misma librería.


2. Primeros pasos con la librería

Para comenzar con nuestra librería lo único que debemos hacer es seleccionar "biblioteca de clases" como nuevo proyecto.



En este instante crearemos una estructura que representará un instalador y una lista que serán las opciones de filtrado de los mismos.

Para utilizar posteriormente la librería en otro proyecto, simplemente debemos añadirla como "Referencia" a nuestro nuevo proyecto:



Así la utilizaríamos como si fuera una clase, llamándola primero por su nombre, es decir:

Código:
Libreria.Clase nombre = new Libreria.Clase([parámetros,...]);


Nota
El código mostrado en el artículo está escrito de forma simplificada.


Entonces nuestra estructura es similar a:

Código:
public struct Uninstaller
{
    public String UninstallInternalName;
    public String UninstallFullKeyPath;
    public String UninstallDisplayName;
    public Boolean HasDisplayName;
    public Boolean IsVisible;
}


Las opciones de filtrado tienen una particularidad:

Código:
[Flags]
public enum UninstallType
{
    OnlyNamed = 1,
    OnlyNotNamed = 2,
    AnyType = OnlyNamed | OnlyNotNamed
}


El secreto tras las potencias de dos
En la enumeración anterior, arriba de la declaración de la misma está el atributo [Flags] el cual guarda una estrecha relación con las potencias de dos.

Esta relación se usa frecuentemente en todas las librerías del sistema y aunque en este caso en particular no tiene una trascendencia mayor, es importante explicar su función. Imaginemos las propiedades de un texto:



Adicionalmente podríamos combinar sus propiedades:



Entonces ¿cómo podríamos ahora llamar a una función que aplique formato del texto?

Código:
aplicarFormato(Boolean negr, Boolean curs, Boolean subr);


Pero, ¿y si quisiéramos sólo utilizar una variable?:

Código:
aplicarFormato(int formato);


Lo normal sería hacer una tabla por cada una de las combinaciones, asociándolas a un número, en efecto obtendríamos algo así:



Ahora, volviendo a la primera propuesta, ¿qué tienen que ver las potencias de dos acá?:



¿Se nota?, los valores que son potencias de dos son características únicas que definen sólo un bit, en cambio las características compuestas son la suma de las características única. Así cuando especifico [Flags] en la enumeración le estoy indicando al compilador que trate de esta forma los elementos de la enumeración misma, así con OR podremos generar configuraciones compuestas, evidentemente:



Astuto ¿cierto?


Entonces tenemos ya las definiciones principales, ahora quedaría el cuerpo del código. Como demostración sólo mostraré uno de los constructores más relevantes:

Código:
public class UninstallManager
{
    /// <summary>
    /// Clase para administrar los desinstaladores de programas
    /// </summary>
    /// <param name="baseKey">Clave base de los instaladores</param>
    /// <param name="installerName">Clave para buscar el instalador</param>
    /// <param name="installerSubKey">Subclave contenedora</param>
    public UninstallManager(String baseKey, String installerName, String installerSubKey):
        this(baseKey, installerName, installerSubKey, installerSubKey + "Hidden",true)
    {   }
}


También, secretamente, quería mostrarles también cómo pueden invocar a otros constructores para no tener que repetir varias veces las mismas asignaciones clásicas de los constructores, de hecho, al igual que en otros lenguajes, si el contructor está vacío se podría omitir y la clase compilaría de todas formas.


3. La información sobre las instalaciones

Para acceder a la información sobre los desinstaladores que aparecen en la lista de agregar o quitar programas me basaré en las siguientes claves del registro:

Código:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\
HKLM\SOFTWARE\Classes\Installer\
HKCU\SOFTWARE\Microsoft\Installer\


De esta forma, lo único que haré será recorrer las subclaves en busca de todas las que contengan un registro específico, denominado "DisplayName", generalmente, aunque es personalizable.

Así el código es bastante explícito:

Código:
// Para cada ruta existente como subclave...
foreach (String uninstaller in uninstallStringList)
{
    uninstallKeyPath = uninstalls.Name + "\\" + uninstaller;

    // Obtener el nombre
    String uninstallName = (String)Registry.GetValue(uninstallKeyPath, 
      displayName, "");

    Boolean addToOutput = true;
    if (uninstallName.Length > 0 && filterMode == UninstallType.OnlyNotNamed) 
        addToOutput = false;  // Tiene nombre
    else
        if (uninstallName.Length == 0 && filterMode == UninstallType.OnlyNamed)
            addToOutput = false; // No tiene nombre

    if (addToOutput) // Sirve, tiene el elemento dentro y pasa por los filtros
    {
        Uninstaller u;
        u.UninstallInternalName = uninstaller;
        u.UninstallDisplayName = uninstallName;
        u.UninstallFullKeyPath = uninstallKeyPath;
        u.IsVisible = uninstallVisible;

        // Añadirla a la lista
        uninstallList.Add(u);
    }
}



4. Ocultando las instalaciones ¿cierto?

Una parte importante del proceso será ocultar las instalaciones (en efecto, es el propósito principal del programa). Esta tarea tiene un secreto, Visual Studio no proporciona directamente funciones para copiar ni mover ramas completas de registro entonces tendremos que trabajar primero en implementar esta funcionalidad y luego podremos ocultar las instalaciones.

¿Por qué necesitamos mover o copiar una rama completa de registro para ocultar una instalación?
Pues porque es la forma más sencilla de hacerlo, la otra posibilidad es remover los privilegios de lectura de la clave, sin embargo esto podría traer graves consecuencias posteriores.

Así lo que haré simplemente será crear una clave hermana a la contenedora de la clave que queremos "ocultar" y moveré la rama completa hacia la "hermana", así también podré restaurar fácilmente los instaladores ocultos repitiendo el proceso a la inversa.



5. Copiando y moviendo ramas completa del registro

La tarea de copiar y mover son muy similares, de hecho son lo mismo, el único detalles es que al mover, copiamos y borramos el contenido copiado. Esta similitud está muy presente en los usuarios de Linux, especialmente aquellos habituados a utilizar la terminal del sistema.

De esta forma podríamos abarcar muy suciamente este procedimiento, con ayuda del comando "reg", exportar, modificar y reimportar el registro para copiarlo. Estas soluciones son buenas para alcanzar un objetivo, pero nuestra aplicación será una obra, aprenderemos y por consiguiente aquí está la siguiente pincelada en nuestro lienzo etéreo:

Código:
/// <summary>
/// Copia una rama del registro entera a un destino
/// </summary>
/// <param name="root">Clave pivote de la copia</param>
/// <param name="source">Subclave de origen</param>
/// <param name="destination">Subclave de destino</param>
/// <returns>Resultado de la operación</returns>
public Boolean CopyKeyTree(String root, String source, String destination)
{
    try
    {
        RegistryKey s, d;

        s = Registry.LocalMachine.OpenSubKey(root, false).OpenSubKey(source, false);
        d = Registry.LocalMachine.OpenSubKey(root, true).CreateSubKey(destination);
       
        if (d.GetValueNames().Length + d.GetSubKeyNames().Length > 0)
            return false;

        foreach (String n in s.GetValueNames())
            d.SetValue(n, s.GetValue(n), s.GetValueKind(n));

        foreach (String n in s.GetSubKeyNames())
            CopyKeyTree(root, source + "\\" + n, destination + "\\" + n);
            
        return true;
    }
    catch
    {
        return false;
    }
}


¿Se entiende?, primero abro las llaves de origen y destino, luego copio todas las llaves de registro de la ruta actual. Para las subclaves vuelvo a llamar a la misma función de copia con los parámetros de la subclave misma, repitiendo el proceso, recursivamente.

Ahora, para mover una clave es lo mismo pero eliminando la clave de origen (más pistas de su implementación dentro del código).


6. Ocultando las instalaciones

Después del pequeño break educativo, ahora podemos entrar de lleno a ocultar las aplicaciones instaladas en las llaves comentadas anteriormente.

Primero, cuando el usuario "desmarque" un instalador, llamaremos a nuestra librería que usara una función extremadamente sencilla:

Código:
/// <summary>
/// Cambiar el estado de una clave de registro
/// </summary>
/// <param name="uninstallerKey">Clave de registro</param>
/// <param name="visibility">Visible u oculto</param>
/// <returns>Resultado de la operación</returns>
public Boolean ChangeVisibility(String uninstallerKey, Boolean visibility)
{
    String source = uninstallBaseKeySystem,
        destination = uninstallBaseKeyHidden;

    if (visibility)
    {
        source = destination;
        destination = uninstallBaseKeySystem;
    }

    return MoveKeyTree(uninstallBaseKey, source + "\\" + uninstallerKey, destination +
"\\" + uninstallerKey);
}


Y ahora viene la parte difícil... ¡mentí!, no hay parte difícil, sólo un poco de lógica y como siempre un resultado satisfactorio.


7. Bueno y... ¿la librería?

Claro, claro, hicimos una librería que por el momento está cumpliendo la misma función que si hubiera sido una clase dentro del programa original... entonces, ¿cuál es la gracia?

Veamos, creamos otro proyecto en la solución de Visual Studio, ahora una aplicación de consola:



De igual forma hacemos referencia a nuestra librería (si no la librería no fuera de la misma solución tendríamos que hacer referencia al archivo DLL).

¡Ahora usamos la librería desde nuestra aplicación de consola!, ¿qué más elegante?:

Código:
static void Main(string[] args)
{
    CommandLineArgs cmd = ParseArgs(args);

    if (cmd.ShowHelp)
        ShowHelp();
    else
        new UninstallAdmin().ListUninstallers(cmd.Filter, cmd.ShowDetails); // Nuestra
librería
}


Agotador, ¿no es así?


8. Código y ejecutables

La vista previa para los indecisos:



Código fuente para Visual Studio 2005 (C#)
Aplicación para Windows (Uninstall Hidder)
Aplicación para consola (Uninstall Hidder)
Sólo la librería compilada (DLL)

Por cierto, anteriormente no les comenté sobre la posibilidad de personalizar este pequeño programita por medio del archivo de configuración que incluye, en efecto no es tan interesante al verlo en el diseñador de Visual Studio:



De hecho podríamos conseguir ocultar casi cualquier llave del registro. A mi parecer lo ideal sería trabajar un poco más en el mismo y lograr una aplicación "universal", pero claro, no será para este episodio.


9. Conclusiones

Otro ejemplo más de lo entretenido y productivo de la programación en este entorno, especialmente por lo curioso que pueden llegar a ser para alguien relacionado con computación todo este mundo de las librerías que Windows nos introduce, particularmente con menos fuerza que antes con el famoso infierno de las DLLs.

Claramente nuestra librería dista mucho de ser un producto profesional y óptimo, incluso alejándose del bajo nivel que presentan librerías del sistema, sin embargo, no quita la utilidad de las mismas.

Haga clic sobre una de las estrellas para calificar este artículo.

Opiniones y comentarios (Escribir un nuevo comentario)
Jesus: si para Uds es lo mismo Ir a un lugar y valerse de la información de los objetos que están en él (biblioteca), que, Ir a un lugar por el que se debe pagar por cada objeto que solo podrá usar en otro sitio. Es otra cosa. Aplicando el mismo cirterio * Ni Uds chilenos ni los argentinos somos americanos, ya que sería un gentilicio únicamente válido para los nativos de Estados Unidos * Actualmente sería lo mismo que "de hecho" * largo sería lo mismo que grande etc. Probablemente esa sea parte de un planteo de aculturación de la empresas que quieren venderte sus bibliotecas a toda costa. Como la marca Explorer, y el la conjugación verbal "explore en internet", cuando sería "naveggue en internet" pero según planteás vos "hojee la internet" (browse the web)
Escrito por MAbeeTT (06/12/2008 02:16 AM)
No sé de qué país serán HPCarnace o MAbeeTT pero acá en Chile claramente son librerías y no bibliotecas el término que se usa.
Escrito por Jesus Concha (05/12/2008 08:23 PM)
Madre mía xD controlas todos los lenguajes de programación? ^_^ Eres una máquina, ánimo campeón!
Escrito por JoniJnm (26/04/2008 06:53 PM)
Jaja Ese comentario de MAbeeTT. Bueno debe ser que a Edwin se le pegó lo de las HP ya que veo que en los foros hablan más de "liberías" que bibliotecas, pero yo estoy más de acuerdo con MAbeeTT. Debe decirse bibliotecas, no liberías. En lo libros que yo estudiaba nunca hablaban de "Librerías de Enalace Dinámico", me refiero a los de C++ 5.02, sino de Bibliotecas de Enlace Dinámico. Pero muy bueno el aporte. Salu2.
Escrito por HPCarnace (31/03/2008 10:10 PM)
Eso NO SON librerías son bibliotecas... la traducción apropiada de _library_ es biblioteca. y _libería_ sería bookstore. Aunque tratandose de windogs....
Escrito por MAbeeTT (17/01/2008 02:25 PM)
No hay para qué ser tan purista, ambos términos son aceptados en español
Escrito por Erwin Ried (17/01/2008 03:41 PM)
Wow!, Microsoft si que tiene trabajo! Bien hecho!
Escrito por Z (16/01/2008 09:13 PM)

Copyright © 2013 por Erwin Ried.