Crear un archivo autoextraible con Delphi

Este es un ejemplo, muy sencillo, de como crear un fichero autoextraible. Este pequeño programa funciona de una manera muy simple, si se le pasa como parámetro un archivo, crea una copia de si mismo donde se incrusta una copia comprimida del archivo que se le paso como parámetro. Y si por el contrario, el programa detecta que tiene un fichero incrustado lo extrae.

El ejemplo es intencionadamente simple, por ese motivo las rutas donde se guardan los ficheros las calcula internamente el programa en vez de mostrar un dialogo "Guardar como ...", que seria lo mas correcto. En caso de utilizar el código en una aplicación "real", no sería muy complicado añadir estos diálogos, aunque sería conveniente reducir al mínimo el uso de estos para no incrementar demasiado el tamaño del ejecutable, que en este caso si es importante.

La compresión se realiza usando zlib, que viene incluido con delphi, así que no necesitamos ningún componente de terceros, ni dlls, ni nada que engorde el ejecutable o haga más difícil su distribución.

El código es el siguiente (es una aplicación sin formularios):

program SimpleSFX;
 
uses
  Windows,
  SysUtils,
  Classes,
  ZLib;
 
const
  strUID = '{EE6068A1-487C-4ED0-9DCA-9DCB0E6B35CB}';
 
// Esta funcion comprueba si existe un recurso con el nombre strUID
function CheckRes: Boolean;
var
  hRes: HRSRC;
begin
  Result:= FALSE;
  hRes:= FindResource(0, strUID, RT_RCDATA);
  if hRes <> 0 then
    Result:= TRUE;
end;
 
// Crea una copia del ejecutable con el archivo incrustado
procedure CrearSFX;
var
  Src: TFileStream;
  Temp: TMemoryStream;
  Path: String;
  hUpdateRes: THANDLE;
begin
  // Abrimos el fichero de origen como "solo lectura"
  Src:= TFileStream.Create(ParamStr(1),fmOpenRead);
  try
    Temp:= TMemoryStream.Create;
    try
      // Comprimimos el fichero
      with TCompressionStream.Create(clMax,Temp) do
      try
        CopyFrom(Src,0);
      finally
        Free;
      end;
      // La ruta del nuevo fichero es la misma que el fichero de origen + .exe
      Path:= ParamStr(1)+'.exe';
      if CopyFile(PChar(ParamStr(0)),PChar(Path),FALSE) then
      begin
        hUpdateRes:= BeginUpdateResource(PChar(Path), FALSE);
        if hUpdateRes <> 0 then
          if UpdateResource(hUpdateRes, RT_RCDATA,strUID,0,Temp.Memory,Temp.Size) then
            // Guardamos el fichero como un recurso
            if EndUpdateResource(hUpdateRes,FALSE) then
              Exit;
        DeleteFile(Path);
      end;
    finally
      Temp.Free;
    end;
  finally
    Src.Free;
  end;
end;
 
const
  BUFFERSIZE = 1024*1024;
 
procedure Extraer;
var
  Src: TResourceStream;
  Dst: TFileStream;
  Path: String;
  Buffer: PByte;
  i: Integer;
begin
  // Abrimos el recurso donde esta guardado el fichero
  Src:= TResourceStream.Create(0,strUID,RT_RCDATA);
  try
    // El fichero se extrae en la mima ruta que el ejecutable - .exe
    Path:= ChangeFileExt(ParamStr(0),EmptyStr);
    if Path <> ParamStr(0) then
    begin
      Dst:= TFileStream.Create(Path,fmCreate);
      try
        // Descomprimimos el fichero
        with TDeCompressionStream.Create(Src) do
        try
          GetMem(Buffer,BUFFERSIZE);
          try
            i:= Read(Buffer^,BUFFERSIZE);
            while i > 0  do
            begin
              Dst.Write(Buffer^,i);
              i:= Read(Buffer^,BUFFERSIZE);
            end;
          finally
            FreeMem(Buffer);
          end;
        finally
          Free;
        end;
      finally
        Dst.Free;
      end;
    end;
  finally
    Src.Free;
  end;
end;
 
 
begin
  // Comprobamos si tenemos algun archivo incrustado
  if CheckRes then
  begin
    // Si lo tenemos lo extraemos
    Extraer;
  end else
    // Si no lo tenemos y nos pasan uno como parametro, lo incrustamos
    if ParamCount = 1 then
      CrearSFX;
end.

El código se puede bajar de aquí

Para usarlo simplemente arrastra un fichero sobre el ejecutable y automáticamente se creara un fichero .exe al lado del fichero original. También puedes pasarle el fichero como parámetro, por ejemplo "SimpleSFX archivo.txt"

Enlaces de interés:
http://www.zlib.net/

Comentarios

Como siempre, muy interesante tu entrada.

Gracias Domingo.

Desconocía que existía de fábrica un componente de compresión en Delphi.

Muchas gracias, fenomenal post.

Muy bueno :-)

Como siempre amigo, muy interesante el código que publicas y sobre todo con mucha aplicación.

Saludos

¡Hijo de ****! ¡Desde que tengo memoria que quería saber cómo se hacía!

Un grande, Seoane, sos un grande.

Hola Seoane,
me ha parecido genial, muy bueno.
Tengo una duda respecto al strUID, ¿cómo lo has calculado?

Saludos y gracias

Es un número aleatorio que utilizo como "identificador único", cualquier otro nombre "único" hubiese servido también. Para generarlo simplemente pulsa en el editor de delphi Ctrl+Mayus+G y se insertara un nuevo número en el texto de forma automática.