Einzelnen Beitrag anzeigen

Benutzerbild von littleDave
littleDave

Registriert seit: 27. Apr 2006
Ort: München
556 Beiträge
 
Delphi 7 Professional
 
#19

AW: BeginThread - Methoden aufruf

  Alt 29. Aug 2010, 21:16
Mit ein paar Tricks kann man auch Methoden mit Parametern, ohne "große" Probleme bekommen.

Delphi-Quellcode:
interface

type
  // Event-Deklaration, die in der Klasse aufgerufen wird
  TThreadExecuteEvent = procedure(const Params: array of const) of object;

procedure RunInThread(Meth: TThreadExecuteEvent; const Params: array of const); register;

implementation

type
  // interne Daten
  TConstArray = array of TVarData;
  PConstArray = ^TConstArray;

  PExecutionInfo = ^TExecutionInfo;
  TExecutionInfo = packed record
    EntryPoint : TThreadExecuteEvent;
    ArrayLen : integer;
    Params : PConstArray;
  end;

// Dass ist die Methode, die man bei "BeginThread" aufruft, als Parameter bei "BeginThread"
// kommt dann der Pointer auf ein TExecutionInfo - record
function __internThreadProc(ExecInfo: PExecutionInfo): integer; stdcall;
var pPtr : Pointer;
    pSelf : Pointer;
    pBase : Pointer;
    len : integer;
begin
  try
    // Start Address of the method
    pPtr := TMethod(ExecInfo^.EntryPoint).Code;
    // Self-Pointer
    pSelf := TMethod(ExecInfo^.EntryPoint).Data;
    // Parameter array
    pBase := ExecInfo^.Params;
    // length of Parameter
    len := ExecInfo^.ArrayLen;
    asm
      // save base registers
      push ecx
      push ebx
      push eax

      // push self ptr
      mov eax, pSelf
      // push array of const
      // array of const ->
      // 1st the pointer to the 1st element of the array
      mov ebx, pBase
      // 2nd (hidden parameter): high value of the array
      mov ecx, len

      // call
      call pPtr

      // restore registers
      pop eax
      pop ebx
      pop ecx
    end;
  finally
    // ExecInfo freigeben (wurde von "CallMethod" erzeugt)
    Dispose(ExecInfo);
  end;
  result := 0;
end;

procedure RunInThread(Meth: TThreadExecuteEvent; const Params: array of const);
var p: PExecutionInfo;
begin
  // p wird in der Methode "__internThreadProc" wieder freigegeben
  New(p);
  p^.EntryPoint := Meth;
  p^.ArrayLen := High(Params);
  p^.Params := PConstArray(@Params);

  // hier der BeginThread - Aufruf:
  // BeginThread(nil, 0, @__internThreadProc, p, 0, ThreadID);
  
  // Test-Code: direkter Aufruf
  __internThreadProc(p);
end;
Hier mal ein kleines Beispiel zur Benutzung:
Delphi-Quellcode:
type
  TTest = class
  private
    FValue : string;
  public
    procedure Test(const Params: array of const);
  end;

{ TTest }

procedure TTest.Test(const Params: array of const);
begin
  // Test des Self-Pointers
  ShowMessage(FValue);
  // Test auf Länge des Params-Array
  ShowMessage(IntToStr(length(Params)));
  // Ausgabe des 1. Parameters
  ShowMessage(IntToStr(TVarRec(Params[0]).VInteger));
end;

procedure TForm1.Button1Click(Sender: TObject);
var t: TTest;
begin
  t := TTest.Create;
  try
    t.FValue := 'Hallo';
    RunInThread(t.Test, [12345, 15676]);
  finally
    // wichtig: für das Testen wird kein Thread erstellt
    // daher können wir im finally-Part auch das Objekt wieder freigeben.
    // Falls man nun wirklich die Sache in einem Thread ausführt, darf
    // das Objekt erst freigegeben werden, wenn der Thread beendet ist - also nicht
    // hier.
    t.Free;
  end;
end;
Das ganze ist nur als Denk-Anstoß gedacht.

Gruß

EDIT
Ich seh gerade, dass das ganze per Threading noch nicht so ganz läuft: der Hacken ist in der Methode "RunInThread" in der Zeile: p^.Params := PConstArray(@Params); . Beim direkten Aufruf (also der Testcode), fällt der Fehler noch nicht auf. Jedoch wenn man es per Threading macht: das array-of-const wird freigegeben, sobald die Methode verlassen wird. Jedoch ist der Thread mit 99%er wahrscheinlich noch nicht beendet, d.h. der Pointer auf das "array-of-const"-Array wird ungültig. Man müsste das Array in der Methode neu kopieren - aber darauf habe ich gerade keine Lust mehr . Außerdem ist es wirklich etwas übertrieben. Hier mal eine einfache "RunInThread"-Methode, die ein beliebiges Objekt als Parameter entgegen nimmt (damit sollte es relativ einfach sein, das Grundprinzip zu verstehen):

Delphi-Quellcode:
interface

procedure RunInThread(Meth: TNotifyEvent; const Params: TObject);

implementation

type
  // interne Daten
  PExecutionInfo = ^TExecutionInfo;
  TExecutionInfo = packed record
    EntryPoint : TNotifyEvent;
    Data : TObject;
  end;

// Dass ist die Methode, die man bei "BeginThread" aufruft, als Parameter bei "BeginThread"
// kommt dann der Pointer auf ein TExecutionInfo - record
function __internThreadProc(ExecInfo: PExecutionInfo): integer; stdcall;
begin
  try
    ExecInfo^.EntryPoint(ExecInfo^.Data);
  finally
    // ExecInfo freigeben (wurde von "CallMethod" erzeugt)
    Dispose(ExecInfo);
  end;
  result := 0;
end;

procedure RunInThread(Meth: TThreadExecuteEvent; const Data: TObject);
var p: PExecutionInfo;
begin
  // p wird in der Methode "__internThreadProc" wieder freigegeben
  New(p);
  p^.EntryPoint := Meth;
  p^.Data := Data;

  // hier der BeginThread - Aufruf:
  // BeginThread(nil, 0, @__internThreadProc, p, 0, ThreadID);
  
  // Test-Code: direkter Aufruf
  __internThreadProc(p);
end;
Noch ein Beispiel
Delphi-Quellcode:
type
  TTest = class
  private
    FValue : string;
  public
    procedure Test(Sender: TObject);
  end;

{ TTest }

procedure TTest.Test(Sender: TObject);
begin
  // Test des Self-Pointers
  ShowMessage(FValue);
  // Test des Parameters
  ShowMessage(TTest(Sender).FValue);
end;

procedure TForm1.Button1Click(Sender: TObject);
var t: TTest;
begin
  t := TTest.Create;
  try
    t.FValue := 'Hallo';
    RunInThread(t.Test, t);
  finally
    // wichtig: für das Testen wird kein Thread erstellt
    // daher können wir im finally-Part auch das Objekt wieder freigeben.
    // Falls man nun wirklich die Sache in einem Thread ausführt, darf
    // das Objekt erst freigegeben werden, wenn der Thread beendet ist - also nicht
    // hier.
    t.Free;
  end;
end;
Jabber: littleDave@jabber.org
in case of 1 is 0 do external raise while in public class of object array else repeat until 1 is 0

Geändert von littleDave (29. Aug 2010 um 22:07 Uhr)
  Mit Zitat antworten Zitat