![]() |
non-vcl Thread innerhalb einer Klasse deklarieren
Hallo Community,
Ich stehe gerade vor einem Syntaxproblem (jedenfalls hoffe ich das ganz stark!). Ich möchte eine non-vcl-Thread-Klasse erstellen, die in etwa so aussehen sollte:
Delphi-Quellcode:
BeginThread verlangt eine Variable vom Typ TThreadFunc und genau das ist das Problem. Sobald dummyThread als Bestandteil einer Klasse deklariert wird, ist es keine Variable mehr. Kann mir jemand sagen, ob man dies iwie umgehen kann?
TMyClass = class
private [...] TMyThread = function dummyThread(Ptr: Pointer): DWORD; //funzt so leider nicht myThread: TMyThread; public procedure callThread(); end; [...] function TMyDialog.dummyThread(Ptr: Pointer): DWORD; // dummyThread ist Bestandteil der Klasse TMyDialog begin result := 1; end; procedure TMyDialog.callThread(); begin tHandle := BeginThread([...], @myThread, [...]); tHandle := BeginThread([...], @dummyThread, [...]); // Syntaxfehler! end; mfg Nogge |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
das
Delphi-Quellcode:
ist eine Typdeklaration, keine Variablendeklaration.
TMyThread = function dummyThread(Ptr: Pointer): DWORD;
Entweder so
Delphi-Quellcode:
Oder du willst das anderes erreichen...
TMyClass = class
private [...] type TMyThread = function dummyThread(Ptr: Pointer): DWORD; var myThread: TMyThread; public procedure callThread(); end; /EDIT: Ich sehe gerade, du nutzt Delphi 7, somit würde das nicht funktionieren... |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Ja, genau - das funktioniert so leider nicht. Gibt's da keine Lösung für ältere Delphiversionen bzw. für Version 7?
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Du hast das Problem das ThreadFunc eine einfache Funktion sein muss, und keine Methode.
Du musst also erstmal eine "einfache" Dummy-funktion erstellen und in dem einen Parameter pübergibst du halt einen Pointer auf eine Variable TMethod (ähnlich, wie es TThread macht) und kannst dann die Thread-Methode aufrufen. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Damit habe ich mich gerade gestern erst auseinandergesetzt:
![]() Das funktioniert natürlich nicht nur mit Callback-Funktionen. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Das funktioiert doch aber nur, wenn ich den durch getmem angeforderten Speicherbereich auch ausführen darf Luckie. Und wann gibts du den Speicher wieder frei?
Edit: Ich dachte doch eher an sowas (habs jetzt nicht getestet):
Delphi-Quellcode:
Naja, irgendwie so halt.function threaddispatcher(Method:ppointer):integer;stdcall; var ThreadProc:function:integer of object; begin ThreadProc:=Method^; freemem(Method,8); result:=ThreadProc; end; //... var p:pointer; getmem(p,8); move(@TMyDialog.dummyThread,p^,8); Beginthread(...,@threaddispatcher,...,p,...); |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Freigeben tue ich den Speicher wieder im Destruktor der Klasse (siehe dazu die Demo).
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Na gut, aber ich dachte mit getmem angeforderten Speicher kann man nicht ausführen. Da müsste man ja noch ein virtualprotect vorbeischicken.
Edit: Aber funktioniert ja :gruebel: |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Schon mal vielen Dank im Voraus, Lucky!
Allerdings bedarf es für mich (und best auch einigen anderen, die sich näher für deine Lösung interessieren) einer detailierteren Erklärung: Zitat:
2. "Aufruf der Methode" == Adresse der Methode? Seltsam, denn ich dachte, dass der erste Zeiger auf den Code bereits dies symbolisiert. 3. Ein Funktionszeiger einer Callback-Funktion ist also ein "erweiterter" Zeiger einer Methode? 4. Wieso hast du in deinem asm-Code alle Instruktionen mit mov-Instruktionen ersetzt? P.S. Du hast da zwei Rechtschreibfehler eingebaut: Zitat:
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Also bei mir funktioniert es. Allerdings, ob es das bei Prozessoren auch tut, die die verhindern könne, dass Code im Datenbereich ausgeführt wird, müsste man noch mal gucken. Und unter Vista ist es noch nicht getestet.
Wie würde denn dein Code mit VirtualProtect aussehen? @Nogge: Den Code habe ich von den Schweizern übernommen. Zu dem ASM-teil wollte ich sowieso selber noch ein paar Verständnisfragen stellen. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Ach Du schande. Da bin ich dann wohl wieder gefragt ;-)
So wild ist das eigentlich nicht. Ich hab irgendwo eine voll dokumentierte Version von dem ASM-Teil auf der Platte zuhause. Muss ich heut Abend mal nachgucken, wenn ich nicht in Transformers gehe. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
@Luckie
Funktioniert auch bei mir. Früher ging das doch nicht :gruebel: Ich musst immer extra mit virtualalloc und virtualfree statt getmem und freemem arbeiten (z.B. bei meinem Matheparser der die Funktion auch adhoc implementiert). Naja, vielleicht war ich auch nur zu vorsichtig. virtualprotect geht so, dass du erst mit virtualquery dir die BaseAdress und die Regionsize holst und dann mit virtualprotect dieses Segment auf Page_Execute_ReadWrite legst. @Nogge Das Prinzip von Luckie ist etwas verzwickt. Man könnte auch sagen unsauber,... aber schön :mrgreen: (ab hier für 32bit-System) 1. Unterschied zwischen Funktionszeiger und Methodenzeiger: Der Funktionszeiger besteht aus einer 32bit-Adresse die wird einfach aufgerufen , fertig. Beim Methodenzeiger liegt neben der Adresse noch ein zweiter Zeiger (self), der auf die Instanz zeigt und an die Mthode als erster (versteckter) Parameter übergeben wird. Deswegen hast du immer die Variable self zur Verfügung, damit du überhaupt auf dein Objekt zugreifen kannst. Self ist quasi der erste Übergabeparameter und steht einfach im Methodenzeiger mit drin. Deswegen gibts TMethod.Code (der stinknormale Funktionszeiger, der einfach dahin zeigt, wo die Methode anfängt, genauso wie bei einer Funktion) und TMethod.Data der "self" beinhaltet. 2. Jetzt wirds schmutzig :mrgreen: Was macht diese Methode MakeProcInstance. diese Methode schreibt einfach eine neue Funktion. Die Funktion gibts vorher noch nicht im Code und nicht in der Exe. Dazu werden 15 Byte Speicher angefordert (solang soll dei Funktion werden. Und jetzt kommt ASM, was die Methode adhoc implementiert, in dem einfach der OpCode, so wie ihn der Prozessoer will, in die 15 Bytes geschrieben wird. Darin ist halt die Variable self und der Funktionszeiger hard-gecoded enthalten. sieht dann etwa so aus:
Delphi-Quellcode:
Und nur dieses self ist der Unterschied zwischen Methoden und Funktionsaufruf.
{auf dem Stack liegt jetzt folgendes
Rücksprungadresse Parameter 1 Parameter 2 Parameter ... } //zwei Konstanten deren Wert die MakeProcinstance festlegt const self:pointer=... funktionszeiger:pointer=... asm mov ecx,self pop edx //bisherige Rücksprungadresse nach edx push ecx //self als Parameter 0 anfügen push edx //Rücksprungadresse zurück auf den Stack mov ecx,funktionszeiger //adresse nach ecx laden jmp ecx //und zur Methode SPRINGEN //hier kein Call, ansonsten würde noch eine Rücksprungadresse auf den Stack gelegt end; {auf dem Stack liegt jetzt folgendes Rücksprungadresse Parameter 0 = self Parameter 1 Parameter 2 Parameter ... } (btw: da alle Parameter auf dem Stack liegen geht eben nur stdcall) MakeProcInstance liefert jetzt die Adresse dieser neu implementierten Funktion zurück. Und diese Funktion macht eben nix weiteres, als self (welches ja in den 15 Bytes mit enthalten ist) als Parameter vorne dran zu hängen und die eigentliche Funktion (siehe Funktionszeiger) aufzurufen. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Und warum wurde das im original Code alles mit MOV gemacht?
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Warum so kompliziert? Bei BeginThread kann man doch einen Parameter übergeben. Man legt einfach eine Funktion (wie laut dokumentation beschrieben) an und nutzt den Parameter um die Instanz mitzugeben.
Delphi-Quellcode:
TMyClass = class
private procedure ThreadProc(); public procedure callThread(); end; [...] {global functions} function GlobalThreadProc(Ptr: Pointer): DWORD; begin TMyClass(Ptr).ThreadProc(); end; {class methods} procedure TMyClass.ThreadProc(); begin //Place Thread-code here end; procedure TMyClass.callThread(); begin tHandle := BeginThread([...], @GlobalThreadProc, Self, [...]); end; |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
So ist es dann also in Pseudocode:
Delphi-Quellcode:
=> Wahnsinn, vielen Dank für die ausführliche Erklärung, sirius! Ich habe keine Fragen mehr.
//zwei Konstanten, deren Wert die MakeProcinstance-Parameter festlegen
const self: pointer = M.Data; funktionszeiger: pointer = M.Code; @SirThornberry: Diese Lösung hat sirius ja bereits weiter oben vorgeschlagen, allerdings ist dies so etwas umständlich. @Lucky: Mit den mov-Instruktionen werden Op-Codes in den Speicher vor dem Funktionszeiger geschrieben, die den oben angegebenen Code ausführen (also pop, push usw.) und somit in der Klassenmethode die self-Referenz zur Verfügung stellen. Ohne diese Referenz wäre es ja keine Klassenmethode, sondern eine ganz "normale" Funktion ohne Klassenzugehörigkeit. Aber genau das Gegenteil wollte ich ja implementieren. |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Zitat:
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Zitat:
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Oh, ja natürlich, sonst macht es ja keinen Sinn. ;)
|
Re: non-vcl Thread innerhalb einer Klasse deklarieren
Mh, ich habe das mal getestet, allerdings wird der Zeiger auf den/die Parameter nicht korrekt übergeben, sodass jedesmal eine Zugriffsverletzung in TMyDialog.SaveThread erscheint.
Delphi-Quellcode:
procedure TMyDialog.createSpecificThread(methodAddr: Pointer;
var Parameter: TFileTransferList); var AThreadHandle : Integer; //Param : PParam; begin {New(Param); Param^.AObject := Parameter;} AThreadHandle := beginThread(methodAddr, Parameter); if (AThreadHandle > 0) then begin // warten, bis sich der Thread geschlossen hat if (WaitForSingleObject(AThreadHandle, INFINITE) <> WAIT_OBJECT_0) then begin ShowMsg('ERROR(WaitForSingleObject): Cannot close thread!', '', MB_ICONERROR); end; CloseHandle(AThreadHandle); end; end; [...] function TNonVCLThread.beginThread(methodAddr: Pointer; Parameter: Pointer): Integer; var method : TMethod; ThreadID : Cardinal; begin method.Code := methodAddr; method.Data := self; // converts method to function pointer FProcInst := MakeProcInstance(method); // creates the thread result := System.BeginThread(nil, 0, FProcInst, Parameter, 0, ThreadID); end; [...] // Aufruf procedure TMyDialog.SaveFiles; var files: TFileTransferList; begin files := TFileTransferList.Create(); [...] // files.add usw. createSpecificThread(@TMyDialog.SaveThread, files); end; [...] function TMyDialog.SaveThread(Ptr: Pointer): DWORD; stdcall; var Param : PParam; files : TFileTransferList; len : Integer; begin result := 0; {Param := PParam(Ptr); try files := TFileTransferList(Param^.AObject); finally Dispose(Param); end;} files := TFileTransferList(Ptr); try BytesCount := 0; len := files.Count; // <- AV bzw. ein ungültiger int-Wert for i := 0 to len-1 do [...] end; |
Re: non-vcl Thread innerhalb einer Klasse deklarieren
So, ich habe das Problem gelöst. Es lag an BeginThread. Diese Funktion verwendet CreateThread intern anders, als es von MSDN beschrieben/empfohlen wird. Ich habe daher...
Delphi-Quellcode:
...durch...
result := System.BeginThread(nil, 0, FProcInst, Parameter, 0, ThreadID);
Delphi-Quellcode:
...ersetzt und siehe da, es funktioniert nun alles wunderbar mit der Parameterübergabe.
System.IsMultiThread := true;
result := Windows.CreateThread(nil, 0, FProcInst, Parameter, 0, ThreadID); |
Alle Zeitangaben in WEZ +1. Es ist jetzt 10:09 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz