Uso del API de Windows : Funciones para ficheros .INI (III)

En el artículo anterior vimos las funciones del API de Windows para leer ficheros .INI, ahora vamos a dar un repaso a la funciones de escritura y completaremos las funciones que manejan ficheros de configuración.

Escribir cadenas en ficheros .INI

La función de escritura de ficheros .INI más utilizada es, sin duda, la de escritura de cadenas de caracteres. Esta función se declara en Visual FoxPro así :

    DECLARE ;
      INTEGER WritePrivateProfileString ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cClave, ;
      STRING   cCadena, ;
      STRING   cNombreFichero

A la función le pasamos la sección, la clave, el valor y el nombre del fichero. La cadena del valor no tiene que pasarse por referencia, es decir, no pasamos un buffer, sino simplemente el valor como una cadena.

Si la función por algún motivo no ha podido escribir dentro del fichero devuelve un 0, si todo ha ido correctamente devuelve un 1.

Escribir enteros en ficheros .INI

No existe una función específica para la escritura del enteros dentro de los ficheros .INI, aunque exista una función para su lectura. Por lo tanto debemos convertir los valores numéricos en cadenas antes de escribir por medio de GetPrivateProfileString.

Es una buena idea construir una función para encapsular esta transformación. Esta función podría llamarse WrIniInt y sería similar a esta :

    PROCEDURE WrIniInt
    LPARAMETER cSeccion, cClave, ;
               nValor, cNombreFichero
    
    RETURN GetPrivateProfileString( ;
             cSeccion, ;
             cClave, ;
             LTRIM( STR( nValor ) ), ;
             cNombreFichero )

Es una función muy sencilla, pero puede ser de utilidad si tenemos que escribir muchos enteros en los ficheros de configuración.

De igual forma, si tenemos guardar muchos números con decimales, fechas o cualquier otro tipo de dato podemos crear nuestras propias funciones para hacer más sencilla y rápida la escritura y lectura de ficheros .INI.

Escribir secciones en ficheros .INI

Al igual que podemos leer toda una sección podemos escribirla. La única dificultad es que debemos pasar una cadena con un formato algo especial : debe contener todas las claves, cada una compuesta por del identificador, el signo igual y el valor, separadas entre si con un carácter nulo.

Para escribir la sección utilizaremos la función :

    DECLARE ;
      INTEGER WritePrivateProfileSection ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cCadena, ;
      STRING   cNombreFichero 

Al igual que en el caso de lectura de secciones, esta función es una clara candidata a ser recubierta por una función escrita por nosotros que, por ejemplo, reciba la sección, una matriz por referencia y el nombre del fichero, construya el buffer necesario y llame a la función del API de Windows. Podría ser algo similar a esta :

    PROCEDURE WrIniSec
    LPARAMETER cSeccion, aDatos, cNombreFichero
    LOCAL nTama, cCadena, nCont
    *** Obtener el tamaño del array ***
    nTama = ALEN( aDatos, 1 )
    *** Inicializar la cadena ***
    cCadena = ''
    *** Transformar en la cadena ***
    FOR nCont = 1 TO nTama
            cCadena = cCadena + ;
                      aDatos[nCont,1] + ;
                      '=' + ;
                      aDatos[nCont,2] + ;
                      CHR(0)
    NEXT
    *** Escribir y retornar ***
    RETURN WritePrivateProfileSection( ;
             cSeccion, ;
             cTmp, ;
             cNombreFichero )

Téngase en cuenta que la matriz debe pasarse por referencia, es decir con un @ antes del nombre, sería algo así :

    DIMENSION aInf[2,2]
    aInf[1,1] = "Clave 1"
    aInf[1,2] = "1000"
    aInf[2,1] = "Clave 2"
    aInf[2,2] = "2000"
    ? WrIniSec( "Escritura 1", ;
                @aInf, ;
                "Ejemplos.ini" )

Cada uno puede crear su propia función, admitiendo los parámetros que más le interese.

Crear un fichero, una sección o una clave

No existen funciones especiales para crear ficheros .INI. Si utilizamos una función de escritura sobre un fichero que no existe entonces la función lo crea automáticamente.

Así mismo si la sección o la clave que pasamos a la función de escritura no existe esta lo crea.

Debemos, por lo tanto, tener cuidado con los nombres que pasamos como parámetros en la funciones de escritura, pues un pequeño error creará un nuevo fichero, una nueva sección o una nueva clave.

Dejar una clave vacía

Si queremos eliminar el valor de una clave, pero no queremos borrar la clave como tal, debemos pasar una cadena nula a la función de escritura de cadenas. Un ejemplo puede ser :

    =GetPrivateProfileString( ;
      "Sección", ;
      "Clave", ;
      "", ;
      "ejemplo.ini" )

En este caso dejaremos una línea con clave, un igual y sin ningún valor.

    [Sección]
    Clave=

Borrar de un fichero .INI

Si lo que queremos hacer es borrar completamente la clave debemos pasar a la función de escritura de cadenas un cero en el parámetro del valor :

    =GetPrivateProfileString( ;
      cSeccion, ;
      cClave, ;
      0, ;
      cNombreFichero )

Para eliminar toda una sección debemos utilizar la función de escritura de secciones pasando como parámetro de los valores un cero :

    =WritePrivateProfileSection( ;
      cSeccion, ;
      0, ;
      cNombreFichero )

Con este método se borra tanto el contenido de la sección, es decir, todas sus claves, como el identificador de las sección dentro del fichero de configuración.

Borrar el fichero

Aunque borremos todas las secciones no borramos el fichero .INI. Por lo tanto, si queremos eliminar el fichero debemos hacer uso de las funciones de borrar de ficheros de Visual FoxPro, pues en este caso no existe una función especial del API de Windows para borrar este tipo de ficheros.

Antes de lanzarnos a borrar nuestro fichero de configuración debemos tener presente que el fichero de configuración podemos instalarlo en el directorio de Windows o en cualquier otro directorio que nosotros elijamos. Pero si decidimos situarlo en el directorio de instalación de Windows debemos saber que este directorio no tiene por que ser C:\WINDOWS (yo utilizo siempre C:\WIN).

Para saber donde esta instalado Windows existen varios métodos. Quizás el más sencillo es consultar con la función GETENV() de FoxPro una variable del sistema llamada WINDIR y que contiene este directorio.

Pero si queremos seguir haciendo uso del API de Windows podemos utilizar la siguiente declaración :

    DECLARE ;
      INTEGER GetWindowsDirectory ;
      IN WIN32API ;
      STRING  @cBuffer, ;
      INTEGER  nTama

Esta función espera un buffer como primer parámetro y el tamaño del mismo como segundo parámetro. El buffer es conveniente que sea al menos de 255 caracteres (por si alguien es muy retorcido en eso de instalar Windows).

Sea como fuere, una vez conocido el directorio donde está instalado Windows podemos utilizar la orden DELETE FILE de FoxPro para borrar nuestro fichero .INI (nunca borrar los ficheros de otras aplicaciones o del sistema, puede ser muy desagradable el resultado).

Funciones del API especiales para WIN.INI

El API de Windows ofrece una serie de funciones para acceder directamente al fichero WIN.INI, sin necesidad de indicar el nombre del fichero, tal y como hacíamos hasta ahora. Estas funciones se comportan exactamente igual que las que hemos visto hasta ahora, pero sólo acceden al fichero WIN.INI. La declaración de estas funciones es :

    DECLARE ;
      INTEGER GetProfileInt ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cClave, ;
      INTEGER  nValorDefecto 
    DECLARE ;
      INTEGER GetProfileString ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cClave, ;
      STRING   cValorDefecto, ;
      STRING  @cCadenaRetorno, ;
      INTEGER  nTama
    DECLARE ;
      INTEGER GetProfileSection ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING  @cCadenaRetorno, ;
      INTEGER  nTama 
    DECLARE ;
      INTEGER WriteProfileString ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cClave, ;
      STRING   cCadena
    DECLARE ;
      INTEGER WriteProfileSection ;
      IN WIN32API ;
      STRING   cSeccion, ;
      STRING   cCadena 

No existen funciones específicas para SYSTEM.INI, necesitando acceder a este fichero como si fuera un "PrivateProfile". También es posible acceder al fichero WIN.INI por medio de las funciones "PrivateProfile" y no con las funciones específicas.

Ejemplo : Guardar la posición de una ventana

Después de dar un repaso a las funciones del API de Windows para manejar los ficheros .INI vamos a describir un ejemplo completo, donde se van a almacenar algunos datos sobre la última posición de una ventana.

Es una buena costumbre que las aplicaciones abran algunos tipos de ventanas en la última posición donde el usuario las dejó. A todos nos ha pasado que después de configurar a nuestro gusto la situación de las ventanas de una aplicación, al volver a arrancarla las ventanas están en su posición inicial y no donde las dejamos nosotros, debiendo volver a situarlas.

Para evitar este feo efecto podemos incluir unas pocas líneas dentro de nuestras pantallas a fin de obtener los datos de la posición de la ventana cuando se cierra y las guardarlos en un fichero .INI. Cuando la ventana se vuelve a crear la podemos situar en la última posición conocida.

Un ejemplo de esta técnica es el siguiente programa :

    *** Cargar la Form y mostrarla ***
    PUBLIC oEjemplo2
    oEjemplo2 = CREATEOBJECT( "Ejemplo2" )
    oEjemplo2.Show()
    RETURN
    *** Form del ejemplo en forma de clase ***
    DEFINE CLASS Ejemplo2 AS form
      BackColor = RGB(192,192,192)
      Caption = "Ejemplo 2 sobre INIs"
      *** Botón para cerrar ***
      ADD OBJECT cmdAceptar ;
        AS COMMANDBUTTON WITH ;
        Top = 6, ;
        Left = 6, ;
        Height = 29, ;
        Width = 94, ;
        Caption = "Aceptar"
      *** Evento al crear la form ***
      PROCEDURE Load
        *** Cargar las declaraciones ***
        DO profiles
        *** Obtener los valores con ***
        *** los que se cerró la última vez ***
        WITH This
          .Top    = GetPrivateProfileInt( ;
                      "Ejemplo 2", ;
                      "Top", ;
                      .Top, ;
                      "ejemplos.ini" )
          .Left   = GetPrivateProfileInt( ;
                      "Ejemplo 2", ;
                      "Left", ;
                      .Left, ;
                      "ejemplos.ini" )
          .Width  = GetPrivateProfileInt( ;
                      "Ejemplo 2", ;
                      "Width", ;
                      .Width, ;
                      "ejemplos.ini" )
          .Height = GetPrivateProfileInt( ;
                      "Ejemplo 2", ;
                      "Height", ;
                      .Height, ;
                      "ejemplos.ini" )
        ENDWITH
      ENDPROC
      *** Evento al destruir la form ***
      PROCEDURE Unload
        *** Cargar las declaraciones ***
        *** pueden haberse descargado ***
        DO profiles

        *** Si está minimizado o maximizado ***
        *** restaurar al tamaño normal ***
        This.WindowState = 0

        *** Guardar los valores de ***
        *** posición y tamaño ***
        WITH This
          = WritePrivateProfileString( ;
              "Ejemplo 2", ;
              "Top", ;
              LTRIM( STR( .Top ) ), ;
              "ejemplos.ini" )
          = WritePrivateProfileString( ;
              "Ejemplo 2", ;
              "Left", ;
              LTRIM( STR( .Left ) ), ;
              "ejemplos.ini" )
          = WritePrivateProfileString( ;
              "Ejemplo 2", ;
              "Width", ;
              LTRIM( STR( .Width ), );
              "ejemplos.ini" )
          = WritePrivateProfileString( ;
              "Ejemplo 2", ;
              "Height", ;
              RTRIM( STR( .Height ) ), ;
              "ejemplos.ini" )
        ENDWITH
      ENDPROC
      *** Método para cerrar la form ***
      PROCEDURE cmdAceptar.Click
        ThisForm.Release()
      ENDPROC
    ENDDEFINE

En el disco de la revista se puede encontrar este ejemplo en formato .SCX además de como código.

Debemos fijarnos que los valores por defecto de la función GetPrivateProfileInt para cuando no se encuentra el fichero, la sección o la clave son la posición actual de la ventana, de esta manera nunca se da un error.

El fichero EJEMPLOS.INI que utilizamos en este programa no existe la primera vez que ejecutamos la form, pero al cerrar, la función WritePrivateProfileString crea el fichero, la sección y las claves al no encontrarlas. En nuestra aplicación podemos incluir el fichero .INI dentro de la instalación.

[Nota : si quiere mantener limpio su directorio Windows, borré el fichero EJEMPLOS.INI después de las pruebas.]

Dos comentarios

Primero. Los nombres de las funciones del API de Windows suelen ser bastante extensos, por ejemplo, WritePrivateProfileSection tiene 26 letras. Si uno no quiere utilizar estos nombres tan largos puede hacer uso de la cláusula ALIAS de la orden DECLARE. De esta forma podemos declarar esta función con otro nombre y llamarla de forma más sencilla :

    DECLARE ;
      INTEGER WritePrivateProfileSection ;
      IN WIN32API ;
      ALIAS WPPS ;
      STRING   cSeccion, ;
      STRING   cCadena, ;
      STRING   cNombreFichero 
    ? WPPS( "Sección", Buffer, "ejemplos.ini" )

Yo personalmente me he acostumbrado a los nombres del API de Windows, pero es cuestión de preferencias.

Segundo. Es muy recomendable encapsular todas las funciones del API de Windows en funciones de mayor nivel. En el caso concreto de las funciones que manejan ficheros .INI es todavía más importante, pues como dijimos, estos ficheros serán sustituidos paulatinamente por el Registro de Configuraciones.

Si nosotros construimos un grupo de funciones de alto nivel para guardar nuestras configuraciones, las aplicaciones no tienen por que verse afectadas por el cambio, sólo estas funciones deberán ser modificadas cambiando sus llamadas a las funciones que manejan .INIs por las que manejan el Registro de Configuraciones o cualquier otro sistema.

Conclusión

Espero que esta aproximación a la utilización del API de Windows desde Visual FoxPro haya sido de interés. Esto no se queda aquí y en el próximo artículo estudiaremos otras útiles funciones.

Si no puede esperar y desea avanzar en el conocimiento del API de Windows puede ver el fichero de ayuda sobre el API de Win32 de la versión profesional en CD-ROM de Visual FoxPro o bien la magnífica información y ejemplos contenidos en los CD-ROM del Microsoft Developer Network (MSDN) o los manuales de Visual C++ 2.0 o 4.0.