PRÁCTICAS DE SISTEMAS OPERATIVOS II

NOVENA SESIÓN


  1. Bibliotecas de enlace dinámico. Construcción.

    Para concluir esta parte dedicada a Windows vamos a ver una parte muy importante de él: las bibliotecas de enlace dinámico, o DLLs. En teoría vimos en qué consiste el enlazado dinámico. En Windows se usa profusamente. De hecho, las funciones de las diferentes APIs se encuentran en bibliotecas DLL. En resumen, se trata de que el código de ciertas funciones venga en unos ficheros especiales llamados bibliotecas. Cuando un programa necesite dicho código, lo cargará de la biblioteca. Así se logra que el código de funciones muy generales no esté innecesariamente repetido en todos los ejecutables. También, con las bibliotecas de enlace dinámico resulta más fácil actualizar a la vez muchos programas.

    Para construir una biblioteca de enlace dinámico, nos podemos ayudar de las facilidades que nos ofrece el compilador Visual C++. Para ello, abriremos un nuevo proyecto de tipo "Win32 Dynamic-Link Library":


    Cuando se nos pida escoger, tomaremos "A DLL that exports some symbols":


    Observaremos que el entorno de desarrollo ha sustituido nuestra vieja conocida función main por:
    BOOL APIENTRY DllMain( HANDLE hModule, 
                           DWORD  ul_reason_for_call, 
                           LPVOID lpReserved
    					 )
    {
        switch (ul_reason_for_call)
    	{
    		case DLL_PROCESS_ATTACH:
    		case DLL_THREAD_ATTACH:
    		case DLL_THREAD_DETACH:
    		case DLL_PROCESS_DETACH:
    			break;
        }
        return TRUE;
    }           
    Cada vez que un proceso quiera usar la DLL que vamos a crear, se hará automáticamente una llamada a DllMain. La diferencia estará en el parámetro ul_reason_for_call. Cuando hagamos LoadLibrary, valdrá DLL_PROCESS_ATTACH. Cuando hagamos FreeLibrary, valdrá DLL_PROCESS_DETACH. Si se crea o muere un hilo entre ambos instantes, se llamará con los valores DLL_THREAD_ATTACH y DLL_THREAD_DETACH respectivamente. Esta llamada nos servirá para poder hacer tareas de reserva inicial o limpieza de la DLL.

    Haremos que la función devuelva TRUE si nos parece bien la petición o FALSE, si no.

    En el código fuente también vienen definidas una clase, una función y una variable que exportará la DLL. En cuanto a la clase, la podéis borrar directamente (tanto del .cpp como del .h) pues no vamos a usar C++. Respecto a la función y la variable, ahí podéis ver cómo se pueden definir en la DLL para que luego la usen otros procesos. Compilad la DLL y buscad, en el directorio "Debug" desde fuera del compilador el fichero que se ha generado. Pulsando sobre el fichero con el botón derecho del ratón sobre el menú "Vista rápida", se os ofrecerá mucha información acerca de la DLL. En particular, las funciones y variables que exporta. Esto fue lo que nos dio a nosotros:


    Si la función se llamaba en mi caso fnBibliot, ¿por qué dice que la función exportada se llama ?fnBibliot@@YAHXZ? La razón está en el enlazador de C++. Las funciones de C++ cuando se guardan en los módulos objeto cambian (o revisten) su nombre en parte debido a que en C++ dos funciones diferentes pueden tener el mismo nombre en el código fuente. Esto hace que usar la función así exportada sea un verdadero infierno. Para volver a sentir las piernas, es necesario incluir la opción de enlazado literal de C. Sustituid en el fichero de cabecera el prototipo de la función exportada por:
    extern "C" BIBLIOT_API int fnBibliot(void);
    El nombre de la funcióni cambiará en vuestro caso. Ahora ya podéis comprobar en la vista rápida de la DLL (una vez recompilada) que el nombre que aparece es el correcto. La macro BIBLIOT_API está definida como __declspec(dllexport) y lo podéis usar mejor. Si queréis exportar variables o funciones de la DLL, con estos ejemplos deberíais poder hacerlo.

  2. DLLs. Utilización por un programa.

    Llegó la hora de usar nuestra flamante DLL. Para poder usarla, hay dos posibilidades cargarla al inicio del programa o cargarla cuando se necesite. Nosotros veremos la segunda opción.

    Lo primero que tenemos que hacer es cargar la DLL en el espacio de memoria del proceso. Lo haremos con:
    HINSTANCE LoadLibrary( LPCTSTR lpLibFileName); 
    En lpLibFileName indicaremos el camino completo de la DLL. Se nos devolverá un manejador de la DLL o NULL, si la llamada fracasó. Además, la llamada hará que se invoque la función DllMain de la DLL con el parámetro ul_reason_for_call igualado a la macro DLL_PROCESS_ATTACH.

    Para poder usar las funciones hay que obtener un puntero a ellas. Lo hacemos con:
    FARPROC GetProcAddress( HMODULE hModule, LPCSTR lpProcName); 
    En hModule va el handle de la DLL y, como segundo parámetro, el nombre de la función.i En nuestro caso, hemos hecho:
        FARPROC funciOn;
    [...]
        funciOn=GetProcAddress(hbiblio,"fnBibliot");
        if (funciOn==NULL)
        {
            PERROR("GetProcAddress"); return 1;
        }
    
        printf("El valor de la función es %d.\n",funciOn()); 


    También hay que acordarse de liberar la DLL cuando se haya acabado de usar con FreeLibrary.

    Si queremos escribir en la pantalla dentro de una función de una DLL puede ser que tengamos que recurrir a escribir directamente con un manejador de la entrada, salida o error estándar:
    HANDLE GetStdHandle( DWORD nStdHandle);
          Puede ser el parámetro: STD_INPUT_HANDLE, STD_OUTPUT_HANDLE o 
                                  STD_ERROR_HANDLE.
    BOOL SetStdHandle( DWORD nStdHandle, HANDLE hHandle); 
    ¿Sois capaces de manejarlas sin nada más y escribir algo en la pantalla dentro de una DLL? Si es así, habéis aprovechado el curso.

  3. Práctica.

    La práctica de esta sesión es como sigue: Haced una DLL que exporte las funciones:
    VOID Poner_a_cero(VOID);
    VOID Incrementa(VOID); y 
    LONG Mostrar(VOID). 
    La DLL mantendrá un contador interno que será puesto a cero, incrementado o devuelto su valor con las funciones que exporta. Para probar la DLL, haced un programa de nombre carrera. Este programa creará cuatro hilos y pondrá sus identificadores por la pantalla. Los hilos se quedarán esperando por un objeto de tipo evento. El hilo principal pondrá el contador de la DLL a cero. A continuación, señalará el evento para que todos comiencen. Cada hilo incrementará sin pausas el contador de la DLL un millón de veces. Mientras, el hilo primero esperará a que vayan acabando los hilos corredores e imprimirá sus identificadores según vayan llegando. Cuando hayan llegado todos, imprimirá el valor del contador de la DLL y acabará con limpieza.

  4. Para saber más

    ¿Habéis aprendido a valeros por vosotros mismos con esta asignatura? ¿No importa lo que os pidan porque aunque no lo sepáis sabréis cómo buscarlo? Esta puede ser la prueba de fuego. Construid un ejecutable que mediante llamadas a la API de Windows logre apagar acabar con la sesión actual de Windows y apagar el ordenador automáticamente. En los ordenadores más modernos el sistema realmente se apagará. En los más antiguos, se pedirá al usuario que lo realice por sí mismo. El programa en sí no debería ocupar más de 30 líneas y es una aplicación de consola.

    Con la llegada de las últimas versiones de Windows, hacer un programa como este se complica aún más. Se debe lidiar con los nuevos mecanismos de seguridad que impiden a los procesos comunes realizar según qué acciones.

  5. Aplicaciones relacionadas.



  6. Funciones de biblioteca relacionadas.



  7. LPEs.


© 2023 Guillermo González Talaván.