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
...