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

...

0 Response to "Punteros y Threads"

Publicar un comentario