Discussion:
Timer und Mausklick
(zu alt für eine Antwort)
Sven Lanoster
2016-09-10 20:33:52 UTC
Permalink
Moin, moin.

Der Anwender meldet, dass unregelmäßig nach einiger Zeit die Buttons
nicht mehr auf Mausklicks reagieren. Zum Beispiel kommt beim Schließen
eines Fensters die Nachfrage "Sollen die Änderungen gespeichert werden?
[Ja] [Nein] [Abbrechen]". Wenn man mit der Maus auf einen der drei
Buttons zeigt, sieht man, dass der Button den Fokus bekommt, aber der
Klick wird nicht ausgelöst. Der Button ändert sich beim Klick optisch
nicht und es passiert auch sonst nichts. Mit der Tastatur läßt sich der
Button jedoch bedienen und ein Mausklick auf das System-X rechts oben
zum Schließen des Fensters funktioniert auch.

Außerdem funktionieren Comboboxen plötzlich nicht mehr. Das
Dropdown-Fenster klappt sofort wieder zu. Da es in dem Programm
Ereignis-Handler für OnDropDown gibt, in dem die DropDown-Liste
dynamisch gefüllt wird, nahm ich an, hier läge der Hase im Pfeffer. Aber
auch nach intensivem meditativem Draufstarren kann ich dort nichts
auffälliges finden.

Auffällig in dem Code ist, dass beim Start ein Timer erzeugt wird
(create(NIL)) mit 100 ms Intervall, der im Ontimer prüft, ob das Form
bereits sichtbar ist. Wenn es das ist, wird der Timer deaktiviert und
eine Initialisierung (eigenes DropDown in die Comboboxen eintragen)
angeworfen. Der Timer wird nicht freigegeben und an keiner anderen
Stelle deaktiviert.

Wenn das Programm im Fehlerzustand ist (Comboboxen und Buttons reagieren
nicht mehr richtig auf Mausklick) und man es schließt, verschwindet das
Programm-Symbol aus der Windows-Startleiste und danach hagelt es
Schutzverletzungen in variabler Anzahl und Adressen (sowohl
dereferenzierte NULL-Pointer als auch sehr große Adressen).

Die Umgebung ist etwas unübersichtlich. Das Programm ist ein
Fremdprodukt (groß, komplex, für Unternehmen, ClientServer,
Multithreaded, Terminalserver, usw.), welches Erweiterungen über
Interfaces zulässt.
Mein Quellcode ist also in einer DLL in einer Klasse, die über mehrere
Ecken von TForm erbt, ein Interface implementiert und per RegisterClass
instanzierbar ist. Das echte Form wird in einer Init-Prozedur übergeben,
so dass ich dort eigene Komponenten hinzufügen und die vorhandenen
manipulieren kann.

Und selbstverständlich ist das Verhalten nicht auf Befehl
reproduzierbar, von mir selbst gar nicht, tritt aber bei den Anwendern
mehrmals am Tag auf. Und ein Programm-Update ist umständlich,
langwierig, bürokratisch, teuer und deswegen möglichst zu vermeiden. Ich
kann also nicht mal eben etwas ändern und mit "probieren Sie mal so..."
kommen.

Per Debug-Logs habe ich festgestellt, dass der 100ms-Timer während das
Fenster noch unsichtbar ist, nur alle 500 bis 800 ms überhaupt zum
Schuss kommt. Ich vermute, dass es ein Anwender schaffen könnte, das
Fenster per [ESC] zu schließen, bevor der Timer im OnTimer deaktiviert
wird (das passiert ja nur, wenn das Fenster sichtbar ist). Da der Timer
nicht freigegeben wird (der Rest drumherum jedoch schon), werden weiter
WM_TIMER-Nachrichten an die Applikation geschickt, die ja direkt die
Speicheradresse der OnTimer-Prozedur enthalten, also anders als sonstige
Messages behandelt werden. Was mit diesen Messages dann passiert, ist
mir unklar.

Die Frage ist jetzt: Gibt es einen denkbaren Zusammenhang zwischen dem
amoklaufenden Timer und dem Mausklick-Fehler?

Sowohl die Combobox als auch der Button haben ein OnDropDown...-Event,
das (indirekt) über Messages gefeuert wird. Kann es sein, dass diese
Messages "liegen bleiben" bis zum Programmende?

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Hans-Peter Diettrich
2016-09-10 21:47:28 UTC
Permalink
Post by Sven Lanoster
Wenn das Programm im Fehlerzustand ist (Comboboxen und Buttons reagieren
nicht mehr richtig auf Mausklick) und man es schließt, verschwindet das
Programm-Symbol aus der Windows-Startleiste und danach hagelt es
Schutzverletzungen in variabler Anzahl und Adressen (sowohl
dereferenzierte NULL-Pointer als auch sehr große Adressen).
Da kann der Debug-Modus des FastMM4 sehr hilfreich sein, um kaputte
Pointer und mehr zu finden.
Post by Sven Lanoster
Die Umgebung ist etwas unübersichtlich. Das Programm ist ein
Fremdprodukt (groß, komplex, für Unternehmen, ClientServer,
Multithreaded, Terminalserver, usw.), welches Erweiterungen über
Interfaces zulässt.
Mein Quellcode ist also in einer DLL in einer Klasse, die über mehrere
Ecken von TForm erbt, ein Interface implementiert und per RegisterClass
instanzierbar ist. Das echte Form wird in einer Init-Prozedur übergeben,
so dass ich dort eigene Komponenten hinzufügen und die vorhandenen
manipulieren kann.
Das ist übel, da kann der schwarze Peter beliebig hin und her geschoben
werden :-(

DoDi
Sven Lanoster
2016-09-10 22:49:05 UTC
Permalink
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Wenn das Programm im Fehlerzustand ist (Comboboxen und Buttons
reagieren nicht mehr richtig auf Mausklick) und man es schließt,
verschwindet das Programm-Symbol aus der Windows-Startleiste und
danach hagelt es Schutzverletzungen in variabler Anzahl und Adressen
(sowohl dereferenzierte NULL-Pointer als auch sehr große Adressen).
Da kann der Debug-Modus des FastMM4 sehr hilfreich sein, um kaputte
Pointer und mehr zu finden.
Das ist grundsätzlich ein tolles Werkzeug und eine gute Idee. Vielen Dank.

Im konkreten Fall frage ich mich, ob es technisch möglich ist.
Kann ich in meiner DLL einen anderen Memorymanager benutzen als das
(fremde) Hauptprogramm? Und was passiert dann, wenn das Hauptprogramm
von meiner DLL erzeugte Controls (Owner und Parent sind ein Form des
Hauptprogramms) freigibt?

Ich benutze übrigens Notification, um die lokalen Zeiger auf NIL zu
setzen, wenn die Controls freigegeben werden.
Kann es sein, dass mein Dummy-Form in der DLL bereits freigegeben ist
und erst danach ein Notification eintrudelt?
Oder gibt es eine Automatik, die eine Komponente, die sich für
Notifications registriert hat, ebendort wieder austrägt, wenn sie selbst
freigegeben wird?
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Die Umgebung ist etwas unübersichtlich. Das Programm ist ein
Fremdprodukt (groß, komplex, für Unternehmen, ClientServer,
Multithreaded, Terminalserver, usw.), welches Erweiterungen über
Interfaces zulässt.
Mein Quellcode ist also in einer DLL in einer Klasse, die über mehrere
Ecken von TForm erbt, ein Interface implementiert und per
RegisterClass instanzierbar ist. Das echte Form wird in einer
Init-Prozedur übergeben, so dass ich dort eigene Komponenten
hinzufügen und die vorhandenen manipulieren kann.
Das ist übel, da kann der schwarze Peter beliebig hin und her geschoben
werden :-(
Weise Worte.

Zum Glück ist die Beziehung zum Hersteller des Programms sehr gut. Und
leider ist in diesem Fall eindeutig meine DLL schuld an den Problemen.
Selbst wenn der Kern des Problems im Hauptprogramm läge, bin ich es, der
einen Weg drumherum finden muss.

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Hans-Peter Diettrich
2016-09-11 11:29:47 UTC
Permalink
Post by Sven Lanoster
Im konkreten Fall frage ich mich, ob es technisch möglich ist.
Kann ich in meiner DLL einen anderen Memorymanager benutzen als das
(fremde) Hauptprogramm?
Kann man, insbesondere wenn man nicht weiß, womit das Hauptprogramm
seinen Speicher verwaltet. Nur wenn das auch ein Delphi Programm ist,
kann man die gleiche Manager-DLL verwenden.
Post by Sven Lanoster
Und was passiert dann, wenn das Hauptprogramm
von meiner DLL erzeugte Controls (Owner und Parent sind ein Form des
Hauptprogramms) freigibt?
Wie sollte das gehen?

Die meisten Probleme dürften dynamische Strings machen, die hin und her
übergeben werden.
Post by Sven Lanoster
Ich benutze übrigens Notification, um die lokalen Zeiger auf NIL zu
setzen, wenn die Controls freigegeben werden.
Die lokalen Zeiger kannst Du doch gleich löschen, oder hat Deine DLL
keine message-loop?
Post by Sven Lanoster
Kann es sein, dass mein Dummy-Form in der DLL bereits freigegeben ist
und erst danach ein Notification eintrudelt?
Oder gibt es eine Automatik, die eine Komponente, die sich für
Notifications registriert hat, ebendort wieder austrägt, wenn sie selbst
freigegeben wird?
Das geht normalerweise über das Client/Owner Modell in der VCL. Jede
Form enthält die Listen Components[], für alle zugehörigen Controls
(Owner), und Controls[] für die Baumstruktur (Parent).

DoDi
Sven Lanoster
2016-09-12 19:18:47 UTC
Permalink
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Im konkreten Fall frage ich mich, ob es technisch möglich ist.
Kann ich in meiner DLL einen anderen Memorymanager benutzen als das
(fremde) Hauptprogramm?
Kann man, insbesondere wenn man nicht weiß, womit das Hauptprogramm
seinen Speicher verwaltet. Nur wenn das auch ein Delphi Programm ist,
kann man die gleiche Manager-DLL verwenden.
KLar, Du denkst an den einfachen "Exe mit DLL"-Fall. Da habe ich auch
halbwegs im Blick, wer was wo macht.

Ich habe eine DLL, die gegen eine BPL des Herstellers gelinkt werden
muss. In dieser DLL erbe ich von einem TAxForm. Das "Ax" steht an
mehreren anderen Stelle für ActiveX. Dazu implementiere ich ein
Interface (mit AfterLoad, BeforeSave, usw.) dessen Parameter alle
OleVariant sind. Bis auf die Procedure Init, die mir das originale
(angezeigte) AxForm (als TAxForm) übergibt. Lustigerweise werden
Constructor und Destructor meines Form nie ausgeführt.

Und jetzt bin ich mir mit dem Memory-Manager nicht mehr sicher.

Auf der einen Seite werde ich vermutlich keine Kopie der VCL haben,
sondern die des Hauptprogramms benutzen. Denn ich gehe die Controls des
übergebenen Forms durch, frage den Typ ab, typecaste bei Bedarf auf
TCombobox und mache damit dann alles Mögliche.

Auf der anderen Seite kennt das Hauptprogramm nur den Namen meiner DLL
und die Klasse (die ich per RegisterClass in der DLL bekannt gebe). Also
IMHO zu wenig Infos, um daraus irgendetwas für CreateComObject zu
basteln. Und von GUIDS ist da auch nix zu sehen.
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Und was passiert dann, wenn das Hauptprogramm von meiner DLL erzeugte
Controls (Owner und Parent sind ein Form des Hauptprogramms) freigibt?
Wie sollte das gehen?
Hmm, wer gibt den Speicher denn frei, wenn ich in der DLL folgendes mache:

//origForm ist das Form des Hauptprogramms, übergeben im Init s.o.
xCB := TCombobox.create(origForm);
xCB.Parent := origForm.Panel2;

Und mich anschließend nicht weiter drum kümmere?
Post by Hans-Peter Diettrich
Die meisten Probleme dürften dynamische Strings machen, die hin und her
übergeben werden.
Ich mache tatsächlich sinngemäß sowas:
origForm.Edit2.Text := 'Ist String hier einklich ein Widestring?';
oder bei den Comboboxes tausche ich im OnDropDown per Assign die ganze
DropList aus.

Aber meine DLL ist wohl auch keine normale mit exportierten Funktionen,
sondern irgend etwas anderes gruseliges.
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Ich benutze übrigens Notification, um die lokalen Zeiger auf NIL zu
setzen, wenn die Controls freigegeben werden.
Die lokalen Zeiger kannst Du doch gleich löschen, oder hat Deine DLL
keine message-loop?
Ich bin der Meinung ich brauch die noch. Und das mit der Message-loop
verstehe ich nicht. Also weder den Zusammenhang noch wer sich einklich
wofür verantwortlich zeichnet.

Ein Timer feuert jedenfalls lustig vor sich hin. Also irgendwer wird
sich für das Dispatchen zuständig fühlen. Keinen Schimmer wer.
Erleuchtung erwünscht.
Post by Hans-Peter Diettrich
Post by Sven Lanoster
Kann es sein, dass mein Dummy-Form in der DLL bereits freigegeben ist
und erst danach ein Notification eintrudelt?
Oder gibt es eine Automatik, die eine Komponente, die sich für
Notifications registriert hat, ebendort wieder austrägt, wenn sie
selbst freigegeben wird?
Das geht normalerweise über das Client/Owner Modell in der VCL. Jede
Form enthält die Listen Components[], für alle zugehörigen Controls
(Owner), und Controls[] für die Baumstruktur (Parent).
Ich registriere mich (self) bei einer Combobox per
Combobox.FreeNotification(self);
Sagt dann jemand der Combobox bescheid, wenn ich (self) freigegeben
werde und den Notification-Aufruf wirklich nicht mehr brauchen kann?
Also funktioniert das in beide Richtungen?

Gruß,
Sven.
--
Seltsam? Aber so steht es geschrieben...
Hans-Peter Diettrich
2016-09-12 20:10:14 UTC
Permalink
Post by Sven Lanoster
Ich habe eine DLL, die gegen eine BPL des Herstellers gelinkt werden
muss.
Warum dann DLL statt Package? Unterschiedliche Delphi Versionen?

Zu ActiveX kann ich nichts sagen.
Post by Sven Lanoster
//origForm ist das Form des Hauptprogramms, übergeben im Init s.o.
xCB := TCombobox.create(origForm);
xCB.Parent := origForm.Panel2;
Parent hat mit der Freigabe nichts zu tun, der Owner wird bei Create
angegeben (origForm).

Sorry, bin zu lange aus Delphi raus :-(

DoDi

Loading...