Impedir que una aplicación se ejecute dos veces

Una direccion¿Cómo podemos evitar que una aplicación se ejecute más de una vez de forma simultánea?

En Windows existe un método sencillo: usar un mutex.

El nombre mutex es una abreviatura de un algoritmo de exclusión mutua. Es un dispositivo que se usa normalmente para evitar concurrencia, es decir para controlar el acceso de determinadas zonas de código que tienen acceso a recursos compartidos.

En nuestro caso, el recurso al que tenemos que limitar el acceso es el ordenador. Como se trata de un recurso que puede ser compartido, sólo limitaremos el acceso a nuestro programa, es decir, cuando nuestro programa esté accediendo al sistema operativo impedirá que otras instancias del mismo programa puedan acceder a él.

Un mutex es ideal para este propósito, ya que sólo puede estar en posesión de un proceso a la vez. Si un proceso tiene un manipulador de un mutex, no es posible que otro pueda obtenerlo.

De todos modos, para el propósito de este artículo no usaremos el mutex de ese modo, sencillamente intentaremos crear un mutex con un nombre determinado. Si el mutex no existía previamente, continuamos con la ejecución de la aplicación, si existía, abandonamos la ejecución.

Para obtener un manipulador para un mutex usaremos la función del API de Windows CreateMutex. Los dos primeros parámetros no nos importan demasiado. El primero es un descriptor de seguridad, que para este propósito nos basta con el descriptor por defecto. El segundo sirve para obtener la propiedad del mutex cuando es creado o no. Para nuestro caso es indiferente, ya que sólo verificaremos si el mutex existe o no. El tercero es el nombre del mutex. Usaremos un nombre único para nuestra aplicación.

Hay que tener presente que lo que impedirá que nuestra aplicación se ejecute es la presencia del mutex, de modo que este mecanismo no sólo impide que dos instancias del mismo programa se ejecuten a la vez, también impedirá la ejecución de cualquier otra aplicación que use el mismo nombre para el mutex.

// Prueba de mutex
// Mayo 2013 Con Clase
// Salvador Pozo
#include <windows.h>
#include <iostream>

using namespace std;

int main()
{
    bool EnEjecucion;

    //mutex =
    CreateMutex(NULL, FALSE, "PROGRAMA_EN_EJECUCION");
    EnEjecucion = (GetLastError() == ERROR_ALREADY_EXISTS || GetLastError() == ERROR_ACCESS_DENIED);

    if(EnEjecucion) {
        MessageBox(NULL, "El programa ya está en ejecución", "Probar mutex", MB_OK);
        return 0;
    }
    // Resto del programa aquí...
    cin.get();
    return 0;
}

Alternativamente a  usar mutex podemos hacer lo mismo con un fichero de disco. Si no existe, lo creamos al iniciar la aplicación y lo borramos al terminar. Si existe, cerramos la aplicación.

El problema de este método es que si la aplicación termina de forma imprevista el fichero no se borra, y no se podrá ejecutar la aplicación hasta que borremos el fichero de forma manual.

La idea es siempre crear un objeto global desde el punto de vista del sistema operativo, que sólo exista mientras la aplicación esté en ejecución. La ventaja del mutex es que se destruye automáticamente cuando la aplicación termina, independientemente del modo en que termine, aunque sea a causa de un error.

Colocar un icono en el área de notificación

IconoA veces puede ser interesante que nuestras aplicaciones coloquen un icono en el área de notificación.

Sin embargo, tampoco conviene abusar de esta técnica. El área de notificación se diseñó para proporcionar mensajes importantes al usuario, pero si se añaden demasiados iconos esta funcionalidad puede perder gran parte de su eficacia.

Las aplicaciones candidatas para colocar iconos en la zona de notificación son aquellas que no necesitan mantener un interfaz con el usuario, y que se ejecutan en segundo plano. El icono indica al usuario que el programa se está ejecutando. Cambiando el icono, la aplicación puede enviar mensajes cuando se requiera la atención del usuario, ya sea mediante un texto, o modificando la apariencia del icono. Además, sirve como punto para que el usuario acceda a un menú contextual, o para que maximice la ventana asociada.

La información que se encuentra por Internet sobre este tema es abundante, pero casi nadie explica cómo hacerlo usando sólo el API de Windows. Siempre he pensado que sería difícil de hacer, pero la verdad es que es muy fácil, así que paso a explicarlo.

Funciones, estructuras y mensajes del API

Lo he puesto en plural, pero lo cierto es que sólo nos interesa una estructura de datos, una función del API y tres mensajes.

La estructura es NOTIFYICONDATA, que actualmente tiene esta definición:

typedef struct _NOTIFYICONDATA {
  DWORD cbSize;
  HWND  hWnd;
  UINT  uID;
  UINT  uFlags;
  UINT  uCallbackMessage;
  HICON hIcon;
  TCHAR szTip[64];
  DWORD dwState;
  DWORD dwStateMask;
  TCHAR szInfo[256];
  union {
    UINT uTimeout;
    UINT uVersion;
  };
  TCHAR szInfoTitle[64];
  DWORD dwInfoFlags;
  GUID  guidItem;
  HICON hBalloonIcon;
} NOTIFYICONDATA, *PNOTIFYICONDATA;

En versiones anteriores a Windows 2000, los miembros desde dwState en adelante no existían, y se han ido añadiendo en sucesivas versiones del API. Para un uso básico no necesitaremos esos datos miembro.

Y la función es Shell_NotifyIcon, que tiene este prototipo:

WINSHELLAPI BOOL WINAPI Shell_NotifyIcon(
    DWORD dwMessage,    // message identifier
    PNOTIFYICONDATA pnid    // pointer to structure
);

Hay, además, tres mensajes:

NIM_ADD
NIM_DELETE
NIM_MODIFY

Y en las últimas versiones del API se han añadido dos mensajes más:

NIM_SETFOCUS
NIM_SETVERSION

De todos modos, no hablaré de ellos aquí, porque no los vamos a necesitar.

En una entrada futura hablaremos de otras posibilidades del área de notificación, como mensajes emergentes en forma de globo.

Añadir un icono

Usaremos la función Shell_NotifyIcon para enviar un mensaje NIM_ADD, que insertará un icono en el área de notificación.

Previamente rellenaremos una estructura NOTIFYICONDATA con los valores adecuados.

#define IDENTIFICADOR 3200
#define MENSAJE WM_USER+11
void MostrarIcono(HWND hwnd) {
    HICON hIcon = LoadIcon(GetModuleHandle(0), "Icono");
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
    nid.uCallbackMessage = MENSAJE;
    nid.hIcon = hIcon;
    strcpy(nid.szTip, "Estoy en el área de notificación!");
    Shell_NotifyIcon(NIM_ADD, &nid);
    DestroyIcon(hIcon);
}

El miembro cbSize debe contener el tamaño de la estructura. En general, las estructuras que tienen un miembro con este nombre es porque son diferentes en cada versión de Windows, o al menos está previsto que su estructura cambie. Este es el caso, ya que esta estructura ha crecido desde Windows 2000, XP y Vista.

El miembro hWnd debe contener un manipulador de la ventana de la aplicación asociada al icono.

El miembro uID contiene un identificador de la pequeña ventana dentro del área de notificación, asociada al icono. Se usa para enviar mensajes procedentes del icono relacionados con el ratón.

uFlags contiene una combinación de banderas que indica qué campos de la estructura contienen valores válidos. En este caso, NIF_ICON indica que hIcon contiene un manipulador de icono, NIF_MENSAJE indica que uCallbackMessage contiene un código de mensaje válido, y NIF_TIP que szTip contiene una cadena para un tooltip asociado al icono. Este texto se mostrará cuando el ratón se sitúe en el icono.

En uCallbackMessage almacenaremos un valor entero que será usado como identificador de mensaje. Este mensaje se enviará a la ventana asociada, que hemos indicado en el campo hWnd. En wParam se enviará el valor de uID y en lParam el mensaje detectado, por ejemplo WM_MOUSEMOVE o WM_LBUTTONDOWN.

Por si no lo has notado, la misma aplicación puede poner varios iconos diferentes en el área de notificación. Los mensajes procedentes de cada uno de ellos se distinguen por el valor de uID.

En hIcon colocaremos el manipulador del icono que queremos mostrar. Es importante destruir el manipulador después de llamar a la función Shell_NotifyIcon, ya que de no hacerlo se pueden producir fugas de memoria.

Por último, en szTip copiaremos la cadena a usar como texto en el tooltip asociado al icono. Esta cadena está limitada a 64 caracteres.

Modificar el icono

Podemos cambiar el icono cuando queramos.

Por ejemplo, supongamos que el icono que se muestra cuando la aplicación se está ejecutando correctamente, y no necesita atención por parte del usuario es Icono1. En un momento determinado se produce una situación que queremos notificar. En ese caso, procedemos a cambiar el icono por Icono2. Podemos modificar también el texto del tooltip, para dar una pista sobre la nueva situación.

void CambiarIcono(HWND hwnd, HICON hIcon) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_ICON;
    nid.hIcon = hIcon;
    Shell_NotifyIcon(NIM_MODIFY, &nid);
    DestroyIcon(hIcon);
}

En este caso usaremos el mensaje NIM_MODIFY, y como sólo queremos cambiar el icono, uFlags contendrá el valor NIF_ICON.

Por supuesto, en hIcon colocaremos el manipulador del nuevo icono a mostrar.

Modificar el texto

De un modo similar, podemos modificar el texto del tooltip.

void CambiarTexto(HWND hwnd, char *cad) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(nid);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;
    nid.uFlags = NIF_TIP;
    strcpy(nid.szTip, cad);
    Shell_NotifyIcon(NIM_MODIFY, &nid);
}

Ahora uFlags debe valer NIF_TIP, y deberemos modificar el valor de szTip.

Quitar icono de área de notificación

Para eliminar un icono usaremos el mensaje NIM_DELETE

void OcultarIcono(HWND hwnd) {
    NOTIFYICONDATA nid;

    nid.cbSize = sizeof(NOTIFYICONDATA);
    nid.hWnd = hwnd;
    nid.uID = IDENTIFICADOR;

    Shell_NotifyIcon(NIM_DELETE, &nid);
}

No necesitamos especificar uFlags, ni ninguno de los valores opcionales.

Responder a mensajes desde el área de notificación

Si hemos indicado el valor NIF_MESSAGE al añadir el icono, el procedimiento de ventana asociado recibirá un mensaje MENSAJE. En wParam recibiremos el identificador asociado al icono, y en lParam el mensaje original.

Para procesar estos mensajes sólo hay que añadir el caso adecuado para MENSAJE.

switch (message) { /* handle the messages */
...
    case MENSAJE:
        if(wParam == IDENTIFICADOR) {
            switch(lParam) {
                 case WM_LBUTTONUP:
                     OcultarIcono(hwnd);
                     ShowWindow (hwnd, SW_RESTORE);
                     break;
            }
        }
        break;
...

Este ejemplo restaurará la ventana asociada al icono cuando se detecte el que el botón izquierdo del ratón fue soltado sobre el icono del área de notificación. Por supuesto, podemos procesar todos los mensajes que se reciban desde el icono, como WM_MOUSEMOVE, WM_LBUTTONDOWN, etc.

Ejemplo completo

Puedes descargar un ejemplo completo desde aquí.

Envío de email desde C++

CorreoDe nuevo, como necesidad de unos de los programas que tuve que escribir hace un tiempo, surgió el problema de enviar mensajes de correo electrónico desde un programa en C++.

De nuevo, me puse a buscar por Internet, con resultados decepcionantes, en la mayoría de los casos.

La opción más simple es usar MAPI (Messaging Application Programming Interface), pero esto tiene como consecuencia directa que se usa la aplicación de correo instalada en Windows como pasarela. Esto provoca algunos inconvenientes, al menos para la aplicación que estaba desarrollando.

  • En mi caso, la aplicación de correo era Outlook. Para evitar el envío de correos de forma indiscriminada, lo cual está bien, Outlook muestra un cuadro de diálogo cada vez que se intente enviar un mensaje desde fuera de él. Esto era un inconveniente en mi caso, ya que la aplicación debía funcionar automáticamente, de modo desatendido, es decir, sin la presencia de un usuario.
  • Los mensajes enviados de este modo permanecen en la carpeta de mensajes enviados. Esto es un problema menor, o incluso, ni siquiera es un problema.
  • Los mensajes se envían usando el servidor de correo por defecto para la cuenta de Outlook.

En mi caso concreto, el mayor inconveniente era el primero, pero no hay que descartar los otros problemas y otros que se me hayan podido pasar por alto.

Lo que buscaba era una forma de enviar correos independiente de las aplicaciones instaladas en el PC, y que funcionase incluso sin una aplicación de correo instalada.

La solución que más me gustó fue usar la librería Blat, (BLAT.DLL). Como siempre, por simplicidad, ya que requiere poca instalación (sólo una DLL), y es fácil de utilizar, al menos relativamente fácil.

Tengo que aclarar que la aplicación de la que hablo no se dedica a hacer spam, aunque pueda parecer lo contrario. Es más, espero que nadie use esta guía con esa finalidad. En la página de Blat advierten sobre el mismo tema. Por supuesto, no me hago responsable del uso que cada uno haga de esta guía, pero si la usas para actividades de legalidad dudosa, al menos no me menciones como fuente.

Descargar Blat

Existe una página dedicada al proyecto Blat: www.blat.net desde la que se puede descargar la última versión, ya sea para 32 ó 64 bits. En el momento que escribo esta entrada, la última versión es la 3.11, y usaré la versión de 32 bits.

Para descargar los ficheros necesarios basta con pulsar la opción «Download», que nos lleva a la página del proyecto en SourceForge, y donde podremos elegir la versión que mejor nos parezca. Para los ejemplos usaremos la versión completa, y no el código fuente.

El fichero zip que descargaremos contiene dos carpetas: docs y full.

La primera contiene información sobre la librería: historial de versiones, créditos, términos de licencia y sintaxis.

La segunda es la que más nos interesa, y contiene los siguientes ficheros:

  • blat.exe: programa para línea de comandos que permite enviar mensajes de correo.
  • blat.dll: librería de enlace dinámico, para usar blat desde nuestros programas en C/C++.
  • blatdll.h: fichero de cabecera para incluir en nuestros proyectos.
  • blat.lib: librería estática para poder enlazar con la dinámica.

El primer inconveniente, si el compilador que usamos es Mingw/GCC, es que la librería estática no nos sirve. Puedes consultar la entrada de cómo convertir librerías LIB a formato A para evitar este problema.

Después de obtener el fichero «libblat.a», lo copiaremos a la carpeta «lib» de Mingw. También tenemos que hacer que el fichero blatdll.dll sea accesible, bien haciendo que esté en la misma carpeta que nuestro programa ejecutable, o bien copiándolo en una carpeta cuya ruta esté incluida en el PATH.

Una vez hecho esto, ya podremos usar la DLL en nuestro programa.

Sintaxis

Para nosotros, la única función de la librería que nos interesa es Send, que sólo tiene un parámetro, en forma de cadena de caracteres terminada en nulo.

Esta sintaxis es engañosamente simple, ya que la sintaxis de la cadena que usaremos como argumento es bastante complicada.

La cadena consta varios parámetros, cada uno de los cuales está formado por dos cadenas. La primera, que siempre empieza con un ‘-‘ indica el nombre del parámetro, y la segunda el valor.

Hay muchos parámetros, pero no voy a exponerlos todos aquí, sólo aquellos imprescindibles para enviar mensajes con ficheros adjuntos.

Relativos al servidor de correo

Esta librería no funciona como un servidor de correo, sino que usa una cuenta existente en un servidor de correo electrónico en Internet. Por lo tanto, necesitamos tener acceso a una cuenta de correo, sus parámetros y contraseña. Puede ser un servidor SMPT o NNTP.

-server <addr>  : especifica el servidor SMTP a usar (opcionalmente, addr:port)
-serverSMTP <addr> : lo mismo que -server
-serverNNTP <addr> : especifica el servidor NNTP a usar (opcionalmente, addr:port)
-serverPOP3 <addr> : especifica el servidor POP3 a usar (opcionalmente, addr:port) cuando se requiera un acceso POP3 antes de enviar un email
-serverIMAP <addr> : especifica el servidor IMAP a usar (opcionalmente, addr:port) cuando se requiera un acceso IMAP antes de enviar un email
-f <sender>     : ignora la dirección de remite por defecto (debe ser conocida para el servidor)
-i <addr>       : una dirección de origen (‘From:’), no necesariamente conocida por el servidor
-port <port>    : puerto a usar por el servidor SMTP, por defecto para SMTP (25)
-portSMTP <port>: lo mismo que -port
-portNNTP <port>: puerto a usar por el servidor NNTP, por defecto para NNTP (119)
-portPOP3 <port>: puerto a usar por el servidor POP3, por defecto para POP3 (110)
-portIMAP <port>: puerto a usar por el servidor IMAP, por defecto para IMAP (110)
-u <username>   : nombre de usuario para AUTH LOGIN (usar con -pw) o para AUTH GSSAPI con -k
-pw <password>  : contraseña para AUTH LOGIN (usar con -u)
-pu <username>  : nombre de usuario para POP3 LOGIN (usar con -ppw)
-ppw <password> : contraseña para POP3 LOGIN (usar con -pu)
-iu <username>  : nombre de usuario para IMAP LOGIN (usar con -ppw)
-ipw <password> : contraseña para IMAP LOGIN (usar con -pu)

-k              : Use UNKNOWN mutual authentication and AUTH GSSAPI
-kc             : Use UNKNOWN client-only authentication and AUTH GSSAPI
-service <name> : Set GSSAPI service name (use with -k), default «smtp@server»
-level <lev>    : Set GSSAPI protection level to <lev>, which should be one of: None, Integrity, or Privacy (default GSSAPI level is Privacy)
-nomd5          : Do NOT use CRAM-MD5 authentication.  Use this in cases where the server’s CRAM-MD5 is broken, such as Network Solutions.

Direcciones de destino

-to <recipient> : recipient list (also -t) (comma separated)

-cc <recipient> : carbon copy recipient list (also -c) (comma separated)

-bcc <recipient>: blind carbon copy recipient list (also -b)

-ur             : set To: header to Undisclosed Recipients if not using the
-to and -cc options

-subject <subj> : subject line, surround with quotes to include spaces(also -s)
-ss             : suppress subject line if not defined

-body <text>    : message body, surround with quotes («) to include spaces

Ficheros adjuntos

-attach <file>  : attach binary file(s) to message (filenames comma separated)
-attacht <file> : attach text file(s) to message (filenames comma separated)
-attachi <file> : attach text file(s) as INLINE (filenames comma separated)
-embed <file>   : embed file(s) in HTML.  Object tag in HTML must specify
content-id using cid: tag.  eg: <img src=»cid:image.jpg»>
-af <file>      : file containing list of binary file(s) to attach (comma
separated)
-atf <file>     : file containing list of text file(s) to attach (comma
separated)
-aef <file>     : file containing list of embed file(s) to attach (comma
separated)
-base64         : send binary files using base64 (binary MIME)
-uuencode       : send binary files UUEncoded
-enriched       : send an enriched text message (Content-Type=text/enriched)
-unicode        : message body is in 16- or 32-bit Unicode format
-html           : send an HTML message (Content-Type=text/html)
-alttext <text> : plain text for use as alternate text
-alttextf <file>: plain text file for use as alternate text
-mime           : MIME Quoted-Printable Content-Transfer-Encoding
-8bitmime       : ask for 8bit data support when sending MIME
-multipart <size>
: send multipart messages, breaking attachments on <size>
KB boundaries, where <size> is per 1000 bytes
-nomps          : do not allow multipart messages
-contentType <string>
: use <string> in the ContentType header for attachments that
do not have a registered content type for the extension
For example: -contenttype «text/calendar»

Parámetros menos frecuentes

-organization <organization>
: Organization field (also -o and -org)
-ua             : include User-Agent header line instead of X-Mailer
-x <X-Header: detail>
: custom ‘X-‘ header.  eg: -x «X-INFO: Blat is Great!»
-noh            : prevent X-Mailer/User-Agent header from showing Blat homepage
-noh2           : prevent X-Mailer header entirely
-d              : request disposition notification
-r              : request return receipt
-charset <cs>   : user defined charset.  The default is iso-8859-1
-a1 <header>    : add custom header line at the end of the regular headers
-a2 <header>    : same as -a1, for a second custom header line
-dsn <nsfd>     : use Delivery Status Notifications (RFC 3461)
n = never, s = successful, f = failure, d = delayed
can be used together, however N takes precedence
-hdrencb        : use base64 for encoding headers, if necessary
-hdrencq        : use quoted-printable for encoding headers, if necessary
-priority <pr>  : set message priority 0 for low, 1 for high
-sensitivity <s>: set message sensitivity 0 for personal, 1 for private,
2 for company-confidential

Ejemplo C++

Veamos un ejemplo sencillo que envía un mensaje de texto con dos adjuntos:

#include <iostream>
#include <windows.h>
#include <cstdio>
#include "blatdll.h"

using namespace std;

int main()
{
    char cad[128];
    ULONG err;
    char nombre[2][256];
    char remiteCorreo[64];
    char servidorCorreo[64];
    char usuarioCorreo[64];
    char passwordCorreo[64];
    char asuntomsg[256];
    char textomsg[1024];
    char cmdline[4096];
    char szTo[64];
    char szCc[64];
    char szCo[64];

    strcpy(remiteCorreo, "<usuario@servidor>");
    strcpy(servidorCorreo, "<servidor.de.correo>");
    strcpy(usuarioCorreo, "<usuariodecorrreo>");
    strcpy(passwordCorreo, "<contraseña>");
    sprintf(nombre[0], ".\\main.cpp");  // Ruta de acceso del 1er fichero
    sprintf(nombre[1], ".\\blatdll.h"); // Ruta de acceso del 2º fichero

    sprintf(asuntomsg, "mensaje enviado usando Blat");
    sprintf(textomsg, "Cuerpo del mensaje.\nRealizando prueba.\n-- \nConClase\n");

    sprintf(cmdline, "-body \"%s\" -q -f %s -server %s -u %s -pw %s -attach %s,%s -base64 -subject \"%s\"",
            textomsg, remiteCorreo, servidorCorreo, usuarioCorreo, passwordCorreo, nombre[0], nombre[1], asuntomsg);
    cout << cmdline << endl;

    // Añadir direcciones To:
    strcpy(szTo, " -to <direccion_destino>");
    strcat(cmdline, szTo);

    strcpy(szCc, " -cc <direccion_copia>");
    strcat(cmdline, szCc);

    strcpy(szCo, " -bcc <direccion_copia_oculta>");
    strcat(cmdline, szCo);

    err = Send(cmdline);
    switch(err) {
         case 0: 
             sprintf(cad, "Mensaje enviado"); 
             break;
         case 1: 
             sprintf(cad, "Error de conexión o dirección de destino erronea."); 
             break;
         case 2: 
             sprintf(cad, "Conexión denegada o dirección de remite erronea."); 
             break;
         default: 
             sprintf(cad, "Error %ld", err); 
             break;
    }
    cout << cad << endl;
    if(err) return -1;

    return 0;
}

Bastará con sustituir los textos entre <> por lo adecuados en tu caso.

Clase PHP para MySQL

Base de datpsEn las últimas versiones de PHP, el soporte para programación orientada a objetos mejoró considerablemente. A día de hoy, que cualquier proyecto medianamente complejo desarrollado en PHP esté basado en POO es prácticamente una necesidad.

En nuestros proyectos, pequeños o grandes, usar clases es una gran ventaja. Sobre todo si las diseñamos con cierta perspectiva y pensando en su reutilización. Empezamos ahora una serie de artículos sobre el encapsulamiento de algunas herramientas que usaremos frecuentemente en proyectos PHP. La idea es mejorar nuestro rendimiento y el de nuestras páginas, reutilizando códigos tan optimizados como seamos capaces de escribir.

Aunque PHP ya tiene diseñadas clases para estas tareas (por ejemplo, mysqli), a veces es más cómodo usar las nuestras. Ya sé que esto está en contra de la reutilización, y que es «reinventar la rueda». Teniendo todo esto en cuenta, consideremos esto como un ejercicio. 🙂

Tanto más si tenemos en cuenta las recomendaciones que aparecen en la documentación de PHP:

Esta extensión está obsoleta a partir de PHP 5.5.0 y no está recomendada para escribir código nuevo, ya que será eliminada en el futuro. En su lugar, se debería utilizar la extensión mysqli o PDO_MySQL.

Al final veremos como usar la clase mysqli, en lugar de la nuestra.

Nuestras clases para bases de datos

Empecemos con un par de clases para acceder a bases de datos MySQL.

<?php
    /*  Clase mySQLDB para acceder a bases de datos mySQL.
        Clase queryDB para realizar consultas en una base de datos.
    */
    class mySQLDB {
        const servidor = 'host'; /* Sustituir por valor adecuado */
        const usuario  = 'user'; /* Sustituir por valor adecuado */
        const clave    = 'password'; /* Sustituir por valor adecuado */
        protected $IdConexion;
        protected $basedatos;

        function __construct() {
            $this->IdConexion = @mysql_connect(self::servidor, self::usuario, self::clave) 
                or die ('Imposible conectar con base de datos.');
        }

        function __destruct() {
            @mysql_close($this->IdConexion);
        }

        function SetBaseDatos($nombrebd) {
            $this->basedatos = $nombredb;
            @mysql_select_db( $nombredb, $this->IdConexion);
        }

        function GetConexion() {
            return $this->IdConexion;
        }

        function GetBaseDatos() {
            return $this->basedatos;
        }
    }

    class queryDB {
        protected $BD;
        protected $Id;

        function __construct( $Bd, $Query ) {
            $this->BD = $Bd;
            $this->Id = @mysql_query( $Query, $this->BD->GetConexion() );
        }

        function __destruct() {
            @mysql_free_result($this->Id);
        }

        function Cerrar() {
            @mysql_free_result($this->Id);
        }
        function NumRows() {
            return @mysql_num_rows($this->Id);
        }

        function FetchArray() {
            return @mysql_fetch_array($this->Id);
        }
    }
?>

En la definición de las clases anteriores quiero llamar la atención sobre algunas cosas.

  1. El carácter ‘@’ antes de las llamadas a las funciones ‘mysql_’ es para evitar que se muestren los mensajes de aviso que se producen cuando se usan funciones obsoletas. Antes he mencionado que la extensión «mysql» está considerada obsoleta a partir de PHP 5.5.
  2. En el constructor de la base de datos incluimos el código necesario para establecer la conexión, y en el destructor, cerramos esa conexión. Esto libera a nuestra aplicación de la tarea de abrir y cerrar conexiones a bases de datos, ya que estas se realizan de forma automática.
  3. SetBaseDatos asigna una base de datos por defecto a la conexión. Podremos cambiar esta base de datos cuando queramos, o ignorarla, incluyendo el nombre completo de la tabla en las consultas, basededatos.tabla.
  4. GetBaseDatos permite recuperar el nombre de la base de datos por defecto.
  5. GetConexion nos devuelve el identificador de la conexión asociada a la base de datos, aunque probablemente nunca necesitemos usarla.
  6. En este ejemplo he optado por crear clases separadas para bases de datos y consultas, aunque se podrían haber creado ambas usando una única clase.
  7. El constructor de la clase queryDB almacena una variable con la base de datos y un identificador a la consulta creada. Para ello se necesitan dos parámetros, que son la base de datos y la cadena con la consulta.
  8. El destructor libera los resultados asociados a la consulta.
  9. El método Cerrar nos permite liberar los resultados de la consulta cuando sea necesario. Por ejemplo, no será posible hacer una consulta en una base de datos hasta que la anterior haya sido cerrada. Si hacemos dos consultas en el mismo método será necesario cerrar la primera antes de hacer la segunda.
  10. NumRows recupera el número de filas que contiene el resultado de la consulta.
  11. FetchArray recupera una fila o tupla de la consulta.

Usar estas clases en una aplicación es muy sencillo. Por ejemplo:

    $database = new mysqlDB();
    $Consulta = new queryDB( $database, "SELECT * FROM agenda.personas ORDER BY nombre");
    print("<table>\n");
    print("<tr><th>Nombre</th><th>Fecha</th></tr>\n");
    for( $i = 0; $i < $Consulta->NumRows(); $i++) {
        $reg = $Consulta->FetchArray();
        print("<tr><td>".$reg["nombre"]."</td><td>".$reg["fecha"]."</td></tr>\n");
    }
    $Consulta->Cerrar();
    print("</table>\n");

Esta es una de muchas posibles implementaciones, simplificada a propósito, ya que no proporciona soporte para la mayor parte de las funciones de la extensión mysql. Sin embargo, resulta bastante útil, y es una buena muestra de las ventajas de encapsular que proporciona la programación orientada a objetos.

Usando la clase Mysqli

PHP dispone de una extensión para acceso a MySQL que en la documentación se denomina «extensión MySQL mejorada».

Consta no de dos clases, sino de seis, aunque tres de ellas son de utilidad limitada para la mayoría de las aplicaciones: mysqli_driver, mysqli_warning y mysqli_sql_exception.

Nos centraremos en las otras tres.

Para ver una documentación completa de estas clases recomiendo acceder a la página de PHP.

La clase mysqli

El constructor tiene cierto parecido al nuestro, los tres primeros parámetros son los mismos. Se puede añadir un cuarto, con el nombre de la base de datos por defecto, un quinto con el puerto de conexión y un sexto con el socket. Aunque los seis parámetros son opcionales, seguramente querremos usar los tres o cuatro primeros siempre. Por supuesto, este constructor crea o intenta crear la conexión con la base de datos.

El método query permite realizar consultas. El primer parámetro es la cadena con la consulta, el segundo es opcional, y permite seleccionar el modo en que se trata el resultado. Por defecto es MYSQL_STORE_RESULT, que indica que el resultado será almacenado. Si se usa MYSQL_USE_RESULT los resultados de la consulta no serán almacenados. Esto es poco aconsejable si se realizan muchas actualizaciones desde el lado del cliente, ya que las tablas consultadas se bloquean.

El método select_db permite seleccionar una base de datos por defecto.

Otro dato miembro interesante es $insert_id, que contiene el identificador de la última fila insertada en una columna autoincrementada.

La clase mysqli_result

El método query devuelve un objeto de la clase mysqli_result. En esta clase hay, entre otros, los siguientes datos y métodos:

El dato $num_rows contiene el número de filas de un resultado.

El método fetch_array obtiene una fila desde el conjunto de resultados.

El método free (o sus alias close y free_result) liberan la memoria asociada al resultado.

La clase mysqli_smtp

Esta clase se usa para realizar las consultas usando otro sistema, que requiere la preparación de la consulta, asociar columnas con variables, etc.

Será un tema para tratar en otra entrada del blog.

Ejemplo usando mysqli

    $database = new mysqli("localhost", "user", "password");
    $Resultado = $database->query( "SELECT * FROM agenda.personas ORDER BY nombre");
    print("<table>\n");
    print("<tr><th>Nombre</th><th>Fecha</th></tr>\n");
    for( $i = 0; $i < $Resultado->num_rows; $i++) {
        $reg = $Resultado->fetch_array();
        print("<tr><td>".$reg["nombre"]."</td><td>".$reg["fecha"]."</td></tr>\n");
    }
    $Resultado->free();
    print("</table>\n");                                    

Como se puede apreciar, los ejemplos son muy parecidos.

Crear librerías .a a partir de librerías .lib

LibrosCon cierta frecuencia encontramos bibliotecas (o librerías) de funciones que nos pueden resultar útiles en nuestros programas. En este blog hablaremos de algunas de ellas.

Casi siempre las encontraremos en formato de de bibliotecas de enlace dinámico (DLL), pero incluso en esos casos, para usarlas en nuestros programas C/C++ usaremos una biblioteca estática para acceder a ellas.

Un problema frecuente, cuando se trata de librerías de dominio público y cuando las queremos usar en Windows junto con MinGW/GCC es que la librería estática se distribuye con la extensión «.lib», y MingW/GCC usan el formato de Unix, con la extensión «.a».

No son formatos compatibles, y no basta con renombrar el fichero, ya lo intenté…

Afortunadamente, junto con el paquete de MinGW se distribuyen algunas herramientas que permiten hacer una conversión de «.lib» a «.a».

Crear una carpeta de trabajo

Con el fin de no contaminar carpetas de aplicaciones, o si no tenemos definido el PATH para la carpeta bin de MinGW, lo más sencillo es crear una carpeta de trabajo en la que haremos las conversiones que necesitemos. Puedes usar una carpeta temporal, o crear la que mejor te parezca.

En esa carpeta copiaremos los siguientes ficheros, que encontraremos en la carpeta MinGW/bin, cuya ruta completa dependerá de la instalación que hayas hecho del compilador.

  • reimp.exe
  • dlltool.exe
  • as.exe

El fichero «reimp.exe» no siempre viene con MinGW, de hecho, casi nunca viene. Forma parte de las utilidades de MinGW, y se encuentra en un paquete que se debe obtener por separado. Puedes buscar la última versión aquí.

Además, copiaremos los ficheros necesarios para hacer la conversión, y que generalmente estarán incluidos en el paquete de la biblioteca que hemos descargado:

  • <biblioteca>.lib
  • <biblioteca>.dll

Obtener el fichero .DEF

El primer paso consiste en obtener el fichero <biblioteca>.def. A veces el fichero forma parte del paquete, si es así, ¡enhorabuena!

Si no, no te preocupes, es fácil de obtener, basta con ejecutar esta orden desde una ventana de línea de comandos:

reimp -d <biblioteca>.lib

A veces hay que eliminar los caracteres ‘_’ que aparecen como prefijos en el fichero .def obtenido. Se puede hacer con un editor normal, ya que el fichero .def sólo contiene texto.

Una segunda alternativa, válida en caso de nos disponer del fichero .lib, es conseguir la utilidad «pexports«, que también forma parte de las utilidades de MinGW.

Para obtener el fichero .def, esta utilidad usa el fichero .dll, con una línea de comandos de este tipo:

pexports fichero.dll > fichero.def

En este caso podemos tener algunos problemas, que aunque fáciles de resolver, pueden darnos algunos dolores de cabeza.

Las funciones de una DLL pueden usar dos tipos de llamadas: stdcall y cdecl. Si se usa stdcall, el nombre de la función en el fichero .def debe tener un sufijo @’n’, donde n es un número entero.

Por desgracia, la utilidad pexports no es capaz de generar esos sufijos, de modo que tendremos que añadirlos manualmente, editando el fichero .def.

La cuestión es, cómo saber qué funciones usan stdcall, y cual es el valor de n en cada una de ellas.

Hasta donde he podido averiguar, la única forma es compilar el programa y ver qué mensajes de error nos da en la fase de enlazado.

Por ejemplo, si obtenemos un mensaje del tipo:

undefined reference to `<funcion>@4'

En este caso sabremos que la función <funcion> usa stdcall, y que debemos añadir el sufijo @4 en el fichero .def. Por supuesto, en ese caso, tendremos que volver a generar el fichero de librería estática .a.

Obtener el fichero .a

A partir del fichero .def y del fichero .dll podemos obtener el fichero de librería estática usando este comando:

dlltool -d <biblioteca>.def -D <biblioteca>.dll -k -l lib<biblioteca>.a -S ./as.exe

Es importante añadir el prefijo «lib» al fichero de biblioteca estática obtenido, ya que MinGW no lo encontrará si no tiene el formato lib<nombre>.a

Cada fichero en su lugar

Ahora sólo queda copiar cada fichero en su sitio.

El fichero con extensión «.a» debe copiarse a la carpeta «lib» de MinGW, de modo que el enlazador pueda localizarlo. Otra alternativa es copiarlo en otra ruta, pero en ese caso hay que añadir esa ruta a cada proyecto que use esta biblioteca.

El fichero con extensión «.h», que formará parte del paquete, debe copiarse a la carpeta «include» de MinGW, o a la carpeta del proyecto. Eso depende de tu propio criterio, y en última instancia, de con qué frecuencia uses estas bibliotecas.

El fichero «.dll» debe distribuirse junto a la aplicación. De nuevo, si se trata de una biblioteca que se usa con cierta frecuencia, puedes copiarla a una carpeta que esté en el PATH del sistema, o bien copiarla en la misma carpeta que el fichero ejecutable de la aplicación.

Cuando se cree un proyecto que use esta DLL hay que incluir el parámetro «-<biblioteca>» entre las opciones del enlazador.

Opciones

En este ejemplo se han añadido las bibliotecas «blat» y «sqlite». El enlazador buscará los ficheros de biblioteca estática «libblat.a» y «libsqlite.a».

Acceder a ficheros comprimidos desde C++

Comprimir y descomprimirHace unos meses me encontré con la necesidad de acceder a ciertos ficheros desde una aplicación C++.

Estos ficheros individuales están contenidos dentro de un fichero comprimido con la extensión ZIP.

Así que me puse a buscar alguna librería C o C++ que permitiera acceder a ficheros comprimidos, y después de probar algunas, con resultados decepcionantes, di con una página que contiene unas utilidades que permiten hacerlo de una forma sencilla y rápida, dos de las características que considero más importantes y deseables en cualquier caso.

Así que lo comparto con vosotros, al tiempo que explico cómo usarlo y muestro algunos ejemplos.

Descargar el paquete

Lo primero es conseguir el «paquete». No se trata de una librería, sino de un conjunto de ficheros entre los que hay cuatro que son los que nos interesan. Dos ficheros C++ y dos ficheros de cabecera. Estos cuatro ficheros forman dos parejas, una para comprimir y otra para descomprimir, que tienen los nombres: (zip.cpp, zip.h) y (unzip.cpp, unzip.h).

Esto, de entrada, ya es una buena noticia, puesto que simplifica mucho la inclusión de ficheros en un proyecto que use ficheros comprimidos. En mi caso, por ejemplo, sólo necesité añadir al proyecto la pareja (unzip.cpp, unzip.h), dado que no necesitaba comprimir ficheros, sólo descomprimirlos.

El paquete se descarga desde esta página: Zip Utils – clean, elegant, simple, C++/win32

Como el propio título indica, es una forma limpia, elegante y simple de utilidades de compresión, para C++ y para el API 32 de Windows. El señor Lucian Wischik se ha tomado la molestia de reempaquetar el código de zlib disponible en www.gzip.org/zlib, escrito por Jean-Loup Gailly y Mark Adler entre otros.

El paquete contiene también otros ficheros: un readme.txt, un fichero HTML con el contenido de la página desde la que lo hemos descargado, y algunos ejemplos de uso.

Descomprimir el paquete

Ya sé que parece raro que una utilidad para descomprimir y comprimir ficheros se distribuya comprimida, pero el problema nunca fue comprimir o descomprimir ficheros, sino hacerlo desde nuestros programas C++.

De modo que sería conveniente guardar el fichero comprimido en un lugar seguro y accesible, y descomprimir los ficheros que necesitemos en la carpeta del proyecto que estemos desarrollando. A veces necesitaremos los cuatro ficheros, y otras veces sólo una de las parejas de ficheros.

Descomprimir ficheros

Veamos primero un ejemplo sencillo para descomprimir ficheros desde C++.

#include <iostream>
#include <windows.h>
#include "unzip.h"

using namespace std;

int main() {
    HZIP hz = OpenZip("fichero.zip", 0);
    ZIPENTRY ze;

    GetZipItem(hz, -1, &ze);
    int numitems=ze.index;

    for (int i = 0; i < numitems; i++) {
        GetZipItem(hz, i, &ze);
        cout << ze.name << endl;
        UnzipItem(hz, i, ze.name);
    }
    CloseZip(hz);
}

La idea de este pequeño programa es sencilla:

  1. Obtenemos un manipulador HZIP para el fichero zip, usando la función OpenZip. El primer parámetro es el nombre del fichero, el segundo, la contraseña, o 0 si no hay.
  2. Obtenemos un objeto ZIENTRY, con el índice -1 como segundo parámetro, usando la función GetZipItem. Este valor de índice obtiene un objeto que contiene características del fichero zip completo, como el número de ficheros que contiene. Para el primer parámetro indicamos el manipulador del fichero zip, y en el tercero, un puntero a la estructura ZIENTRY que recibirá los datos obtenidos.
  3. El dato index del objeto ZIENTRY obtenido contiene el número de ficheros que contiene el fichero comprimido. Almacenamos ese valor para usarlo en el bucle que recorre cada uno de los ficheros contenidos en el fichero zip.
  4. Volvemos a usar la función GetZipItem, esta vez con el índice de cada uno de los ficheros, empezando en 0, para obtener un objeto ZIENTRY con la descripción de cada fichero.
  5. En nuestro ejemplo mostramos el nombre del fichero, que está en el campo name, y lo extraemos descomprimido, usando la función UnzipItem, usando el mismo valor de índice, y el nombre que queremos que tenga el fichero, que será el mismo almacenado en name. Este nombre contiene la ruta relativa completa, por lo que se creará la estructura de directorios original.
  6. Una vez descomprimidos todos los ficheros, liberamos el manipulador usando la función CloseZip.

En el proyecto será necesario añadir los ficheros «unzip.cpp» y «unzip.h».

En el futuro crearé una referencia completa de las funciones y estructuras incluidas en esta librería.

Comprimir ficheros

Ahora veamos un ejemplo sencillo para comprimir ficheros desde C++.

#include <windows.h>
#include <iostream>
#include "zip.h"

using namespace std;

int main() {
    HZIP hz;
    
    hz = CreateZip("ejemplo2.zip",0);
    ZipAdd(hz,"main.cpp", "main.cpp");
    ZipAdd(hz,"zip.cpp", "zip.cpp");
    ZipAdd(hz,"zip.h", "zip.h");
    ZipAdd(hz,"bin/debug/zip.exe", "./bin/debug/zip.exe");
    CloseZip(hz);
    return 0;
}

Tampoco en este ejemplo hay nada complicado:

  1. Creamos el fichero zip y obtenemos un manipulador HZIP para él, usando la función CreateZip. El primer parámetro es el nombre del fichero. El segundo, la contraseña, o 0 si no hay contraseña.
  2. Añadimos ficheros usando la función ZipAdd. El primer parámetro es el manipulador del fichero zip, el segundo el nombre que tendrá el fichero dentro del zip, y el tercero, el nombre del fichero a añadir. En el caso del nombre dentro del zip, no se deben incluir los directorios . o .. como parte del nombre.
  3. Cerramos el fichero y el manipulador, usando la función CloseZip.

En el proyecto será necesario añadir los ficheros «zip.cpp» y «zip.h».

Combinar compresiones y descompresiones

Evidentemente, si nuestra aplicación necesita comprimir y descomprimir ficheros, podemos combinar ambas características. Sólo será necesario incluir en el proyecto los cuatro ficheros: «zip.h», «zip.cpp», «unzip.h» y «unzip.cpp».

Otras posibilidades

Tan sólo añadir a lo anterior que esta utilidad permite usar archivos comprimidos de otras formas, por ejemplo, como parte de un fichero de recursos. También es posible comprimir en memoria en lugar de en un fichero, y usar tuberías.

La estructura ZIENTRY

En los ejemplos anteriores sólo hemos usado esta estructura para obtener el nombre de cada fichero o el número de ficheros en el zip, pero tiene otros valores que nos pueden interesar.

Esta es la declaración de la estructura completa:

typedef struct
{ int index;                 // index of this file within the zip
  TCHAR name[MAX_PATH];      // filename within the zip
  DWORD attr;                // attributes, as in GetFileAttributes.
  FILETIME atime,ctime,mtime;// access, create, modify filetimes
  long comp_size;            // sizes of item, compressed and uncompressed. These
  long unc_size;             // may be -1 if not yet known (e.g. being streamed in)
} ZIPENTRY;

Ya conocemos los dos primeros: el índice y el nombre.

El tercero contiene los atributos del fichero.

El cuarto, quinto y sexto las fechas y horas de último acceso, creación y modificación del fichero.

El séptimo el tamaño del fichero comprimido.

El octavo, el tamaño del fichero descomprimido.

Estos dos últimos pueden ser -1, si el tamaño es desconocido, por ejemplo, si el fichero zip proviene de un canal stream.

Saludos

Hola, visitante, seas quien seas: ¡bienvenido!

He decidido crear un blog asociado a ‘Con Clase’, para incluir todas aquellas cosas que no tienen un espacio claro en la página, y, en la medida de lo posible, para intentar recuperar parte del público que hemos ido perdiendo en los últimos meses.

La idea es insertar al menos una entrada cada semana o dos, y en principio los temas estarán relacionados con programación, aunque seguramente ampliaremos algo el abanico, para incluir cosas que actualmente no tienen cabida en conclase.net.

Por desgracia, el nombre «conclase.wordpress.com» ya existe, aunque el blog asociado está vacío y abandonado, así que me veo obligado a usar otro nombre.

A ver qué sale de esto. De momento tengo buenas intenciones, a ver cuánto duran. 🙂