Posted by Bart van der Werf on February 19, 2006 unit Coroutine; { interface uses SysUtils, SyncObjs, Classes, Windows; type TProcThread = class(TThread) private FProc: TThreadMethod; protected procedure Execute; override; public constructor Create(Proc: TThreadMethod); end; { Coroutine support class TCoroutine = class private {We are abusing a thread here for several reasons. FContext: TProcThread; FCallerSEH: Pointer; FCallerStackBase: Pointer; FCallerStackLimit: Pointer; FCallerStack: Pointer; FCallerInstruction: Pointer; FThreadLock: TEvent; FThreadInitFlag: TEvent; FExceptionRaised: Exception; procedure StealThread; procedure Setup; procedure Reset; procedure BackToCaller; procedure Enter; protected procedure Yield; // call me from the Execute method to return the thread to // the call of Invoke, unless IsTerminating is true then an exception // is raised function IsTerminating: Boolean; // signal to Execute that it should not call // Yield and that it should cleanup its resources and return // from the method procedure Execute; virtual; abstract; // override me, either return implementation { TCoroutine } function GetCurrentAddress: Pointer; asm mov eax, [esp] ret end; procedure FpuInit; const Default8087CW: Word = $1332 { $133F}; asm FNINIT FWAIT FLDCW Default8087CW end; procedure TCoroutine.Enter; var Me: TCoroutine; begin Me := Self; asm pushad cld call FpuInit mov eax, Me mov ecx, 0 mov edx, fs:[ecx] mov [EAX].TCoroutine.FCallerSEH, edx mov ecx, 4 mov edx, fs:[ecx] mov [EAX].TCoroutine.FCallerStackBase, edx mov ecx, 8 mov edx, fs:[ecx] mov [EAX].TCoroutine.FCallerStackLimit, edx mov edx, esp mov [EAX].TCoroutine.FCallerStack, edx mov edx, offset @A mov [EAX].TCoroutine.FCallerInstruction, edx mov ecx, 0 mov edx, [EAX].TCoroutine.FCurrentSEH mov fs:[ecx], edx mov ecx, 4 mov edx, [EAX].TCoroutine.FStackBase mov fs:[ecx], edx mov ecx, 8 mov edx, [EAX].TCoroutine.FStackLimit mov fs:[ecx], edx mov edx, [EAX].TCoroutine.FCurrentStack mov esp, edx mov edx, [EAX].TCoroutine.FCurrentInstruction push edx ret @A: popad end; end; procedure TCoroutine.Setup; begin try Execute; except on e: Exception do begin FExceptionRaised := e; end; end; Reset; BackToCaller; end; constructor TCoroutine.Create; begin FInCoroutine := False; FThreadLock := TEvent.Create(nil, True, False, ''); FThreadLock.ResetEvent; FThreadInitFlag := TEvent.Create(nil, True, False, ''); FThreadInitFlag.ResetEvent; FContext := TProcThread.Create(StealThread); FThreadInitFlag.WaitFor(INFINITE); FThreadInitFlag.Free; Reset; end; destructor TCoroutine.Destroy; begin Assert(not FInCoroutine); if FActive then begin FTerminating := True; FInCoroutine := True; Enter; FInCoroutine := False; Assert(not FActive); end; if Assigned(FThreadLock) then FThreadLock.SetEvent; FContext.Free; FThreadLock.Free; end; procedure TCoroutine.Invoke; var E: Exception; begin Assert(not FInCoroutine); FActive := True; FInCoroutine := True; Enter; FInCoroutine := False; if Assigned(FExceptionRaised) then begin E := FExceptionRaised; FExceptionRaised := nil; raise E; end; end; type TBlip = record case Blap: Boolean of True: (A: TThreadMethod;); False: (B, C: Pointer;); end; procedure TCoroutine.Reset; var FProc: TBlip; begin FProc.A := Setup; FCurrentInstruction := FProc.B; FCurrentSEH := FSEH; FCurrentStack := FStack; FActive := False; end; procedure TCoroutine.StealThread; var Me: TCoroutine; begin Me := Self; asm mov eax, Me mov ecx, 0 mov edx, fs:[ecx] mov [EAX].TCoroutine.FSEH, edx mov ecx, 4 mov edx, fs:[ecx] mov [EAX].TCoroutine.FStackBase, edx mov ecx, 8 mov edx, fs:[ecx] mov [EAX].TCoroutine.FStackLimit, edx mov edx, esp sub edx, 1024; // reserve some stack for the call to the mutex mov [EAX].TCoroutine.FStack, edx end; FThreadInitFlag.SetEvent; FThreadLock.WaitFor(INFINITE); end; procedure TCoroutine.BackToCaller; var Me: TCoroutine; begin Me := Self; asm mov eax, Me cld call FpuInit mov ecx, 0 mov edx, [EAX].TCoroutine.FCallerSEH mov fs:[ecx], edx mov ecx, 4 mov edx, [EAX].TCoroutine.FCallerStackBase mov fs:[ecx], edx mov ecx, 8 mov edx, [EAX].TCoroutine.FCallerStackLimit mov fs:[ecx], edx mov edx, [EAX].TCoroutine.FCallerStack mov esp, edx mov edx, [EAX].TCoroutine.FCallerInstruction push edx ret end; end; procedure TCoroutine.Yield; var Me: TCoroutine; begin Assert(FInCoroutine); if FTerminating then raise Exception.Create('Cannot yield, terminating'); Me := Self; asm pushad cld call FpuInit mov eax, Me mov ecx, 0 mov edx, fs:[ecx] mov [EAX].TCoroutine.FCurrentSEH, edx mov edx, esp mov [EAX].TCoroutine.FCurrentStack, edx mov edx, offset @A mov [EAX].TCoroutine.FCurrentInstruction, edx mov ecx, 0 mov edx, [EAX].TCoroutine.FCallerSEH mov fs:[ecx], edx mov ecx, 4 mov edx, [EAX].TCoroutine.FCallerStackBase mov fs:[ecx], edx mov ecx, 8 mov edx, [EAX].TCoroutine.FCallerStackLimit mov fs:[ecx], edx mov edx, [EAX].TCoroutine.FCallerStack mov esp, edx mov edx, [EAX].TCoroutine.FCallerInstruction push edx ret @A: popad end; end; function TCoroutine.IsTerminating: Boolean; begin Result := FTerminating; end; { TProcThread } constructor TProcThread.Create(Proc: TThreadMethod); begin FProc := Proc; inherited Create(False); end; procedure TProcThread.Execute; begin FProc(); end; end.
Related Articles and Replies
|
|