Win32

  • October 2019
  • PDF

This document was uploaded by user and they confirmed that they have the permission to share it. If you are author or own the copyright of this book, please report to us by using this DMCA report form. Report DMCA


Overview

Download & View Win32 as PDF for free.

More details

  • Words: 5,818
  • Pages: 17
El API win32 23 de febrero de 2004 ´Indice 1. Introducci´ on 2. Ejemplos de uso del API 2.1. CreateProcess . . . . . . . . . . . . . 2.2. Variables de entorno . . . . . . . . . 2.3. Pipes . . . . . . . . . . . . . . . . . . 2.4. Manejo estructurado de Excepciones 2.5. Manejo de ficheros . . . . . . . . . . 2.6. Directorios . . . . . . . . . . . . . . 2.7. Se˜ nales de consola . . . . . . . . . .

1.

1 . . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

2 2 5 6 8 10 13 15

Introducci´ on

Este documento describe la API de programaci´on de win32. Esta API es com´ un (con ciertas diferencias m´ınimas) en todos los sistemas operativos Windows de 32 bits (Windows 98/NT/2000/XP), e incluye un gran n´ umero de funciones, desde manejo de ficheros y procesos hasta funciones gr´aficas. En este documento no podemos tratar todas esas funciones, y nos centramos en las funciones m´as interesantes de programaci´on a nivel de sistema (manejo de ficheros y directorios, procesos, variables de entorno, tuber´ıas entre procesos, manejo de excepciones, etc.) Hay una serie de consideraciones generales acerca de la programaci´on con la API de win32 que conviene puntualizar: Todo programa Windows normalmente incluye el fichero de cabecera ((windows.h)). Los programas win32 usan su propio sistema de tipos para ser portable entre distintos sistemas Windows y tambi´en para permitir una mejor programaci´on de bajo nivel. As´ı por ejemplo: • Casi cualquier recurso del sistema representa a un objeto del n´ ucleo, y se maneja con el tipo gen´erico HANDLE. • El tipo DWORD especifica una variable que puede guardar una doble palabra de 16 bits 1 (32 bits), el tipo BOOL, tambi´en de 32 bits representa valores booleanos 2 , el tipo LPSTR un puntero a una cadena de caracteres, LPCSTR un puntero a una cadena de caracteres constante, etc. 1 Se

considera que una palabra es de 16 bits aunque los procesadores actuales utilizan palabras de al menos 32 bits por cuestiones de compatibilidad con versiones anteriores. 2 Se puede pensar que utilizar un valor de 32 bits para representar s´ olamente un bit es un desperdicio. Sin embargo, actualmente los ordenadores son capaces de manejar mucho m´ as r´ apido un valor de 32 o ´ 64 bits (gracias a la alineaci´ on de memoria) que una parte de 1 bit de un registro, con lo que se gana en velocidad. Adem´ as, la cantidad de memoria normalmente ya no representa el principal cuello de botella de los programas.

1

• Normalmente se utiliza la llamada notaci´ on polaca inversa para nombrar las variables. As´ı por ejemplo, un HANDLE para un fichero se nombrar´a como hFichero, un puntero a una cadena ASCIIZ, como lpszNombre (long pointer to String-z), etc. Aunque win32 tambi´en permite cierto tipo de se˜ nales (muy sencillas y orientadas a la consola, como se ver´a), Windows introduce el concepto de excepci´ on. Las excepciones permiten un tratamiento estructurado de los errores en puntos comunes que adem´as permiten liberar los recursos temporalmente utilizados que ya no son v´alidos debido al error. Veremos ejemplos de este tipo de tratamiento de errores en los programas de la siguiente secci´on. Para el manejo de memoria seguiremos utilizando las funciones est´andar de C (malloc()/free()) en vez de las ofrecidas por la API win32. Esto se debe a que su uso ya es conocido de la pr´actica de Linux, adem´as de que estas funciones pertenecen al est´andar de C y por lo tanto cualquier compilador debe incluirlas. Windows permite trabajar con cadenas en formato UNICODE o en ASCII. En la primera opci´on se soportan la mayor´ıa de los lenguajes del mundo, incluyendo chino, hind´ u, japon´es, etc., y los caracteres ya no ocupan un s´olo byte, sino varios (2 o´ 4). La API win32 ofrece macros que permiten hacer programas que usan indistintamente ambas codificaciones. Sin embargo en nuestros ejemplos usaremos s´olo ASCII por ser m´as sencillo (cada car´acter ocupa un byte) y por continuidad con las pr´acticas anteriores. El Registro de Windows es una base de datos que relaciona cadenas de caracteres (claves) con valores (que pueden ser num´ericos, cadenas de caracteres, etc.). Es parecido a un conjunto de variables de entorno organizadas jer´arquicamente. Mientras que las variables de entorno son v´alidas para una sesi´on, el Registro es persistente. Los programas de Windows y el propio sistema operativo utilizan el Registro para almacenar datos estructurados de forma persistente. La API win32 ofrece funciones para acceder al registro similares al acceso a las variables de entorno que veremos despu´es. Debido a su tama˜ no y complejidad, el tratamiento de los valores contenidos en el Registro queda fuera de este documento.

2.

Ejemplos de uso del API

En esta secci´on se muestra el uso de la API win32 a trav´es de ejemplos de programas funcionales que realizan ciertas tareas sencillas. Estos programas est´an disponibles en la p´agina Web de la asignatura como un Workspace de Microsoft Visual C++. Cada programa ilustra un conjunto de funciones de la API para manejar procesos, variables de entorno, ficheros, directorios, etc.

2.1.

CreateProcess

En primer lugar veremos la creaci´on de procesos. A diferencia de UNIX, Windows no mantiene una estructura jer´arquica de procesos ni sigue el esquema fork()/exec(). Al contrario, un proceso puede crear directamente a otro, especificando si quiere que se hereden los ficheros abiertos, etc. La funci´on que nos permite crear procesos es CreateProcess, que se muestra a continuaci´on extra´ıda directamente de la definici´on de los archivos de cabecera de Windows: BOOL WINAPI CreateProcess( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles,

2

DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ); De los par´ametros de la funci´on, los siguientes son los m´as importantes (el resto normalmente ir´a a NULL o´ 0 dependiendo de su tipo): lpCommandLine Especifica la l´ınea de o´rdenes a ejecutar. Normalmente el par´ametro anterior, lpApplicationName, se deja a NULL. bInheritHandles Especifica si se deben heredar los ficheros abiertos. lpStartupInfo Puntero a una estructura STARTUPINFO: typedef struct _STARTUPINFO { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } STARTUPINFO, *LPSTARTUPINFO; Por ahora s´olo nos interesan de esta estructura los u ´ltimos tres campos, que especifican los HANDLEs para los ficheros de entrada est´andar, salida est´andar y de error. En el ejemplo se ver´a c´omo se puede hacer que un proceso herede los HANDLEs de ficheros que se necesiten para hacer una redirecci´on. lpProcessInformation Puntero a una estructura PROCESS INFORMATION: typedef struct _PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;

3

Esta estructura es muy interesante, porque una vez lanzado el proceso, el programa que lo cre´o puede obtener datos como: el HANDLE al proceso, el HANDLE al hilo3 , y los identificadores de ambos (equivalentes a los PIDs, Process IDs, en UNIX). Un proceso puede obtener su propio HANDLE llamando a la funci´on GetCurrentProcess y puede obtener su identificador llamando a GetCurrentProcessId. Finalmente, un proceso puede terminar usando la llamada ExitProcess, que acepta un entero con el valor del error (equivalente a la funci´on exit() de UNIX). El siguiente ejemplo muestra el uso de CreateProcess: Ejecuta el programa (junto con los par´ametros) que se le pasa por la l´ınea de o´rdenes. #include <stdio.h> #include <windows.h> int main(int argc, char*argv[ ]) { char* cmdline; STARTUPINFO si; PROCESS INFORMATION pi; int i, len;

5

10

/* Construir la l´ınea de comando a parir de los par´ ametros */ len = 0; cmdline = malloc(1); cmdline[0] = ’\0’; for (i=1 ; i < argc; i++) { len += 2 + strlen(argv[i]); cmdline = realloc(cmdline, len); strcat(cmdline, argv[i]); strcat(cmdline, " "); }

15

20

printf("cmdline: \" %s\"\n", cmdline); 25

ZeroMemory( &si, sizeof (si) ); si.cb = sizeof (si); ZeroMemory( &pi, sizeof (pi) ); /* Creamos el proceso hijo. */ if (!CreateProcess(NULL, /* No nombre de modulo (usar linea de ordenes). */ cmdline, /* Linea de ordenas a ejecutar. */ NULL, /* El manejador de proceso no se hereda. */ NULL, /* El manejador de hilo no se hereda. */ FALSE, /* Los manejadores no se heredan. */ 0, /* El proceso se crea sin ninguna opcion especial. */ NULL, /* Heredamos el bloque de entorno del padre. */ NULL, /* Usamos el directorio de inicio del padre. */ &si, /* Puntero a estructura STARTUPINFO. */ &pi)) /* Puntero a estructura PROCESS INFORMATION. */ { perror ("Imposible crear proceso"); return 1; }

30

35

40

45

printf("[Identificador de proceso: %d]\n",pi.dwProcessId); /* Si la orden se ejecuta en primer plano, debemos esperar a que termine.*/ WaitForSingleObject(pi.hProcess, INFINITE); 3 En

win32 cada proceso ejecuta al menos un hilo (thread).

4

50

CloseHandle(pi.hProcess); CloseHandle(pi.hThread); free(cmdline);

55

return 0; } /* $Id: CreateProcess.c,v 1.2 2004/02/11 19:52:40 dsevilla Exp $ */

La explicaci´on del programa es como sigue: L´ıneas 12–22: Construye una l´ınea de o´rdenes en cmdline a partir de las partes obtenidas en los distintos argv. L´ınea 31: Llama a CreateProcess con los par´ametros tal y como se han explicado m´as anteriormente. L´ınea 47: Se utiliza la variable dwProcessID de la estructura PROCESS INFORMATION para imprimir el identificador del proceso creado. L´ınea 50: La llamada WaitForSingleObject se utiliza para esperar a que el nuevo proceso termine. L´ınea 53–54: Se liberan los HANDLEs del proceso y del hilo.

2.2.

Variables de entorno

El mecanismo y uso de las variables de entorno es muy similar al presente en UNIX, salvo que ahora las funciones para manejarlo cambian. As´ı, la funci´on para obtener una variable de entorno es GetEnvironmentVariable: DWORD WINAPI GetEnvironmentVariable( LPCSTR lpName, LPSTR lpBuffer, DWORD nSize ); En lpName se especifica el nombre de la variable requerida. En lpBuffer se ofrece espacio para que la funci´on devuelva la cadena. En nSize se especifica el tama˜ no de memoria reservado en lpBuffer; esto se hace para que la funci´on sepa si tiene espacio suficiente en el buffer para meter el valor de la variable. Si no es as´ı, devuelve el n´ umero de bytes necesarios para albergar el valor de la variable. Por su parte, la funci´on SetEnvironmentVariable sirve para establecer el valor de una variable de entorno: BOOL WINAPI SetEnvironmentVariable( LPCSTR lpName, LPCSTR lpValue ); Los par´ametros son claros, salvo que ahora ambos son punteros a cadenas constantes. Ejecutando ((set)) en un ((S´ımbolo del Sistema)) de Windows se pueden ver ciertas variables de entorno que son interesantes para la realizaci´on de la pr´actica. Algunas de las m´as importantes:

5

60

PATH Equivalente a la que se encuentra en UNIX, salvo que en este caso se separa cada uno de los directorios con ((;)). USERNAME Guarda el nombre de usuario cuya sesi´on se est´a ejecutando. HOMEPATH Directorio ra´ız de los datos del usuario actual. USERPROFILE Guarda el directorio base del perfil de usuario. Este directorio se usar´a de base para almacenar directorios como ((Mis Documentos)), etc. Suele coincidir con HOMEPATH.

2.3.

Pipes

win32 tambi´en permite establecer tuber´ıas (pipes) entre procesos. El mecanismo es muy parecido al de UNIX, con la salvedad de las diferencias en la creaci´on de procesos que se vieron anteriormente. La creaci´on de tuber´ıas entre procesos es posible gracias al mecanismo introducido antes de ((herencia de handles)). A continuaci´on se muestra un programa que ejecuta dos o´rdenes conectadas por una tuber´ıa: ((cmd /c dir \ /S)) y ((more)). Como en Windows la orden dir es una orden interna, se debe llamar expl´ıcitamente al int´erprete de o´rdenes (cmd.exe) para ejecutar la orden a trav´es del par´ametro /c. La orden lista todos los directorios a partir del directorio ra´ız del disco actual. #include <stdio.h> #include <windows.h> int main(int argc, char*argv[ ]) { /* Ambas o ´rdenes: “dir \ /S | more” */ char* cmd1 = "cmd /c dir \\ /S"; char* cmd2 = "more.com";

5

10

HANDLE hReadPipe, hWritePipe; SECURITY ATTRIBUTES PipeSA = /* Para los handlers que se heredan */ { sizeof (SECURITY ATTRIBUTES), NULL, TRUE }; STARTUPINFO si1, si2; PROCESS INFORMATION pi1, pi2;

15

GetStartupInfo( &si1 ); GetStartupInfo( &si2 ); ZeroMemory( &pi1, sizeof (pi1) ); ZeroMemory( &pi2, sizeof (pi2) );

20

/* Creaci´ on de la Pipe */ CreatePipe( &hReadPipe, &hWritePipe, &PipeSA, 0); /* Establecer los valores para el primer proceso */ si1.hStdInput = GetStdHandle(STD INPUT HANDLE); si1.hStdError = GetStdHandle(STD ERROR HANDLE); si1.hStdOutput = hWritePipe; si1.dwFlags = STARTF USESTDHANDLES;

25

30

/* Creamos el proceso hijo. */ if (!CreateProcess(NULL, /* No nombre de modulo (usar linea de ordenes). */ cmd1, /* Linea de ordenas a ejecutar. */ NULL, /* El manejador de proceso no se hereda. */ NULL, /* El manejador de hilo no se hereda. */ TRUE, /* Los manejadores S´ I se heredan. */ 0, /* El proceso se crea sin ninguna opcion especial. */ NULL, /* Heredamos el bloque de entorno del padre. */ NULL, /* Usamos el directorio de inicio del padre. */ &si1, /* Puntero a estructura STARTUPINFO. */ &pi1)) /* Puntero a estructura PROCESS INFORMATION. */ {

6

35

40

perror ("Imposible crear primer proceso"); return 1; }

45

/* Ya no estamos interesados en la escritura de la tuber´ıa */ CloseHandle(hWritePipe); CloseHandle(pi1.hThread);

50

/* Establecer los valores para el primer proceso */ si2.hStdInput = hReadPipe; si2.hStdError = GetStdHandle(STD ERROR HANDLE); si2.hStdOutput = GetStdHandle(STD OUTPUT HANDLE); si2.dwFlags = STARTF USESTDHANDLES;

55

/* Creamos el proceso hijo. */ if (!CreateProcess(NULL, /* No nombre de modulo (usar linea de ordenes). */ cmd2, /* Linea de ordenas a ejecutar. */ NULL, /* El manejador de proceso no se hereda. */ NULL, /* El manejador de hilo no se hereda. */ TRUE, /* Los manejadores S´ I se heredan. */ 0, /* El proceso se crea sin ninguna opcion especial. */ NULL, /* Heredamos el bloque de entorno del padre. */ NULL, /* Usamos el directorio de inicio del padre. */ &si2, /* Puntero a estructura STARTUPINFO. */ &pi2)) /* Puntero a estructura PROCESS INFORMATION. */ { perror ("Imposible crear segundo proceso"); return 1; }

65

/* No estamos interesados en la lectura de la tuber´ıa */ CloseHandle(hReadPipe); CloseHandle(pi2.hThread);

75

/* Esperar a ambos procesos */ WaitForSingleObject(pi1.hProcess, INFINITE); CloseHandle(pi1.hProcess);

60

70

80

WaitForSingleObject(pi2.hProcess, INFINITE); CloseHandle(pi2.hProcess); return 0;

85

} /* $Id: pipe.c,v 1.4 2004/02/23 09:40:48 dsevilla Exp $ */

La explicaci´on del programa es como sigue: L´ınea 11: Se definen ambos HANDLEs para la lectura y la escritura de la tuber´ıa. L´ınea 12–13: Se rellena una estructura SECURITY ATTRIBUTES con los datos que se muestran. Esta estructura es necesaria para que los HANDLEs se puedan heredar entre procesos. L´ınea 23: Se crea la tuber´ıa. La funci´on tiene la siguiente definici´on: BOOL WINAPI CreatePipe( PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize

7

); El u ´ltimo par´ametro especifica el tama˜ no del buffer interno. Si su valor es 0, se utilizar´a el valor por defecto. La funci´on acepta punteros a ambos HANDLEs, que se establecer´an como la lectura y la escritura de la tuber´ıa, respectivamente, as´ı como un puntero a una estructura de seguridad SECURITY ATTRIBUTES: typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; En nuestro caso, el descriptor no se utiliza. La u ´ltima variable hInheritHandle est´a puesta a un valor TRUE (l´ınea 13). L´ıneas 26–29: La estructura STARTUP INFO del primer proceso se rellena estableciendo los HANDLEs espec´ıficos. As´ı, la salida est´andar se conecta con la escritura en la tuber´ıa. Una cosa importante es que para que realmente los ficheros se hereden, la variable dwFlags se debe poner a STARTF USESTDHANDLES. L´ıneas 49–50: Se cierra el HANDLE de escritura en la tuber´ıa y el del hilo. Ya no se van a necesitar m´as. L´ıneas 52–76: Se realiza el proceso an´alogo para el otro proceso. Esta vez se utiliza hReadPipe. L´ıneas 79–final: Se espera a ambos procesos con la llamada WaitForSingleObject. La ejecuci´on de este programa muestra el resultado esperado: un listado de todos los ficheros y directorios recursivamente, paginados a trav´es de la orden more.

2.4.

Manejo estructurado de Excepciones

La API win32 incluye macros y funciones que permiten trabajar tratando los errores como excepciones. Las excepciones son m´as convenientes que las meras comprobaciones de error porque b´asicamente permiten dos cosas: Tener localizados en un u ´nico punto todos los manejadores de errores de un c´odigo, pudiendo separar cada manejador para cada tipo de error, y la posibilidad de ejecutar un c´odigo de finalizaci´on independientemente de que se haya producido una excepci´on. Este c´odigo de finalizaci´on es el encargado de liberar la memoria y otros recursos que ya no se necesitan debido al error. As´ı, es una ayuda al programador, evitando que ´este olvide liberar recursos ante la aparici´on de un error. La sintaxis del manejo de excepciones es la siguiente: Un bloque precedido de try en el que se ejecuta cierto c´odigo que puede lanzar excepciones, y una parte de tratamiento de excepciones precedida por except: __try{ ... /* C´ odigo que puede generar una excepci´ on (fallo) */ } __except (...) { ... /* C´ odigo de tratamiento de la excepci´ on */ }

8

Alternativamente, en vez de except, el bloque try puede terminar con un ciones del finally siempre se ejecutar´an, se produzcan o no excepciones:

finally. Las instruc-

__try{ ... /* C´ odigo que puede generar una excepci´ on (fallo) */ } __finally { ... /* C´ odigo de finalizaci´ on (siempre se ejecuta) */ } El siguiente ejemplo muestra un uso sencillo del mecanismo estructurado de excepciones. Los siguientes programas introducen t´ecnicas algo m´as sofisticadas que explicaremos despu´es. El programa utiliza una variable sin inicializar (p) para albergar el resultado de una copia de una cadena. Como es normal, este programa causar´a una excepci´on de violaci´on de segmento. #include <windows.h> #include <stdio.h> int main(int argc, char*argv[ ]) { char *p = NULL; char *q = "Hola";

5

try {

10

/* Realizar alg´ un procesamiento */ /* ¡Desastre! ¡p es nulo! */ strcpy(p,q);

15

} except (EXCEPTION EXECUTE HANDLER) { /* Obtener el c´ odigo de excepci´ on */ DWORD ex = GetExceptionCode();

20

if (ex == EXCEPTION ACCESS VIOLATION) { printf("Violaci´ on de segmento.\n"); }

25

} printf("Finalizando el programa.\n"); 30

return 0; } /* $Id: exception.c,v 1.3 2004/02/19 01:36:04 dsevilla Exp $ */

L´ınea 10: El bloque try comienza. L´ınea 15: El programa realiza un acceso no v´alido. Inmediatamente salta a la parte except. Existen varios mecanismos para filtrar excepciones, pero por ahora s´olo nos interesa el valor EXCEPTION EXECUTE HANDLER, que hace que se ejecute lo que hay dentro del bucle de except. L´ınea 21: Se utiliza la funci´on GetExceptionCode para averiguar de qu´e excepci´on se trata concretamente. Existen varias excepciones que se pueden tratar. Algunas de las m´as importantes son (aparte de la ya vista EXCEPTION_ACCESS_VIOLATION): • EXCEPTION_DATATYPE_MISALIGNMENT – Un acceso a memoria no alineado (en arquitecturas que fuerzan a que todos los accesos est´en alineados).

9

• EXCEPTION_FLT_DIVIDE_BY_ZERO – Error de divisi´on por cero (en punto flotante). • EXCEPTION_ILLEGAL_INSTRUCTION – Se ha ejecutado una instrucci´on inv´alida. • EXCEPTION_STACK_OVERFLOW – Desbordamiento de pila. • EXCEPTION_INVALID_HANDLE – Se us´o un HANDLE inv´alido en alguna operaci´on de ficheros. • etc. L´ınea 23: En el caso de una violaci´on de segmento, imprimir un mensaje. El programa finaliza normalmente siendo consciente de su error.

2.5.

Manejo de ficheros

El manejo de ficheros en win32 es muy similar al de UNIX, salvo el nombre de las funciones. La filosof´ıa es la misma. El siguiente ejemplo muestra una copia de ficheros. Adem´as, se introduce un mecanismo m´as sofisticado de tratamiento de errores como excepciones, que se explicar´a a continuaci´on: /* Copia argv[1] en argv[2]. El primero debe existir y el segundo no */ #include <windows.h> #include <stdio.h> /* Para printf */ /* Excepciones definidas por el usuario */ #define EXC MEM (1 << 0) #define EXC DISK (1 << 1)

5

/* Leer el fichero en bloques de 8K */ #define BLOQUE 8192

10

/* Funci´ on para lanzar una excepci´ on del usuario */ void ReportException(LPCSTR mensaje, DWORD dwCodigo) { /* Imprimir el error */ perror(mensaje); /* Lanzar la excepci´ on */ if (dwCodigo != 0) RaiseException ((0x0FFFFFFF & dwCodigo) | 0xE0000000, 0, 0, NULL);

15

20

} int main(int argc, char*argv[ ]) { HANDLE inF = INVALID HANDLE VALUE, outF = INVALID HANDLE VALUE; char* pBuffer = NULL; DWORD bytes; BOOL nuevo = FALSE; /* ¿El fichero destino exist´ıa? */

25

30

try { /* Tratamiento de excepciones */ try { /* Limpiar handlers y buffers */ /* Reservar memoria para el buffer */ pBuffer = (char*)malloc(BLOQUE); if (pBuffer == NULL) ReportException("No hay suficiente memoria", EXC MEM); /* Abrir el fichero de lectura */ inF = CreateFile(argv[1], olo lectura */, GENERIC READ /* S´ 0 /* No se comparte */, NULL /* No se especifica seguridad adicional */, olo si existe */, OPEN EXISTING /* Abrir s´ FILE ATTRIBUTE NORMAL, NULL /* No se utiliza ning´ un otro fichero de ejemplo */);

10

35

40

45

if (inF == INVALID HANDLE VALUE) ReportException("No se puede abrir el fichero de entrada", EXC DISK); 50

/* Abrir el fichero de escritura */ outF = CreateFile(argv[2], GENERIC WRITE /* S´ olo escritura */, 0 /* No se comparte */, NULL /* No se especifica seguridad adicional */, CREATE NEW /* Crear s´ olo si no existe */, FILE ATTRIBUTE NORMAL, NULL /* No se utiliza ning´ un otro fichero de ejemplo */); if (outF == INVALID HANDLE VALUE) ReportException("No se puede abrir el fichero de salida", EXC DISK);

55

60

/* El fichero se ha creado */ nuevo = TRUE; 65

/* Bucle de copia */ bytes = BLOQUE; do { DWORD bytesEscritos;

70

if (!ReadFile(inF, pBuffer, bytes, &bytes, NULL)) ReportException("Error de lectura", EXC DISK); if (!WriteFile(outF, pBuffer, bytes, &bytesEscritos, NULL) | | (bytesEscritos != bytes)) ReportException("Error de escritura", EXC DISK); } while (bytes == BLOQUE); }

}

finally { /* Eliminar memoria y handlers */ if (pBuffer != NULL) free(pBuffer); pBuffer = NULL; if (inF != INVALID HANDLE VALUE) CloseHandle(inF); inF = INVALID HANDLE VALUE; if (outF != INVALID HANDLE VALUE) CloseHandle(outF); outF = INVALID HANDLE VALUE;

} except (EXCEPTION EXECUTE HANDLER) { /* Borrar el fichero creado */ perror("Error procesando ficheros"); if (!nuevo) DeleteFile (argv[2]); }

75

80

85

90

95

printf("Programa finalizado.\n"); return 0;

100

} /* $Id: copyfile.c,v 1.6 2004/02/23 17:54:57 dsevilla Exp $ */

L´ıneas 5–6: Se definen las constantes EXC MEM y EXC DISK para identificar el tipo de error que se ha producido en nuestro programa. L´ıneas 13–21: Se declara la funci´on ReportException, que imprime un mensaje de error utilizando perror() y que adem´as, si el error es serio (dwCodigo != 0) lanza una excepci´on utilizando la llamada win32 RaiseException. Esta funci´on es u ´til para lanzar excepciones en el momento

11

que se produzcan errores inesperados en nuestra aplicaci´on, como veremos m´as adelante. El valor 0xE0000000 es usado para especificar que la excepci´on es realmente un error grave. Lineas 31 y 32: Comienzan dos bloques try; esta construcci´on se realiza para que el bloque try m´as interno pueda tener un bloque finally (l´ınea 80) que permita liberar los recursos utilizados (ya se produzca error o no), mientras que el try externo es el que trata las correspondientes excepciones generadas con except (l´ınea 90). L´ınea 37: Ejemplo de uso de la funci´on ReportException. L´ınea 40: Se abre el fichero de lectura. N´otese que aunque no se est´a creando ning´ un fichero, la funci´on que se debe utilizar es CreateFile, con los par´ametros de abrir para s´olo lectura: HANDLE WINAPI CreateFile( LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile ); Los argumentos son como siguen: lpFileName El nombre del fichero a abrir o crear. dwDesiredAccess Puede ser bien GENERIC READ, GENERIC WRITE o ambos unidos por un ((or)) (|). dwShareMode Por ahora siempre estar´a a 0. Especifica qu´e grado de compartici´on tiene ese fichero con el resto de aplicaciones que quieran abrirlo. Por ahora lo abrimos exclusivamente. lpSecurityAttributes Atributos de seguridad. Por ahora los dejamos a NULL. dwCreationDisposition Especifica si se quiere crear el fichero o si por el contrario se quiere abrir un fichero que ya existe. Los posibles valores son: CREATE NEW, CREATE ALWAYS, OPEN EXISTING, OPEN ALWAYS, TRUNCATE EXISTING. El hecho de especificar que se quiere abrir un fichero que ya exista hace que el sistema falle si no existe el fichero, como se ve en el c´odigo (l´ınea 46). dwFlagsAndAttributes Especifica los atributos del fichero. Existen varios, pero los m´as importantes son: FILE ATRIBUTE NORMAL (atributos normales del fichero) y FILE ATTRIBUTE READONLY (para crear archivos de s´olo lectura). Como se ve, la funci´on devuelve un HANDLE, que puede ser v´alido o no, indicando si se ha podido abrir o no el fichero. Para saberlo se comprueba con INVALID HANDLE VALUE. L´ınea 52: Se crea el archivo destino. N´otese los valores adecuados de los campos de la funci´on CreateFile. L´ıneas 67–78: Bucle de copia. Las funciones utilizadas son ReadFile y WriteFile, que son an´alogas a read() y write() de UNIX. La definici´on de ReadFile es como sigue: BOOL WINAPI ReadFile(

12

HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped ); Como se ve, acepta un HANDLE, un buffer y el n´ umero de bytes a leer. En lpNumberOfBytesRead devuelve el n´ umero de bytes le´ıdos. El u ´ltimo par´ametro se deja a NULL. La funci´on devuelve TRUE si todo ha ido bien, y FALSE en otro caso, lo cual servir´a para lanzar una excepci´on (l´ıneas 73 y 77). L´ıneas 80–89: Se utiliza el bloque finally para liberar todos los recursos utilizados por el sistema (ficheros, memoria, etc.). N´otese que antes no se han cerrado los HANDLEs, sino que siempre se cierran en el bloque finally. L´ıneas 90–96: Finalmente, el bloque except se utiliza para borrar el fichero destino (si existe) en el caso de que se haya producido alguna excepci´on. Como se puede ver, esta forma de tratar las excepciones en un u ´nico punto evita el dejar recursos sin liberar (memoria reservada, ficheros abiertos, etc.), que pueden finalmente hacer que el sistema se quede sin recursos. Por u ´ltimo, decir que en la API win32 hay una funci´on espec´ıfica para copiar ficheros (CopyFile y CopyFileEx), aunque aqu´ı se ha implementado con operaciones de bajo nivel para demostrar su uso.

2.6.

Directorios

A continuaci´on se muestra el manejo de los directorios desde el punto de vista de la API de win32. El esquema que sigue el listado de directorios es similar al utilizado en MS-DOS y en UNIX de ((buscar el primero y buscar el siguiente)). El siguiente programa lista todas las entradas del directorio que coinciden con el patr´on dado como primer par´ametro. Para cada una imprime si se trata de un fichero o un directorio accediendo a sus atributos: /* Lista el directorio dado en argv[1] (puede incluir comodines) */ #include <windows.h> #include <stdio.h> /* Para printf */ /* Excepciones definidas por el usuario */ #define EXC MEM (1 << 0) #define EXC DISK (1 << 1)

5

/* Funci´ on para lanzar una excepci´ on del usuario */ void ReportException(LPCSTR mensaje, DWORD dwCodigo) { /* Imprimir el error */ perror(mensaje); /* Lanzar la excepci´ on */ if (dwCodigo != 0) RaiseException ((0x0FFFFFFF & dwCodigo) | 0xE0000000, 0, 0, NULL);

10

15

} int main(int argc, char*argv[ ]) { HANDLE hDirList; WIN32 FIND DATA fdData;

20

25

try { /* Tratamiento de excepciones */ try { /* Limpiar handlers y buffers */

13

if (INVALID HANDLE VALUE != (hDirList = FindFirstFile(argv[1], &fdData))) { /* Hay al menos un fichero */ do { /* Imprimir el archivo le´ıdo */ printf(" %s", fdData.cFileName); if (fdData.dwFileAttributes & FILE ATTRIBUTE DIRECTORY) printf(" (dir)\n"); else printf(" (fichero)\n");

30

35

40

} while (FindNextFile( hDirList, &fdData)); if (GetLastError() != ERROR NO MORE FILES) ReportException("Error durante la b´ usqueda", EXC DISK);

45

} }

} { }

finally { /* Eliminar memoria y handlers */ if (hDirList != INVALID HANDLE VALUE) FindClose(hDirList); hDirList = INVALID HANDLE VALUE;

50

} except (EXCEPTION EXECUTE HANDLER) 55

printf("Programa finalizado.\n"); return 0;

60

} /* $Id: dir.c,v 1.2 2004/02/11 19:52:40 dsevilla Exp $ */

El programa utiliza el mismo esquema de tratamiento de errores con la funci´on ReportException. Lo m´as destacado del programa es lo siguiente: L´ınea 29: Se llama a la funci´on FindFirstFile. Esta funci´on acepta la especificaci´on de los ficheros a buscar (que puede incluir una ruta completa y comodines) y un puntero a una estructura WIN32 FIND DATA: typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; CHAR cFileName[ MAX_PATH ]; CHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA; El campo dwFileAttributes guarda los atributos del fichero. Los siguientes campos datos como la fecha y el tama˜ no4 . Finalmente, el campo cFileName guarda el nombre del fichero encontrado 4 Como

se ve, el tama˜ no est´ a dividido en dos partes de 32 bits. Esto es para permitir ficheros de m´ as de 4GB.

14

y cAlternateFileName un nombre compatible con MS-DOS (los t´ıpicos nombres encontrados en los listados del directorio de la forma ((FICHERO~1.EXT))). La funci´on devuelve un HANDLE para continuar la b´ usqueda. L´ınea 37: Se comprueba si el fichero es un directorio o un archivo normal a trav´es de los atributos del mismo. L´ınea 42: Se llama a la funci´on FindNextFile. Esta funci´on acepta el HANDLE devuelto por FindFirstFile y actualiza la estructura WIN32 FIND DATA. L´ınea 50: Finalmente, dentro del bloque finally se llama a FindClose para terminar la b´ usqueda de ficheros. N´otese que este paso siempre se realiza, independientemente de que se haya producido error o no, evitando que se desperdicien recursos (por ejemplo, la b´ usqueda que ha quedado a medio mantiene datos sobre cu´al fue el u ´ltimo fichero devuelto, etc.).

2.7.

Se˜ nales de consola

Por u ´ltimo, se ver´a el mecanismo de se˜ nales de consola. Estas se˜ nales permiten tratar por ejemplo la pulsaci´on de Ctrl+C por parte del usuario o tomar el control cuando el usuario pulsa el bot´on de ((cerrar ventana)). El ejemplo siguiente muestra un uso sencillo de un handler de que trata las se˜ nales que recibe el programa. #include <windows.h> #include <stdio.h> /* Funci´ on Handler */ static BOOL WINAPI TrataEvento(DWORD event);

5

/* Variable para indicar si el programa terminar´ a */ volatile static BOOL salida = FALSE; int main(int argc, char* argv[ ]) { /* Establecer la funci´ on handler */ if (!SetConsoleCtrlHandler(TrataEvento, TRUE)) { printf("No se puede establecer el handler.\n"); return 1; } /* Bucle de espera */ while (!salida) { printf("."); fflush(stdout); Sleep(1000); }

10

15

20

25

printf("Programa finalizado.\n"); return 0;

30

} BOOL WINAPI TrataEvento(DWORD event) { switch(event) { case CTRL C EVENT: printf("Recibido Ctrl-C, el programa terminar´ a en 4 segundos.\n");

15

35

break; 40

case CTRL CLOSE EVENT: printf("Recibido evento de cierre de ventana." " Terminando en 4 segundos.\n"); break; 45

default: printf("Recibido el evento n´ umero %d. Quedan 4 segundos\n", event); } /* Duerme cuatro segundos */ Sleep(4000);

50

/* Se saldr´ a del bucle principal */ salida = TRUE; 55

/* El handler ha procesado la se˜ nal */ return TRUE; }

El programa se puede ver l´ınea a l´ınea: L´ınea 5: Se define la funci´on TrataEvento. Esta funci´on ser´a la que se llame cuando se reciba una se˜ nal. La definici´on de la funci´on (salvo el nombre) es siempre la misma. L´ınea 8: La variable salida se define como booleana y volatile. Esta especificaci´on ayuda al compilador a tratar la variable como una entidad que ser´a modificada de forma as´ıncrona. Por ejemplo, se modificar´a dentro de la funci´on TrataEvento cuando se reciba una se˜ nal. 5 L´ınea 14: Se utiliza la funci´on SetConsoleCtrlHandler para a˜ nadir la funci´on que ser´a llamada al recibirse una se˜ nal. Tiene dos par´ametros: la funci´on y una variable booleana que especifica si a˜ nadir (TRUE) o eliminar (FALSE) el handler. Algunas consideraciones: • Se pueden a˜ nadir varios handlers, que ser´an llamados en orden inverso al que se a˜ nadieron a SetConsoleCtrlHandler. • Si el segundo par´ametro es FALSE, el handler se eliminar´a de la lista de handlers. • Con los par´ametros (((NULL, TRUE))), el programa ignorar´a a partir de ese momento la pulsaci´on Ctrl+C. L´ıneas 21–26: Se inicia el bucle principal del programa. Simplemente imprime un punto y espera un segundo usando la funci´on Sleep. L´ınea 35: Se ejecuta un switch para discernir el evento recibido. Hay relativamente pocas se˜ nales, y las m´as importantes son las siguientes: CTRL C EVENT El usuario ha pulsado Ctrl+C. CTRL CLOSE EVENT El usuario ha intentado cerrar la ventana pulsando el bot´on de cerrar (´o Alt+F4). CTRL LOGOFF EVENT El usuario est´a terminando la sesi´on. Estas se˜ nales permiten obtener el control del programa cuando el usuario quiera cerrar la aplicaci´on, por ejemplo para preguntar si se quieren guardar los datos, etc. L´ınea 54: Despu´es de dormir cuatro segundos, se establece a TRUE la variable salida, causando que el programa termine el bucle en la siguiente iteraci´on. 5 El

compilador no guardar´ a esa variable en un registro, ya que puede ser modificada en el curso del bucle por otra funci´ on cuando se recibe una se˜ nal. As´ı, la variable debe estar en memoria y se debe leer en cada bucle.

16

Como se ve en la ejecuci´on del programa, cuando se produce una se˜ nal se crea un nuevo hilo para tratarla. As´ı, el programa principal sigue funcionando (los puntos se imprimen cada segundo) mientras se est´a tratando el evento. $Revision: 1.15 $

17

Related Documents

Win32
October 2019 17
Win32
November 2019 13
Win32
June 2020 7
Ileri Win32
November 2019 11