PRÁCTICAS DE LABORATORIO DE SISTEMAS OPERATIVOS

DECIMOQUINTA SESIÓN


  1. Mensajes en Windows NT.

    El sistema de mensajes de Windows NT está muy asociado a las ventanas. El sistema está continuamente mandando mensajes a las ventanas de las aplicaciones: cuando se mueve el ratón, cuando pasan a primer plano, cuando se pulsa sobre ellas, cuando se selecciona un menú, etc. En la aplicación dueña de la ventana existe una función especial que se encargará de atender y procesar estos mensajes.

    Las aplicaciones que estamos realizando son aplicaciones de consola. Difieren de las aplicaciones normales de Windows en que no tienen una entrada/salida asociada a una ventana. Es por eso que no veremos los mensajes de Windows NT en su conjunto. Sólo veremos un par de funciones que permitirán mandar mensajes entre hilos que no posean ventanas.

    Los mensajes de Windows NT tienen un tipo de mensaje que identificará cuál es la razón del mensaje y dos argumentos, un argumento de 16 bits y otro de 32 bits. Podemos mandar un mensaje a un hilo con la función:
    BOOL PostThreadMessage( DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam);
    En los parámetros se especificará el identificador del hilo receptor del mensaje, el tipo de mensaje, el parámetro de 16 bits y el parámetro de 32 bits. En el tipo de mensaje, podemos usar el WM_USER y siguientes pues están reservados para aplicaciones de usuario. El envío que realiza esta función es un envío no bloqueante en el sentido visto en teoría. Existe otra función SendMessage que no veremos que realiza envíos bloqueantes (la función no retorna hasta que el destinatario haya recibido el mensaje).

    Para que un hilo pueda recibir un mensaje es necesario que el hilo tenga una cola de recepción de mensajes. Los hilos con ventanas a su cargo automáticamente la tienen. Como nuestros hilos pueden no tenerlas, es necesario que el hilo haya realizado antes una llamada:
    PeekMessage(&mensaje, NULL, WM_USER, WM_USER, PM_NOREMOVE);
    antes de poder recibir ningún mensaje.

    Esta última función sirve para poder espiar para ver si hay algún mensaje en la cola. Su prototipo es:
    BOOL PeekMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax,
                      UINT wRemoveMsg); 
    En el primer parámetro se depositará la información del mensaje espiado, el segundo lo dejaremos a NULL. En el tercero y el cuarto especificaremos qué rango de tipo de mensajes queremos comprobar. El último parámetro servirá para indicar si queremos que el mensaje desaparezca de la cola o no, después de espiado.

    Para conseguir el mensaje, se usa la función:
    BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
    igual que PeekMessage con la diferencia de que el mensaje siempre desaparece y de que la función se queda bloqueada si no hay mensajes de los tipos solicitados en la cola. Podemos usar WaitMessage para ver si hay mensajes de cualquier tipo en la cola.

    Los campos de la estructura MSG que devuelven estas funciones se pueden consultar en el resumen.

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

    Para concluir esta parte de Windows NT 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 NT 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 me dio a mí:


    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ón 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.

  3. 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. En mi caso, yo hice:
        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 ó 
                                  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.

  4. Práctica.

    La práctica de la última 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.

  5. 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 NT 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.

  6. Aplicaciones relacionadas.



  7. Funciones de biblioteca relacionadas.



  8. LPEs.


© 2000 Guillermo González Talaván.