![]() |
WndProc in Klasse
Wie kann ich eine WndProc in eine Klasse packen? Als Methode wird es schlecht gehen, da ja in einer solchen in EAX die Adresse des zugehörigen Objektes gespeichert wird. Die Methode ist dann also nicht "STDCALL-kompatibel" und erfüllt nicht die Voraussetzungen für eine WindowProc-Prozedur.
Ich habs jetzt noch nicht ausprobiert, aber aus oben genannten Gründen bin ich recht sicher, dass es nicht als Methode funktionieren wird. Weiß jemand, wie man das machen kann? |
Re: WndProc in Klasse
Moin Chewie,
schau Dir doch mal in den VCL Sourcen an, wie Borland das mit TForm.WndProc gelöst hat. |
Re: WndProc in Klasse
In der Hilfe nach AllocateHWnd() schauen.
Gruß Hagen |
Re: WndProc in Klasse
Zitat:
Edit: Wahrscheinlich erst ab D6 oder so dabei. |
Re: WndProc in Klasse
Moin Chewie,
vorhanden ist die Funktion schon, nur, zumindest in D5 nicht dokumentiert. :? |
Re: WndProc in Klasse
Und seit Delphi 6 von der Unit Forms.pas in Classes.pas gewandert.
|
Re: WndProc in Klasse
Oh. Auch gut. Danke, ich werds morgen mal ausprobieren.
|
Re: WndProc in Klasse
Notfalls hätte man auch Menu\Suchen\In Dateien suchen... und dort Borland\Delphi X\sources als Pfad ausgereicht.
Gruß Hagen |
Re: WndProc in Klasse
Jaja, klar, aber ich war zu faul, in den Sourcen zu suchen :wink:
Und bisher war die ZLib-Unit das einzige, wovon ich bisher eine Borland-Dokumentation vermisst habe (außer der WinAPI natürlich). |
Re: WndProc in Klasse
Mir ist gerade eingefallen, das mir das ganze ja gar nix bringt, weil ich ja die VCL nicht verwenden will. Und AllocateHWnd ruft ja ein ganzes Stück VCL-Routinen auf, sodass das Kopieren der Funktion auch nix bringt.
Gibt es denn keine einfache Methode, eine Methode als Prozedur zu simulieren? |
Re: WndProc in Klasse
Ok, es gibt viele Wege.
Man kann, wenn man erst NACH der Erzeugung eines Fensterhandles ein normales Subclassing durchführen will, per Properties zum Fensterhandle arbeiten. Unter Properties sind NICHT die aus der VCL bekannten gemein, sondern die per API mit SetProp() einem Fenster zugeordnet werden. Man alloziert einmal ein Globales Atom, also einen System-eindeutigen String der per Handle angesprochen wird. Über dieses Handle kann man nun einen DWord mit SetProp() einem Fensterhandle zuordnen. Sozusagen eine neue Eigenschaft einem Fensterhandle definieren. Dahinein kommt dann Self. Die Fensterprocedure ist für alle Fenster die diesen Weg nutzen gleich und liest nun mit GetProp() Self des Fensterhandles aus. Danach ruft sie Szum Self die Fenstermethode auf. Dieser Weg setzt voraus das man erst NACH der Erzeugung des Fensterhandles dieses Subclassing durchführen kann. D.h. mindestens die Messages wm_Create und wm_NCCreate und wm_NCCalcSize werden verschluckt. Eine andere Methode würde einen Speicherbereich allozieren in dem individueller Code steht. Dieser Speicherbereich wäre sozusagen ein ausführbarer Caller-Stub. Ich nutzen diesen Weg z.b. über Stackbasierte, lokale Variablen um z.b. Methodbasierende Callbacks aufrufen zu können. D.h. eigentlich müsste diese Callback mit einer Standard API konforme Aufrufkonvention deklariert sein, z.b. stdcall. So kann man z.b. EnumWindows() eine Callback übergeben die eigentlich eine Objectmethode aufruft. Ähnliches geht auch mit Fensterproceduren. In deinem Falle könntest Du diesen Speicherbereich direkt als Record Member im Object definieren. Sowas wie
Delphi-Quellcode:
@Self.FStub wäre dann die Fensterprocedure. In FStub.MOV_EAX_OpCode muß der OpCode für MOV EAX,Konstant rein. In FStub.JMP_OpCode muß der OpCode für JMP +-OFFSET Konstant rein. Also ein relativer Sprung.type TMyObject = class private FStub: packed record MOV_EAX_OpCode: Byte; Self: Pointer; JMP_OpCode: Byte; Offset: Integer; end; end; Gruß Hagen |
Re: WndProc in Klasse
Ich glaube, ich kann noch eine weitere Methode anbieten:
Hinter jedem Fenster ist Platz für einen Zeiger, den du mit SetWindowLongPtr anhängen kannst. Bei der Erzeugung des Fenster hängst du hier einfach den this-Zeiger (oder wie heißt der noch unter Delphi? Ach ja, Self) an. Zusätzlich erzeugst du eine "globale" Fensterprozedur, die du auch bei der Erstellung des Fensters angibst. Diese macht nichts anderes, als mit GetWindowLongPtr den Self-Zeiger aus dem Fenster zu holen und dann die Klassenmethode, die die Nachricht bearbeiten soll, aufruft. Weiß nicht, wie das unter Delphi ist, aber in C++ kann man die globale Fensterprozedur einfach der Klasse zuordnen, indem man sie als static deklariert (ist für die Übersicht besser). Pseudo-Code:
Code:
So in etwa. Hab' mir die Methode bei Microsoft abgeguckt und sie funktioniert ganz gut und ist auch relativ einfach. Musst halt in der Fensterklasse nur noch die CommonWndProc als Fensterprozedur angeben.
function CommonWndProc(...);
var pClass: TDeineKlasse; begin pClass := TDeineKlasse(GetWindowLongPtr(hWnd, GWLP_USERDATA)); Result := pClass.WindowProc(...); end; function TDeineKlasse.MyCreateWindow(...); var hWnd: THandle; begin hWnd := CreateWindow(...); SetWindowLongPtr(hWnd, GWLP_USERDATA, Self); end; |
Re: WndProc in Klasse
Hi OregonGhost,
die Methode mit den Fensterprops, arbeitet genau wie deine. Mit dem Unterschied das GWL_USERDATA vielen Programmieren bekannt ist und z.b. von deren Tools wie Mousetreiber usw. schon genutzt werden könnte. Es ist also mit GWL_USRDATA nicht sicher das dieser Platz mehrfach verwendet wird und somit unser Self überschrieben wird. Deshalb mein Vorschlag mit SetProp() und einem eindeutigen Atom. Gruß Hagen |
Re: WndProc in Klasse
So meinst du das... Hatte nur ein paar Worte gelesen, dir mir im ersten Moment spanisch vorkamen ;c)
Wie dem auch sei, der Platz bei GWLP_USERDATA ist, wie der Name andeutet, für die (fensterbesitzende) Anwendung reserviert, und Treiber, die diesen Platz verwenden, sind daher nicht gut programmiert. Zitat:
Jedenfalls, in meiner Anwendung hat sich kein Treiber da einzunisten ;c) |
Re: WndProc in Klasse
Danke euch beiden, ich werde mir das mal zu Gemüte führen. Muss das erstmal verdauen...
|
Re: WndProc in Klasse
OK, ich hab also hier drei Methoden zur Auswahl.
So, ich hab mir das nochmal aufgeschrieben, ums zu verstehen. Da Methode1 nicht viel aufwändiger ist als Methode 3, aber sicherer, fällt Methode 3 weg. Aber negaH, könntest du mir bitte nochmal die zweite Methode erklären? Wäre sehr nett. |
Re: WndProc in Klasse
Vielleicht versuche ich es nochmal.
Du kannst (musst aber nicht) ein Atom reservieren. Ein Atom ist ein 16bit-Wert (ungefaehr wie ein Handle). Statt des Atoms kannst du SetProp() und GetProp() auch direkt einen String (mit Namen der Property) uebergeben. Hagen schlug das nur vor um die Eindeutigkeit des Wertes auf dem System zu gewaehrleisten. Denn Atome sind systemweit eindeutig! Allerdings sind sie wie auch DDE ein Relikt aus den Zeiten von Win16 (werden aber selbst in XP noch von MS benutzt). Wenn du XP mit Themes benutzt, wird JEDES Fenster eine Window-Property haben, die dem Theme-Manager irgendwas sagt. Naeheres habe ich noch nicht rausbekommen. Wenn du Lust hast, kannst du mit meinem ![]() Ein Nachteil von Window-Properties soll nicht verschwiegen werden: sie sind weit langsamer als zB SetWindowLong(). SetWindowLongPtr() ist im uebrigen an mehreren Stellen falsch dokumentiert und deklariert (zumindest bei Delphi 4). Zitat:
|
Re: WndProc in Klasse
Dass man statt den Atomenn auch Strings nehmen kann, ist mir klar, steht ja im SDK bei SetProp() dabei.
Hab ich ja auch geschrieben: Zitat:
|
Re: WndProc in Klasse
Davon kannst du dich definitiv verabschieden. Callbacks als Methode einbinden ist IMMER so eine Sache. Die Mechanismen haste ja in deinem ersten Post selbst erlaeutert :)
|
Re: WndProc in Klasse
Zitat:
|
Re: WndProc in Klasse
Tja ich hatte zwar schon mal geantwortet, aber irgendwie wurde es verschluckt.
Du benutzt eine globale Fensterprocedure als Dispatcher von fensterprocedure: stdcall zu Methode. Nachdem du im Object das Fensterhandle erzeugt hast muß mit GetWindowLong(gwl_WndProc) die originale Fensterprocedure geholt und im Object gespeichert werden. Danach wird mit SetProp() Self im fensterhandle gespeichert und anschließend mit SetWindowLong(gwl_WndProc, WndProcDispatcher) ein erneutes subclassing durchgeführt.
Delphi-Quellcode:
Gruß Hagen
var
SelfAtom: TAtom; function WndProcDispatcher(Wnd: hWnd; Msg,wParam,lParam: Integer): Integer; stdcall; var M: TMessage; Self: TMyObjct; begin M.Msg := Msg; M.wParam := wParam; M.lParam := lParam; M.Result := 0; Integer(Self) := GetProp(Wnd, SelfAtom); Self.WindowMethod(M); Result := M.Result; end; // unter Vorbehalt alles aus dem Gedächtnis, // für Headcrash oder 3. Weltkrieg wird also keine Vrantwortung übernommen PS: für den 2. Weg bastel ich noch ein Beispiel, da ich eigentlich diese preferiere |
Re: WndProc in Klasse
So hier die 2. Methode, wenns Fragen gibt WEIL es NICHT funktioniert dann wurschtel dich erstmal durch :)
Delphi-Quellcode:
Im obigen Beispiel wird vorrausgesetzt fas
type
TCallDispatcher = packed record POP_EAX: Byte; PUSH_CONST: Byte; Self: Pointer; PUSH_EAX: Byte; JMP_RELATIVE: Byte; Offset: Integer; end; procedure InitDispatcher(var Dispatcher: TCallDispatcher; Self, Method: Pointer); begin Dispatcher.POP_EAX := $58; Dispatcher.PUSH_CONST := $68; Dispatcher.Self := Self; Dispatcher.PUSH_EAX := $50; Dispatcher.JMP_RELATIVE := $E9; Dispatcher.Offset := PChar(Method) - PChar(@Dispatcher) - SizeOf(Dispatcher); end; type TMyObject = class FDispatcher: TCallDispatcher; FHandle: hWnd; function WndProc(Wnd: hWnd; Msg,wParam,lParam: Integer): Integer; stdcall; procedure CreateHandle; end; function TMyObject.WndProc(Wnd: hWnd; Msg,wParam,lParam: Integer): Integer; stdcall; begin end; procedure TMyObject.CreateHandle; // zur ansatzweisen Demonstration var Params: TCreateParams; begin // TControl.CreateParams(Params); InitDispatcher(FDispatcher, Self, @TMyObject.WndProc); Params.WindowClass.lpfnWndProc := @FDispatcher; // Windows.RegisterClass(); with Params do FHandle := CreateWindowEx(ExStyle, WinClassName, Caption, Style, X, Y, Width, Height, WndParent, 0, WindowClass.hInstance, Param); end; { Aufruf aus dem OS sieht so aus PUSH lParam PUSH wParam PUSH Msg PUSH Wnd CALL WndProc -> PUSH ReturnAddress JMP WndProc -> zeigt auf @FDispatcher WndProc.FDispatcher code ist POP EAX = ReturnAddress PUSH Self PUSH EAX JMP TMyObject.WndProc } procedure Test_WindowProc; var TestWndProc: function(Wnd: hWnd; Msg,wParam,lParam: Integer): Integer; stdcall; TestObject: TMyObject; begin TestObject := TMyObject.Create; try InitDispatcher(TestObject.FDispatcher, TestObject, @TMyObject.WndProc); TestWndProc := @TestObject.FDispatcher; TestWndProc(1, 2, 3, 4); finally TestObject.Free; end; end; // Beispiel für einen anderen CallDispatcher function TForm1.DoEnumWindows(Wnd: hWnd; Param: Integer): Bool; stdcall; begin end; procedure TForm1.Test_Enum; var Enum: TCallDispatcher; begin InitDispatcher(Enum, Self, @TForm1.DoEnumWindows); EnumWindows(@Enum, 0); end; 1.) TMyObject.WndProc eine statische Methode ist also NICH virtual oder dynamic 2.) TMyObject.WndProc stdcall und identisch mit einer normalen Fensterfunktion Der TCallDispatcher und InitDispatcher kann mit JEDER anderen STDCALL Callback umgehen. Z.b. also auch mit EnumWindows() ö.ä. Quatsch wie oben gezeigt. Für deine Zwecke wird also als Fensterprocedure deines Fenster die Adresse von @FDispatcher gesetzt. Achso eines noch: da ich aufzeigen wollte wie man mit dieser Methode von Anfang an JEDE Fenstermessage mitbekommt, also auch wm_Create, wm_NCCreate usw. hat sie eine nicht unwesentliche Schwäche. Da für jedes Object das auf diese Weise arbeitet eine eigene Fensterfunktion die individuell ist existiert, wird auch jedesmal eine eigene Fensterklasse benötigt. D.h. JEDESMAL muß eine eigene Fensterklasse registriert werden. Dies ist natürlich für die Mehrfache Erzeugung des gleichen Objectes inakzeptabel. Dann muß man schon ähnliche Wege gehen wie es Borland macht. Als Start-Fensterfunction wird eine globale Procedure benutzt die nur EINMAL durch wm_Create aufgerufen wird. Diese Funktion mappt dann über GetProp() gespeicherte Werte auf obigen FDispatcher jeweils individuell, mit SetWindowLong(gwl_WndProc,...), Fertig. Bei Borland heist diese Prozedur "StdWndProc". Um es perfekt zu machen musst du also mein letztes Posting mit dem Trick in diesem Posting kombinieren. :firejump: Gruß Hagen |
Re: WndProc in Klasse
Netter Trick mit TCallDispatcher. Nur solltest du noch sicherstellen, das der Speicherbereich auch ausführbar ist. Ein VirtualProtect wäre also angebracht.
Delphi-Quellcode:
procedure InitDispatcher(var Dispatcher: TCallDispatcher; Self, Method: Pointer);
var OldProtect: Cardinal; AlignedAddress: Cardinal; begin Dispatcher.POP_EAX := $58; Dispatcher.PUSH_CONST := $68; Dispatcher.Self := Self; Dispatcher.PUSH_EAX := $50; Dispatcher.JMP_RELATIVE := $E9; Dispatcher.Offset := PChar(Method) - PChar(@Dispatcher) - SizeOf(Dispatcher); if IsBadCodePtr(@Dispatcher) then begin // @Dispatcher auf Speicher-Seite ausrichten (4 KB) AlignedAddress := Cardinal(@Dispatcher) and $FFFFF000; VirtualProtect(Pointer(AlignedAddress), 4096, PAGE_EXECUTE_READWRITE, OldProtect); end; end; |
Re: WndProc in Klasse
Gute Feststellung. Ich habe dies aber absichtlich nicht erwähnt da ich noch nie Probleme damit hatte das irgendein Speicherbereich nicht auch ausführbar war. Soviel ich weiß sind normales Codesegement, Stacks und durch Borland MM angelegter Speicher immer ausführbar.
Ich lasse mich aber gerne vom Gegenteil überzeugen :)) Schwieriger wird es dann schon zu erklären warum eine so erzeugte "Callback" Funktion die ja in unserem Prozessbezogenen lokalen Speicher liegt denoch aus anderen Prozessen heraus dispatcht werden können ;) Gruß Hagen |
Re: WndProc in Klasse
Zitat:
Zitat:
|
Re: WndProc in Klasse
Jedes Processmodul bekommt seinen eigenen virtuellen Speicherbereich. Fordern wir einen Speicherblock an, zB. ein Object mit FDispatcher, so ist die Speicheradresse individuell auf den Process bezogen. D.h. zwei Prozesse mit unseren TMyObject könnten rein theoretisch ein Object allozieren das aus unserer Sicht and der gleichen Adresse im Speicher liegt und denoch an unterschiedlicher Adresse im physikalischen Speicher. Eben ein Feature von Protected Mode Systemen.
Nun installieren wir eine Fensterprocedure die ja auf unsere virtuell Speicheradresse zeigt. Aber diese Fensterfunktion kann Prozessübergreifend benutzt werden. Das macht das OS für uns per CallWindowProc() API. Aber interessant ist es allemale, da ich immer wieder von naiven Versuchen von Programmierern höre die mit GetWindowLong(gwl_Wndproc) versuchen aus Fensterhandles die zu anderen Prozessen gehören die Fensterprocedure zu lesen und direkt aufzurufen !! Aber aus deiner Antwort entnehme ich das dir das schon klar sein dürfte :) Gruß Hagen |
Re: WndProc in Klasse
Zitat:
|
Re: WndProc in Klasse
Ah, die zweite Methode ist das, was ich wollte. Habe es jetzt noch nicht ausprobiert, das werd ich gleich tun. Danke auch für das Beispiel der ersten Methode, auch wenn das nicht erforderlich war, da ich das dank deiner Erklärung und dem SDK selbst zusammengebastelt habe.
|
Re: WndProc in Klasse
So, ich hab mir das jetzt mal angekuckt.
In der Form kann ich das leider nicht gebrauchen, da das Parent-Control bei mir schon existieren muss. Ich erklär mal kurz, wie das funktionieren soll: Die Klasse THexListView ist - wie der Name schon sagt - ein Listview, das als HexViewer funktioniert. Dieses wird dann entweder in der Nachrichtenschleife einer nonVCL-Anwendung oder aber irgendwo in einer VCL-Anwendung erzeugt. Da ich also nicht weiß, in welcher "Umgebung" die Klasse funktionieren soll, will ich die Klasse autonom gestalten, sodass die Nachrichtenschleife des übergeordneten Objektes nicht verändert werden muss. Dafür subclasse ich die Nachrichtenschleife ja in der HexView-Klasse. Ich will nun nichts weiter tun, als in der Klasse die Nachrichten an das Parent zu verarbeiten, bevor das Parent selbst sie verarbeitet. |
Re: WndProc in Klasse
Naja dann musst das Parent.Handle eben subclassen. Dein ListView Control enthält dann eine ParentWndProc() und ein FParentDispatcher. Mit SetWindowLong(Paran.Handle, GWL_WNDPROC, @FParentDispatcher) subclasst du dann.
Gruß Hagen |
Re: WndProc in Klasse
Hi,
ich will das hier nochmal aufgreifen, weil ich ein ähnliches Problem habe (ähnliches? Eigentlich genau das gleiche). Ich habe mir den Beitrag jetzt mehrmals durchgelesen, allerdings bringt mich das alles nicht weiter. Das ist mir alles ein wenig sehr kompliziert. Gibt es 1. eine einfache Erklärung und/oder 2. eine einfachere Lösung? Chris |
Re: WndProc in Klasse
Hi,
ich habe es jetzt per Try-and-Error herausbekommen. Ich habe mir Hagen's 2. Methode noch einmal genauer angesehen und bin dann auch einigermaßen durchgestigen. So funktioniert bis her jedenfalls. :) Chris |
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:35 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-2025 by Thomas Breitkreuz