Página principal
Artículos y trucos
Catálogo de productos
Ejemplos y descargas
Mis libros
Cursos de formación
Investigación y desarrollo
Libros recomendados
Mis páginas favoritas
Acerca del autor
 
En colaboración con Amazon
 
Intuitive Sight

Detección de instancias, números de versión...

Hay mucha gente a la que le gusta llegar a su bar favorito y que el camarero le pregunte: "¿qué, lo de siempre?". Es una visión de la vida muy respetable, pero a mí, que soy un bicho raro, la rutina me agobia. Y sí: también cuando se trata de programar. He reunido en este truco varias técnicas que me veo obligado a repetir en cada aplicación. Son pequeños detalles, pero cualquier aplicación profesional debe contar con ellos. La próxima vez que alguien defina una clase TApplication, ya sabe...

UNA SOLA INSTANCIA

Comencemos con una medida muy sensata: prohibir más de una instancia de la aplicación ejecutándose en cada máquina. Ojo con esto, porque si el usuario no es muy experto, puede tener una instancia "oculta", sólo detectable en el Administrador de Tareas, que le impida trabajar hasta reiniciar. ¿Que cómo puede llegar a tener esa "instancia oculta"? No hace falta que sea usted un mal programador. Basta con que el usuario tenga un hardware de vergüenza o controladores no firmados. Caveat emptor.

Aclarado esto, he aquí cómo detectar la ejecución por duplicado. Copie el siguiente fragmento en su fichero DPR:

resourcestring
   SAlreadyRunning =
      'Existe otra instancia activa de esta aplicación.'#13 +
      'La presente instancia se detendrá ahora mismo.';

function AlreadyRunning(const AIdentifier: string): Boolean;
begin
  CreateMutex(nil, False, PChar(AIdentifier));
  AlreadyRunning := GetLastError = ERROR_ALREADY_EXISTS;
end;

begin
   if AlreadyRunning('Empresa.Aplicación.Versión') then
   begin
      MessageBox(0, PChar(SAlreadyRunning), 'Error', MB_ICONSTOP or MB_OK);
      Halt(1);
   end;
   Application.Initialize;
   ...
end.

Aunque se utiliza un mutex, lo que se aprovecha no es su posibilidad de controlar secciones críticas, sino la asociación de un nombre al objeto del núcleo correspondiente, dentro de la máquina Windows. Este código está adaptado del manual Intuitive Delphi, y ahí puede encontrar una explicación más detallada sobre su lógica.

INFORMACION DE VERSION

No voy a intentar convencerle aquí de lo importante que es mantener un número de versión para cada elemento de un proyecto. Delphi, en particular, nos permite asociar esa información en una de las páginas del diálogo de opciones de proyecto (Project|Options):

Opciones de proyecto


Observe que el idioma del proyecto es el inglés estadounidense. La VCL de Delphi está en ese idioma, y es más sencillo "reconocerlo" en la configuración para poder traducir más adelante los mensajes de la VCL.

Lo complicado no es incluir un número de versión en un módulo, sino obtenerlo desde el propio módulo. Para ello, puede utilizar la siguiente función, que combina el uso de otras dos funciones del API: GetFileVersionInfoSize y GetFileVersionInfo.

function GetVersion: string;
var
   InfoSize, H, RsltLen: Cardinal;
   VersionBlock: Pointer;
   Rslt: PVSFixedFileInfo;
begin
   InfoSize := GetFileVersionInfoSize(PChar(Application.ExeName), H);
   VersionBlock := AllocMem(InfoSize);
   try
      GetFileVersionInfo(PChar(Application.ExeName), H, InfoSize, VersionBlock);
      VerQueryValue(VersionBlock, '\', Pointer(Rslt), RsltLen);
      Result := Format('%d.%d.%d.%d', [
         Rslt.dwProductVersionMS div 65536,
         Rslt.dwProductVersionMS mod 65536,
         Rslt.dwProductVersionLS div 65536,
         Rslt.dwProductVersionLS mod 65536]);
   finally
      FreeMem(VersionBlock);
   end;
end;

En este ejemplo estoy devolviendo el número de versión como una cadena, porque el uso más frecuente es mostrarlo simplemente en el diálogo de créditos. Por supuesto, si va a utilizar esta información para verificar por código la compatibilidad entre componentes, puede que le interese más devolver algún tipo especial de registro o algo por el estilo.

MEMORIA DISPONIBLE

Si vamos a mostrar el número de versión en la pantalla de créditos, es muy probable que querramos mostrar también el consumo de memoria de la aplicación. No veo qué utilidad tiene esto para el usuario, sinceramente, pero está de moda... y qué diantres, para el programador es muy buena idea.

El problema está en la interpretación de los números sobre el consumo de memoria. La función más popular para obtener información sobre la memoria es, quizás, GlobalMemoryStatus, definida en la unidad Windows. Pero la información que ésta retorna "no dice mucho". Es más útil mostrar la cifra de uso de memoria que aparece por omisión en la página de procesos del Administrador de Tareas. Esta cantidad se refiere al tamaño del conjunto de trabajo, o working set size. En otro truco de este sitio ya nos hemos ocupado de esta magnitud:

El truco anterior muestra cómo reducir el tamaño del conjunto de trabajo en memoria, pero es algo más complicado obtener el tamaño actual. La dificultad viene dada nuevamente porque Windows 98/Me no ofrece esa posibilidad. Por este motivo, la función requerida viene declarada en la unidad PSAPI, y sólo debemos ejecutarla si estamos seguros de estar ejecutando la aplicación sobre Windows NT/2000/XP/2003 (a este paso, será preferible referirnos a este grupo de versiones simplemente como Windows, y considerar Win98/ME como un mal recuerdo para olvidar).

Aquí tiene el código necesario para averiguar el tamaño del conjunto de trabajo en memoria de una aplicación, junto con la cantidad de memoria virtual consumida:

function FormatKBytes(Amount: Cardinal): string;
begin
   Result := FormatFloat('O, KB', Amount div 1024);
end;

   ...

   if Win32Platform = VER_PLATFORM_WIN32_NT then
   begin
      PMC.cb := SizeOf(PMC);
      GetProcessMemoryInfo(GetCurrentProcess, @PMC, SizeOf(PMC));
      txWSSize.Caption := FormatKBytes(PMC.WorkingSetSize);
      txVMSize.Caption := FormatKBytes(PMC.PagefileUsage);
   end
   else
   begin
      GlobalMemoryStatus(MemInfo);
      txWSSizeTitle.Caption := SPhysicalMemory;
      txVMSizeTitle.Caption := SAvailableMemory;
      txWSSize.Caption := FormatKBytes(MemInfo.dwTotalPhys);
      txVMSize.Caption := FormatKBytes(MemInfo.dwAvailPhys);
   end;
Opciones de proyecto

Observe que, para no desfigurar el diseño del cuadro de diálogo, comprobamos primero la versión del sistema operativo, y cuando detectamos que no es un Windows "moderno", utilizamos la función alternativa GlobalMemoryStatus. Eso sí, cambiamos también el título de las correspondientes etiquetas.