jueves, 1 de septiembre de 2011

Mi aplicación en Internet

Con Delphi tenemos la posibilidad de meter casi todo los componentes y otros recursos dentro del ejecutable. Una aplicacion delphi tradicional no requiere de mucho mas que el ejecutable mismo. Esto facilitaría el deploy.
En el caso de trabajar con datasnap podriamos publicar nuestra aplicacion en una pequeña aplicacion web y podria ser ejecutada desde cualquier lugar siempre y cuando el servidor datasnap este publico.
Hasta aca todo parece ir sobre ruedas pero aparecen algunas cuestiones, que casi todo pueda embeberse en el ejecutable no significa que absolutamente todo este incluido, pueden quedarnos cosas afuera.
Podemos poner como ejemplo alguna DLL imprescindible con algún driver que no puede faltar, incluso algún archivo ini para guardar configuraciones básicas, previas a todo, IP/Puerto etc y  no puedo exigirle al usuario que descargue ademas de un ejecutable un archivo ini, un driver de base de datos etc, esto tiene que funcionar con un click
Solución
Arrancamos con el ejemplo mas sencillo, supongamos un archivo ini, donde vamos a guardar dirección ip de mi servidor datasnap y alguna que otra cosa que necesite.
Cuando alguien descargue nuestra aplicación puede ponerla en alguna carpeta o ejecutarla directamente
Si decide ejecutar la aplicación correrá en algún directorio que el sistema operativo determine, si se selecciona guardar se pedirá donde colocar el ejecutable. Vamos a tomar como general el caso mas problemático que es cuando seleccionan directamente "Ejecutar". La solución pensada es colocar un ini inicial en alguna carpeta propietaria del usuario o de la aplicación (Para evitar problemas de permisos entre otras cosas), pero hacerlo si y solo si este archivo aun no existe. Entonces primer problema preguntarle al sistema operativo por la carpeta del usuario, aquí el código con algunos comentarios (Solo coloco la implementación, son métodos de clase) :
....uses SHFolder, windows, Classes,   SysUtils;
{Opciones posibles
  CSIDL_DESKTOP
  CSIDL_INTERNET
  CSIDL_PROGRAMS
  CSIDL_CONTROLS
  CSIDL_PRINTERS
  CSIDL_PERSONAL
  CSIDL_FAVORITES
  CSIDL_STARTUP
  CSIDL_RECENT
  CSIDL_SENDTO
  CSIDL_BITBUCKET
  CSIDL_STARTMENU
  CSIDL_DESKTOPDIRECTORY
  CSIDL_DRIVES
  CSIDL_NETWORK
  CSIDL_NETHOOD
  CSIDL_FONTS
  CSIDL_TEMPLATES
  CSIDL_COMMON_STARTMENU
  CSIDL_COMMON_PROGRAMS
  CSIDL_COMMON_STARTUP
  CSIDL_COMMON_DESKTOPDIRECTORY
  CSIDL_APPDATA
  CSIDL_PRINTHOOD
  CSIDL_ALTSTARTUP
  CSIDL_COMMON_ALTSTARTUP
  CSIDL_COMMON_FAVORITES
  CSIDL_INTERNET_CACHE
  CSIDL_COOKIES
  CSIDL_HISTORY
  CSIDL_SYSTEM
}
class function TGenericsUtils.GetUserIniFile(ApplicationExeName:String):String;
begin
  result := ChangeFileExt(IncludeTrailingBackslash( TGenericsUtils.GetSpecialFolderPath(CSIDL_APPDATA))+ExtractFileName(ApplicationExeName), '.ini');
end;


class function TGenericsUtils.GetSpecialFolderPath(folder : integer) : string;
const
 SHGFP_TYPE_CURRENT = 0;
var
 path: array [0..MAX_PATH] of char;
begin
   //[Current User]\My Documents
   //CSIDL_PERSONAL;
   //All Users\Application Data
   //CSIDL_COMMON_APPDATA;
   //[User Specific]\Application Data
   //CSIDL_LOCAL_APPDATA;
   //Program Files
   //CSIDL_PROGRAM_FILES;
   //All Users\Documents
   //CSIDL_COMMON_DOCUMENTS;


 if SUCCEEDED(SHGetFolderPath(0,folder,0,SHGFP_TYPE_CURRENT,@path[0])) then
   Result := path
 else
   Result := '';
end;

Aquí el método GetUserIniFile me retornara el nombre del ini de mi aplicación, ademas del nombre del archivo esta la ruta completa a el, ruta solicitada al sistema operativo por medio del método GetSpecialFolderPath.


fn := TGenericsUtils.GetUserIniFile(Application.ExeName);
  if not FileExists(fn) then begin
    //Si no existe genero un ini basico.....
    TGenericsUtils.ExtractResource('ARCHIVOINI', fn);
  end;

El código anterior pregunta por la existencia del ini, en caso de no existir llama a una método "ExtractResource" que extrae un recurso del ejecutable, un recurso llamada "ARCHIVOINI" porque yo previamente embebí un ini de arranque en el ejecutable, tema que veremos mas adelante, para no tener que tomarme el trabajo de crear todas las secciones y claves que en un archivo ini grande puede exigir varias lineas de código.
Aquí el código de "ExtractResource":


class procedure TGenericsUtils.ExtractResource(ResName: String; Filename: String);
var
  ResStream: TResourceStream;
begin
  ResStream:= TResourceStream.Create(HInstance, ResName, RT_RCDATA);
  try
    ResStream.Position:= 0;
    ResStream.SaveToFile(Filename);
  finally
    ResStream.Free;
  end;
end;


Ademas esto me sirve para cuando tenga que embeber una dll y otro elemento que no puedo generar por código.
¿Como meto recursos en el ejecutable?
El delphi XE es muy sencillo, seleccione el item del menu como indica la figura

Luego añada los recursos colocando le tipo correspondiente y un nombre que luego sera usado para pedirle al método ExtractResource que realice la extracción deseada



En la próxima entrada extendemos la solución a modulos, dll, bpls etc