Algoritmo de la burbuja con awk

Buscando en el baúl de los recuerdos me encontré con mis scripts de awk de la asignatura Sistemas Informáticos Monousuario y Multiusuario. Me llamó la atención una pregunta de un examen en la que se pedía ordenar la información de un fichero de datos mediante un script en awk.

El fichero datos.txt contenía esto:

Beth           4.00           0
Dan            3.75           0
Kathy         4.00           10
Mark          5.00           20
Mary          5.50           22
Susie          4.25           18

Los datos estaban separados con tabulación y no recuerdo el significado de los mismos, pero había que ordenar a cada persona por la segunda columna, de menor a mayor.

#!/bin/sh
awk '
{
  buffer[NR] = $0
}
END{
 for (i = 1; i <= NR; i++)
 {
   split(buffer[i], temp)
          for (u = i + 1; u <= NR; u++)
   {
      split(buffer[u], temp2)
      if (temp[2]>temp2[2])
         {
     linea = sprintf("%s\t%.2f\t%d", temp[1], temp[2], temp[3])
     split(linea, aux)
     buffer[i] = sprintf("%s\t%.2f\t%d", temp2[1], temp2[2], temp2[3])
     buffer[u] = sprintf("%s\t%.2f\t%d", aux[1], aux[2], aux[3])   
  }   
   } 
 }
 
 for (i = 1; i <= NR; i++)
   print buffer[i]
}
' datos.txt
khronos@khronos-netbook:~/Programming/bash$ chmod 777 burbuja.sh
khronos@khronos-netbook:~/Programming/bash$ ./burbuja.sh
Dan      3.75       0
Beth     4.00       0
Kathy    4.00       10
Susie    4.25       18
Mark     5.00       20
Mary     5.50       22
El script no es perfecto, pero me garantizó una buena nota en el examen.

Juego TicTacToe

Hace algún tiempo hice el juego del 3 en raya en Delphi. El código es bastante simple: la máquina no tiene inteligencia ni jugadas programadas, tan sólo juega a ganar escaneando el array del tablero. El juego está encapsulado en el componente TTicTacToe y se puede instalar en el IDE de Delphi como un componente visual.



Y aquí tenéis el código fuente con el programa ya compilado:
http://www.megaupload.com/?d=Y0HL5SUW

SQLServer desde C#

El otro día en clases de Desarrollo de Aplicaciones Informáticas, mi profesor de C# pretendía que utilizáramos el motor de bases de datos SQLServer. Seguramente, os parezca una tarea sencilla, pero la mitad de la clase no sabe programar y su programa de enseñanza deja mucho que desear. Su frase favorita es: bájate un manual de Google.

Anteriormente trabajé con otros motores de base de datos con Delphi y me imaginé que sería similar desde el Visual Studio. En Delphi cuentas con el componente TDBGrid y añadir, eliminar y hacer consultas SQL es muy simple; en cambio en el Visual Studio tienes el componente DataGridView que me produjo grandes dolores de cabeza, seguramente a causa de mi torpeza. Tras una hora de pelearme con el Visual Studio y siguiendo guías del msdn, busqué en Google como ejecutar instrucciones SQL sin DataGridView, sin DataSet...

Me encontré con la clase OleDbConnection, OleDbCommand y OleDbReader que hacen todo lo que buscaba. La primera hace posible la conexión con la base de datos, OleDbCommand ejecuta consultas SQL y OleDbReader obtiene los resultados de las consultas. Así que se me ocurrió crear mi propia clase y hacer una pequeña aplicación de prueba, el resultado fue esta sencilla agenda.

Primero vamos a crear la base de datos para utilizarla en nuestro proyecto. Vamos al explorador de soluciones, seleccionamos nuestro proyecto y le damos al botón derecho. Agregamos un nuevo elemento y seleccionamos "Base de datos local".


Seleccionamos Entity Data Model, siguiente, Modelo Vacío y finalizar.
En el explorador de soluciones abrimos nuestra base de datos, se nos abrirá un nuevo panel. Vamos a "Tablas", botón derecho y le damos a Crear Tabla.


Se nos abrirá una ventana nueva. En el nombre de la tabla ponemos: "Agenda" y agregamos los siguientes campos:

Id: int, identidad y clave principal.
Nombre: nvarchar(20), not null.
Apellidos: nvarchar(30), not null.
Telefono: int, not null.
Direccion: nvarchar(50), not null.


Ya tenemos la base de datos creada, ahora añadimos la clase SqlDB.cs.

using System;
using System.Data.OleDb;
using System.Windows.Forms;

/*
 * Unidad SqlDB.cs
 * Esta unidad contiene la clase SqlDB para trabajar
 * con bases de datos SQLServer mediante una interfaz
 * mínima y sin complicaciones.
 * 
 * Autor: Khronos14
 * email: khronos14@hotmail.com
 * Blog: khronos14.blogspot.com
 */ 

namespace Sqldb
{
    class SqlDB
    {       
        public const int DB_COULDNT_CONNECT     = 1;
        public const int DB_NOT_CONNECTED       = 2;
        public const int DB_ALREADY_CONNECTED   = 3;
        public const int DB_SQL_ERROR           = 4;

        private OleDbConnection DbConnection;
        private string DBFileName;
        private bool FActived;

        public SqlDB(string DBFileName)
        {
            this.DBFileName = DBFileName;
            this.FActived = false;
        }
        
        public void Close()
        {
            if (FActived)
                DbConnection.Close();
        }

        public void Connect()
        {
            if (FActived == true)
            {
                if (OnError != null)
                    OnError(this, DB_ALREADY_CONNECTED);
                return;
            }

            string StrConnection = "Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source="
                + DBFileName + ";";
            
            try
            {
                DbConnection = new OleDbConnection(StrConnection);
                DbConnection.Open();
                FActived = true;
                if (OnConnected != null)
                    OnConnected(this);
            }
            catch (Exception)
            {
                if (OnError != null)
                    OnError(this, DB_COULDNT_CONNECT);
            }
        }

        public string ExecSql(string Sql)
        {
            if (FActived == false)
            {
                if (OnError != null)
                    OnError(this, DB_NOT_CONNECTED);
                return null;
            }

            try
            {
                OleDbCommand Command = new OleDbCommand(Sql, DbConnection);

                OleDbDataReader RecordSet = Command.ExecuteReader();
                if (RecordSet.HasRows == true)
                {
                    RecordSet.Read();
                    string Result = RecordSet[0].ToString();
                    return Result;
                }
            }
            catch (Exception)
            {
                if (OnError != null)
                    OnError(this, DB_SQL_ERROR);
            }
            return null;
        }

        public void ExecSql(string Sql, ref ListView lvTable)
        {
            if (FActived == false)
            {
                if (OnError != null)
                    OnError(this, DB_NOT_CONNECTED);
                return;
            }

            try
            {
                OleDbCommand Command = new OleDbCommand(Sql, DbConnection);
              
                OleDbDataReader RecordSet = Command.ExecuteReader();
                if (RecordSet.HasRows == true)
                {
                    lvTable.View = View.Details;
                    lvTable.Clear();
                    for (int i = 0; i < RecordSet.FieldCount; i++)
                        lvTable.Columns.Add(RecordSet.GetName(i));

                    ListViewItem Item = null;
                    while (RecordSet.Read())
                    {
                        Item = new ListViewItem();
                        for (int i = 0; i < RecordSet.FieldCount; i++)
                        {
                            if (i == 0)
                                Item.Text = RecordSet[i].ToString();
                            else
                                Item.SubItems.Add(RecordSet[i].ToString());
                        }

                        lvTable.Items.Add(Item);
                    }
                }
            }
            catch (Exception)
            {
                if (OnError != null)
                    OnError(this, DB_SQL_ERROR);
            }
        }

        public bool Actived
        {
            get { return FActived; }
        }
        public delegate void TOnConnected(Object sender);
        public event TOnConnected OnConnected;

        public delegate void TOnError(Object sender, int ErrorCode);
        public event TOnError OnError;
    }
}


La función más importantes de la clase SqlDb es ExecSql, que tiene dos versiones. La primera devuelve un string y sólo tiene como parámetro una cadena que será la consulta SQL. Esta función está pensada para buscar un dato de una tabla. La segunda función tiene dos parámetros: la consulta SQL y un ListView que se pasa como referencia. Esta función te cargará las tablas en el ListView, con las columnas correspondientes.



En la imagen puedes ver como me quedó la Agenda. A continuación puedes descargar el programa compilado con todo el código fuente.

http://www.megaupload.com/?d=N0DUL0MZ

TStringList

En Delphi cuentas con una clase muy utilizada llamada TStringList, en C++ Builder también existe esta clase. Esta clase trabaja como si fuera un array de strings, permitiendo mover Items, añadir, borrar e incluso cargarlos y guardarlos desde un fichero de texto plano.

En el GNU C Compiler no existe nada parecido a esto, así que en una tarde de aburrimiento se me ocurrió crear una pequeña clase en C++ que emule las funciones de la clase TStringList de Delphi.

El header de la clase es este:

#ifndef _H_STRINGLIST
#define _H_STRINGLIST

/*
 * File: stringlist.h
 * Este archivo contiene la definición de la clase TStringList
 * Desarrollado por Khronos
 *
 * email: khronos14@hotmail.com
 * blog: khronos14.blogspot.com
*/

struct SL_ITEM{
    char *string;
};

class TStringList{
    private:
        SL_ITEM *Items;
        long numItems;
    public:
        TStringList();
        ~TStringList();
        void Add(char *string);
        long GetCount();
        char * GetItem(long Index);
        bool InsertItem(long Index, char *string);
        bool DeleteItem(long Index);
        bool SetItem(long Index, char *string);
        bool SaveToFile(char * FileName);
        bool LoadFromFile(char * FileName);
};

#endif


El archivo cpp:
/*
 * File: stringlist.cpp
 * Este archivo contiene la implementación de los métodos
 * de la clase TStringList.
 * Desarrollado por Khronos
 *
 * email: khronos14@hotmail.com
 * blog: khronos14.blogspot.com
*/

#include "stringlist.h"
#include "string.h"
#include "fstream"

/*
 * Las cabeceras string.h y fstream las pongo entre comillas dobles
 * por el sistema de resalto de código del blog :S 
*/

using namespace std;

TStringList::TStringList()
{
    numItems = 0;
}

TStringList::~TStringList()
{
    if (numItems > 0)
        delete [] Items;
}

void TStringList::Add(char *string)
{
    if (numItems > 0){
        SL_ITEM * NewItems = new SL_ITEM[numItems + 1];
        for (int i = 0; i < numItems; i++)
            {
                NewItems[i].string = new char[strlen(Items[i].string) + 1];
                strcpy(NewItems[i].string, Items[i].string);
            }
        delete [] Items;
        Items = NewItems;
    }
    else Items = new SL_ITEM[1];
        Items[numItems].string = new char[strlen(string) + 1];
        strcpy(Items[numItems].string, string);
        numItems++;
}

bool TStringList::InsertItem(long Index, char *string)
{
    if (Index >= 0 && Index < numItems)
    {
        SL_ITEM * NewItems = new SL_ITEM[numItems + 1];
  int u = 0;
        for (int i = 0; i < numItems; i++)
        {
            if (Index == i)
            {
                NewItems[u].string = new char[strlen(string) + 1];
                strcpy(NewItems[u].string, string);
                u++;
            }
            NewItems[u].string = new char[strlen(Items[i].string) + 1];
            strcpy(NewItems[u].string, Items[i].string);
            u++;
        }
        delete [] Items;
        Items = NewItems;
        numItems++;
    }
    else return false;
}

bool TStringList::DeleteItem(long Index)
{
    if (Index >= 0 && Index < numItems)
    {
        SL_ITEM * NewItems = new SL_ITEM[numItems - 1];
  int u = 0;
        for (int i = 0; i < numItems; i++)
  {
            if (i != Index)
            {
                NewItems[u].string = new char[strlen(Items[i].string) + 1];
                strcpy(NewItems[u].string, Items[i].string);
                u++;
            }
  }

        delete [] Items;
        Items = NewItems;
        numItems--;
        return true;
    }
    else return false;
}

long TStringList::GetCount()
{
    return numItems;
}

char * TStringList::GetItem(long Index)
{
    if (Index < numItems && Index >= 0)
        return Items[Index].string;
    else return NULL;
}

bool TStringList::SetItem(long Index, char *string)
{
    if (Index >= 0 && Index < numItems)
    {
        delete [] Items[Index].string;
        Items[Index].string = new char[strlen(string) + 1];
        strcpy(Items[Index].string, string);
        return true;
    }
    else return false;
}

bool TStringList::SaveToFile(char * FileName)
{
char separator[] = "\r\n";
char *Buff;

    ofstream stream(FileName, ofstream::binary);
    if (stream != NULL)
    {
        for (int i = 0; i < GetCount(); i++)
        {
            Buff = GetItem(i);
            if (Buff != NULL)
            {
                stream.write(Buff, strlen(Buff));
                if (i != GetCount() - 1)
                    stream.write(separator, 2);
            }
         }
         stream.close();
         return true;
    }
    return false;
}

bool TStringList::LoadFromFile(char * FileName)
{
char *buff;
string line;
    ifstream stream(FileName, ifstream::binary);
    if (stream != NULL)
    {
        while (stream.good())
        {
            getline(stream, line);
            buff = new char[line.length() + 1];
            strcpy(buff, line.c_str());
            Add(buff);
            delete [] buff;
        }
        stream.close();
        return true;
    }
    return false;
}

Se podría hacer un sencillo ejemplo con esta clase:
#include "iostream"
#include "stringlist.h"

using namespace std;

int main(int argc, char* argv[])
{
    TStringList *List = new TStringList();

    List->LoadFromFile("datos.txt");
    if (List->GetCount() > 0)
    {
        cout << "Se cargaron " << List->GetCount() 
        << " Items en el TStringList" << endl;
        for (int i = 0; i < List->GetCount(); i++)
            cout << "Item numero " << i
            << " = " << List->GetItem(i) << endl;
    }
    else {
        cout << "El TStringList esta vacio..." << endl;
        cout << "Vamos a introducir algo..." << endl;
        List->Add("Cadena 1");
        List->Add("Esto es una prueba");
        List->Add("TStringList class...");
        List->InsertItem(2, "Cadena insertada en la posicion 2");
        List->SaveToFile("datos.txt");
    }
    delete(List);
}

Código fuente: http://www.megaupload.com/?d=4T89LLOE

Para compilar el programa:
g++ main.cpp stringlist.cpp -o stringlist

Descargar un archivo

En programación, bajo Windows, tenemos 2 métodos para descargar un archivo, principalmente. La función URLDownloadToFile (librería urlmoon.dll) o empleando la API de WinInet. La primera función es altamente empleada en programación de malware, por lo que en un programa común, si la usamos, podemos hacer saltar las alarmas de los antivirus. Además esta función, no te permite ver como va la descarga y el progreso que lleva de descarga.
Con WinInet y usando 4 o 5 funciones podríamos descargar un archivo fácilmente. El inconveniente de WinInet es que te congelaría la aplicación porque tienes que hacer un bucle para descargar un archivo, pero con un thread se solucionaría.

En vista de este panorama, decidí crear mi propia función para descargar un archivo de una web. WinInet funciona bastante bien, pero quería investigar un poco el protocolo HTTP, así que decidí emplear sockets y el protocolo HTTP directamente para crear mi función. La función se ejecuta en un Thread evitando que se congele la aplicación, además cuenta con 3 eventos con los cuales puedes determinar el estado de la descarga.


La función se llama DownloadFile y se declara así:

procedure DownloadFile(URL: AnsiString; FileName: String; StartDownload: TOnStartDownload;
          Progress: TOnProgress; FinishDownload: TOnFinishDownload);

URL: Es la dirección del archivo web a descargar.
FileName: Es la ruta donde vas a guardar el archivo descargado.
StartDownload: Es un puntero a una función, este se ejecutará al comenzar la descarga. Devuelve como parámetro el tamaño del archivo, si se conoce.
Progress: Es un puntero a una función, este se ejecuta a medida que se va descargando el archivo. Este evento, puede ser útil si quieres mostrar el progreso de la descarga en un TProgressBar, por ejemplo.
FinishDownload: Es un puntero a una función, este se ejecuta si se produce algún error en la descarga o al terminar la descarga. Tiene como parámetro ErrorCode, de tipo byte, si ErrorCode es 0 significa que la descarga se completó con éxito.

A continuación el código de la unidad:

unit URLDown;

(*
 * *****************************************************************************
 * ***************************   Unidad URLDown  *******************************
 *    Esta unidad contiene la función DownloadFile, una función que
 * descarga un archivo desde una dirección URL. Esta función se ejecuta en
 * otro thread, por lo que no "congela" la aplicación ni causa inastabilidad.
 * Además, cuenta con 3 eventos: OnStartDownload, OnProgress y OnFinishDownload.
 *
 * Autor: Khronos
 * Email: khronos14@hotmail.com
 * Blog: khronos14.blogspot.com
 *******************************************************************************
*)

interface

uses SysUtils, Classes, Windows, WinSock;

{$DEFINE OBJECT_FUNCTIONS}
(*
  Si borras la definición OBJECT_FUNCTIONS, los eventos
  de la función DownloadFile no serán de tipo objetos.
  Para emplear esta función en modo consola o sin clases,
  comenta esta definición.
*)

const
  SZBUFFER_SIZE   = 2048; //Este es el tamaño del buffer de descarga

  URLDOWN_OK                    = 0;
  URLDOWN_INVALID_HOST          = 1;
  URLDOWN_CONNECT_ERROR         = 2;
  URLDOWN_DOWNLOAD_ERROR        = 3;
  URLDOWN_UNKNOWN_ERROR         = $FD;

type
  TOnStartDownload = procedure(FileSize: int64) {$IFDEF OBJECT_FUNCTIONS}of object{$ENDIF};
  TOnProgress = procedure(Progress: int64) {$IFDEF OBJECT_FUNCTIONS}of object{$ENDIF};
  TOnFinishDownload = procedure(ErrorCode: byte) {$IFDEF OBJECT_FUNCTIONS}of object{$ENDIF};

  TDownloadVars = record
    URL: AnsiString;
    FileName: String;
    OnStartDownload: TOnStartDownload;
    OnProgress: TOnProgress;
    OnFinishDownload: TOnFinishDownload;
  end;
  PDownloadVars = ^TDownloadVars;

procedure DownloadFile(URL: AnsiString; FileName: String; StartDownload: TOnStartDownload;
          Progress: TOnProgress; FinishDownload: TOnFinishDownload); stdcall;

implementation


function GetDomainName(const URL: AnsiString): AnsiString;
var
P1: integer;
begin
  P1:= Pos('http://', URL);
  if P1 > 0 then
   begin
     result:= Copy(URL, P1 + 7, Length(URL) - P1 - 6);
     P1:= Pos('/', result);
     if P1 > 0 then
       result:= Copy(result, 0, P1 - 1);
   end else
     begin
       P1:= Pos('/', URL);
       if P1 > 0 then
         result:= Copy(URL, 0, P1 - 1)
       else result:= URL;
     end;
end;

function GetFileWeb(const URL: AnsiString): AnsiString;
var
P1: integer;
begin
  P1:= Pos('http://', URL);
  if P1 > 0 then
   begin
     result:= Copy(URL, P1 + 7, Length(URL) - P1 - 6);
     P1:= Pos('/', result);
     if P1 > 0 then
       result:= Copy(result, P1, Length(result) - P1 + 1);
   end else
     begin
       P1:= Pos('/', URL);
       if P1 > 0 then
         result:= Copy(URL, P1, Length(URL) - P1 + 1)
       else result:= URL;
     end;
  if result = GetDomainName(URL) then
    result:= '/';
end;

procedure CleanHttp(var Mem: TMemoryStream);
var
i: integer;
Separator: array [0..3] of AnsiChar;
Mem2: TMemoryStream;
begin
 if Assigned(Mem) then
   begin
     for i := 0 to Mem.Size - 1 do
       begin
         Mem.Seek(i, 0);
         Mem.Read(Separator, 4);
         if (Separator[0] = #13) and (Separator[1] = #10) and (Separator[2] = #13)
             and (Separator[3] = #10) then
               begin
                 Mem2:= TMemoryStream.Create;
                 Mem.Seek(i + 4, 0);
                 Mem2.CopyFrom(Mem, Mem.Size - I - 4);
                 Mem:= Mem2;
                 break;
               end;
       end;
   end;
end;

function SendQuery(Socket: TSocket; RHost: sockaddr_in; Query: AnsiString): boolean;
begin
if Connect(Socket, PSockAddrIn(@RHost)^, Sizeof(RHost)) = 0 then
  begin
    send(Socket, Pointer(Query)^, Length(Query), 0);
    result:= true;
  end else
    result:= false;
end;

function CreateQuery(URL: AnsiString): AnsiString;
begin
  result:= 'GET ' + GetFileWeb(URL) + ' HTTP/1.0' + #13#10 +
    'Host: ' + GetDomainName(URL) +  #13#10 +
    'User-Agent: Khronos' + #13#10#13#10;
end;

function GetContentLength(szBuff: AnsiString; Size: Cardinal): int64;
var
dwStart, dwEnd: integer;
ContentLength: AnsiString;
begin
Result:= 0;
  dwStart:= Pos('Content-Length: ', szBuff);
  if dwStart <> 0 then
    begin
      dwStart:= dwStart + StrLen('Content-Length: ');
      dwEnd:= dwStart;
      repeat
        Inc(dwEnd);
      until (szBuff[dwEnd] = #0) or (szBuff[dwEnd] = #13) or (dwEnd = Size);
      ContentLength:= Copy(szBuff, dwStart, dwEnd - dwStart);
      if TryStrToInt64(ContentLength, Result) = false then
        result:= -1;
    end;
  dwStart:= Pos(#13#10#13#10, szBuff);
end;

function InitializeWinSock(Host: AnsiString; var Socket: TSocket; var RHost: sockaddr_in): boolean;
var
WSA: TWSAData;
Addr: u_long;
Hostent: PHostent;
Ip: ^Integer;
begin
If WSAStartup(MakeWord(2,2), WSA) = 0 then
  begin
     Socket:= WinSock.SOCKET(AF_INET, SOCK_STREAM, 0);
     if Socket <> INVALID_SOCKET then
        begin
          Hostent:= GetHostByName(PAnsiChar(GetDomainName(Host)));
          if Hostent <> nil then
            begin
              Ip:= @Hostent.h_addr_list^[0];
              RHost.sin_family:= AF_INET;
              RHost.sin_port:= htons(80);
              RHost.sin_addr.S_addr:= ip^;
              result:= true;
           end;
        end;
  end else
    result:= false;
end;

function ProcessDownload(Socket: TSocket; FileName: WideString; StartDownload: TOnStartDownload;
          Progress: TOnProgress; FinishDownload: TOnFinishDownload): boolean;
var
szBuffer: array [0..SZBUFFER_SIZE] of AnsiChar;
Stream: TMemoryStream;
ContentLength, ReturnCode: integer;
begin
result:= false;
    try
      Stream:= TMemoryStream.Create;
      ContentLength:= 0;
      repeat
        FillChar(szBuffer, SZBUFFER_SIZE, 0);
        ReturnCode:= recv(Socket, szBuffer, SZBUFFER_SIZE, 0);
        if (ContentLength = 0) and (ReturnCode > 0) then
          begin
            ContentLength:= GetContentLength(szBuffer, ReturnCode);
            if Assigned(StartDownload) then
              StartDownload(ContentLength);
          end;
        if ReturnCode > 0 then
          begin
            Stream.Write(szBuffer, ReturnCode);
            if Assigned(Progress) then
                Progress(Stream.Position);
          end;
      until ReturnCode <= 0;
      if Stream.Size > 0 then
        begin
          CleanHttp(Stream);
          Stream.SaveToFile(FileName);
          if Assigned(FinishDownload) then
            FinishDownload(URLDOWN_OK);
          result:= true;
        end;
    finally
      Stream.Free;
    end;
end;

procedure Download(P: Pointer);
var
Query: AnsiString;
Socket: TSocket;
RHost: sockaddr_in;
begin
  try
    if InitializeWinSock(TDownloadVars(P^).URL, Socket, RHost) then
      begin
        Query:= CreateQuery(TDownloadVars(P^).URL);
        if SendQuery(Socket, RHost, Query) then
          begin
            If ProcessDownload(Socket, TDownloadVars(P^).FileName, TDownloadVars(P^).OnStartDownload,
                TDownloadVars(P^).OnProgress, TDownloadVars(P^).OnFinishDownload) = false then
                if Assigned(TDownloadVars(P^).OnFinishDownload) then
                  TDownloadVars(P^).OnFinishDownload(URLDOWN_DOWNLOAD_ERROR);
            ShutDown(Socket, SD_BOTH);
            CloseSocket(Socket);
          end else
            if Assigned(TDownloadVars(P^).OnFinishDownload) then
              TDownloadVars(P^).OnFinishDownload(URLDOWN_CONNECT_ERROR);
      end else
        if Assigned(TDownloadVars(P^).OnFinishDownload) then
          TDownloadVars(P^).OnFinishDownload(URLDOWN_INVALID_HOST);

    WSACleanUp();
    Dispose(PDownloadVars(P));
  Except on Exception do
    begin
      if Assigned(TDownloadVars(P^).OnFinishDownload) then
          TDownloadVars(P^).OnFinishDownload(URLDOWN_UNKNOWN_ERROR);
    end;
  end;
end;

procedure DownloadFile(URL: AnsiString; FileName: String; StartDownload: TOnStartDownload;
          Progress: TOnProgress; FinishDownload: TOnFinishDownload);
var
DownloadVars: ^TDownloadVars;
begin
  New(DownloadVars);
  DownloadVars^.URL:= URL;
  DownloadVars^.FileName:= FileName;
  DownloadVars^.OnStartDownload:= StartDownload;
  DownloadVars^.OnProgress:= Progress;
  DownloadVars^.OnFinishDownload:= FinishDownload;

  BeginThread(nil, 0, @Download, DownloadVars, 0, PDWORD(0)^);
end;


end.

Subí a MegaUpload un programa de prueba que usa la función, además incluye todo el código fuente.

http://www.megaupload.com/?d=GU5P5QDW

Punteros y Threads

En este pequeño artículo trataré los punteros y los threads bajo Windows en Delphi (Object Pascal).
Delphi trae una clase incorporada para trabajar con threads muy útil y altamente empleada: la clase TThread de la unidad Classes.

Empleando la API de Windows, crear un thread o hilo es tan simple como usar la función CreateThread. Esta función se define así:

function CreateThread(lpThreadAttributes: Pointer;
  dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine;
  lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall;

Los parámetros más importantes son lpStartAddress que será el puntero a nuestra función y lpParameter que es el puntero de los parámetors.

Un sencillo ejemplo desde una consola podría ser:

program Project1;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils;

procedure MyThreadFunc();
var
i: integer;
begin
  for i := 1 to 10 do
    Writeln('I:= ', i);
end;

begin
  CreateThread(nil, 0, @MyThreadFunc, nil, 0, PDWORD(0)^);
  Readln;
end.

Se crearía un hilo que escribiría en la consola I:= 1 .. 10. Para enviarle parámetros a nuestra función hay que trabajar con punteros sin tipo: void en C/C++ o Pointer en Object Pascal.

program Project1;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils;

const
  SZ_MSG = 'Hello from Thread ;)';

type
  MyThreadParameters = record
    dwNum: DWORD;
    szMsg: PAnsiChar;
    Data: Pointer;
  end;
  TMyThreadParameters = MyThreadParameters;
  PMyThreadParameters = ^TMyThreadParameters;

procedure MyThreadFunc(P: Pointer);
begin
  Writeln(TMyThreadParameters(P^).szMsg);
  Writeln('El numero es: ', TMyThreadParameters(P^).dwNum);

  FreeMem(TMyThreadParameters(P^).szMsg);
  Dispose(P);
end;

var
ThreadPar: PMyThreadParameters;
begin
  New(ThreadPar);

  ThreadPar^.dwNum:= 14;
  GetMem(ThreadPar^.szMsg, Length(SZ_MSG) + 1);
  StrCopy(ThreadPar^.szMsg, SZ_MSG);
  ThreadPar^.Data:= nil;

  BeginThread(nil, 0, @MyThreadFunc, ThreadPar, 0, PDWORD(0)^);
  Readln;
end.

Definimos una constante que va a contener nuestro mensaje y las estructuras que nos servirán para pasarle varios parámetros a nuestra función.
En los punteros a estructuras es muy conveniente reservar memoria con la función New y cuando terminemos liberar con Dispose, en este caso, llamo a Dispose desde el propio Thread. Aunque no estoy muy seguro de que esto sea necesario, porque creo que Windows al terminar el Thread ya libera la memoria del puntero a nuestros parámetros.

Establecemos el número 14 a dwNum, reservamos memoria para szMsg y le sumamos 1 para el terminador de cadena. A continuación utilizamos StrCopy para copiar una cadena en la zona de memoria de szMsg, por último establecemos a null el último parámetro.

En esta ocasión llamamos a BeginThread en lugar de CreateThread. La función BeginThread es una función del propio Delphi, pero que tiene los mismos parámetros que CreateThread. Si cambiais BeginThread por CreateThread veréis como el programa no os funciona, os va a dar fallos de acceso a la memoria.

Como sentía curiosidad por saber que hacia la función BeginThread, le eché un vistazo en la unidad System.pas.

type
  PThreadRec = ^TThreadRec;
  TThreadRec = record
    {
      WARNING: Don't change these fields without also changing them in
      the C++ RTL : winrtl/source/vcl/crtlvcl.cpp
    }
    Func: TThreadFunc;
    Parameter: Pointer;
  end;


function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord;
  ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord;
  var ThreadId: LongWord): Integer;
var
  P: PThreadRec;
begin
  if Assigned(SystemThreadFuncProc) then
    P := PThreadRec(SystemThreadFuncProc(ThreadFunc, Parameter))
  else
  begin
    New(P);
    P.Func := ThreadFunc;
    P.Parameter := Parameter;
  end;
  IsMultiThread := TRUE;
  Result := CreateThread(SecurityAttributes, StackSize, @ThreadWrapper, P,
    CreationFlags, ThreadID);
end;


Hay bastantes cosas que cambian, a simple vista parece que P contiene un puntero a la función del Thread y otro puntero a los parámetros. Pero en realidad BeginThread hace muchas más cosas por ejemplo: ThreadWrapper, que es una función escrita en basm que llama a otras funciones en asm inline.

{$IFDEF MSWINDOWS}
function ThreadWrapper(Parameter: Pointer): Integer; stdcall;
{$ELSE}
function ThreadWrapper(Parameter: Pointer): Pointer; cdecl;
{$ENDIF}
asm
{$IFDEF PC_MAPPED_EXCEPTIONS}
        { Mark the top of the stack with a signature }
        PUSH    UNWINDFI_TOPOFSTACK
{$ENDIF PC_MAPPEDEXCEPTIONS}
        CALL    _FpuInit
        PUSH    EBP
{$IFNDEF PC_MAPPED_EXCEPTIONS}
        XOR     ECX,ECX
        PUSH    offset _ExceptionHandler
        MOV     EDX,FS:[ECX]
        PUSH    EDX
        MOV     FS:[ECX],ESP

...

Un script buscador

Hace unas semanas me inicié en la programación en GTK+, desde GNU/Linux con C++, y me surgió la necesidad de buscar en que headers estaban declaradas algunas constantes.

khronos@khronos-netbook:~$ ls /usr/include/gtk-2.0/gtk
gtkaboutdialog.h          
gtkhandlebox.h              
gtkscalebutton.h
gtkaccelgroup.h     
gtkhbbox.h                    
gtkscale.h
gtkaccellabel.h
...

Tengo algunas nociones de bash scripting, y sabía que el problema se podría resolver facilmente combinando el programa cat con grep.

khronos@khronos-netbook:/usr/include/gtk-2.0/gtk$ cat gtkwindow.h | grep GTK_WIN_POS_CENTER

Este comando buscaría la cadena "GTK_WIN_POS_CENTER" en el fichero gtkwindow.h. Ahora solo faltaba hacer esto en todos los archivos de la carpeta gtk/ para saber en que header estaba definido. Hacerlo uno a uno sería una ardua tarea, por lo que programé este pequeño script:

#!/bin/bash
if [ $# = 1 ]; then 
 for i in $( ls $PWD ); do  
  if [ -f "$PWD/$i" ]; then     
   if [ $(cat "$PWD/$i" | grep $1) != "" ]; then
    echo "Se encontró $1 en el fichero $i"   
   fi  
  fi 
 done
else
 echo "Error. Uso searchin [cadena]"
fi
Al final, el script me resultó muy útil, así que lo copié a la carpeta /bin para poder usarlo desde cualquier carpeta.

khronos@khronos-netbook:~/bash$ sudo cp searchin.sh /bin/searchin

Para probarlo, sólo hay que ejecutar "searchin" y la cadena a buscar.

khronos@khronos-netbook:~$ cd /usr/include/gtk-2.0/gtk
khronos@khronos-netbook:/usr/include/gtk-2.0/gtk$ searchin GTK_WIN_POS_CENTER
Se encontró GTK_WIN_POS_CENTER en el fichero gtkenums.h

Se le pueden añadir más funciones al script como búsqueda recursiva, incluir archivos ocultos, que no haga distinción de mayúsculas y minúsculas, etc.. Se puede jugar con los parámetros del comando ls y grep para añadirle más funcionalidades al script que se podrían pasar como parámetros.