Absturz wenn DLL ein Child Fenster erzeugt dessen Parent ein Form ist
-
Moin moin,
ich habe ein merkwürdiges Verhalten in C#/C++ entdeckt, welches ich nicht verstehe. Und zwar habe ich ein Programm in C#, welches aus einer Windows Form besteht. Die Form besitzt einen Button. Drücke ich diesen, so wird die unten stehende Funktion ShowVisualizationButton_Click() aufgerufen. Diese Funktion ruft meine DLL Funktion MyDLL.ShowVisualization auf, welche ein Fenster mittels CreateWindow erzeugt.
Rufe ich die DLL Funktion in der Variante 1 auf, also MyDLL.ShowVisualization(new IntPtr(0)), so startet das Fenster ohne Probleme. Aber es gibt dann halt keine Parent-Child Beziehung.
Rufe ich dagegen die DLL Funktion in der Variante 2 auf, also mit dem Form als Parent-Fenster, so bleibt mein Programm hängen. Es kommt die Meldung "Before CreateWindow()", aber nicht "After CreateWindow()". Also habe ich zu Debug-Zwecken eine WM_NULL Nachricht mittels SendMessageTimeout() an das Parent Fenster gesendet. Und siehe da, der Rückgabewerte ist 0 und GetLastError() liefert 1460 zurück. Die Fehlersuche hierfür liefert die Meldung "Dieser Vorgang wurde wegen Zeitüberschreitung zurückgegeben."
Da ich auf eine blockierende Message Queue in C# tippe, habe ich ShowVisualization() in einem eigenen Thread aufgerufen (siehe Variante 3). Und da funktioniert alles wie es sollte.
Hat jemand eine Ahnung warum Variante 2 nicht funktioniert?
Anwendung (C#)
class MyDLL { [DllImport("MyDLL.dll")] public static extern void ShowVisualization(IntPtr WndParent); } public partial class Form1 : Form { private Thread mStartThread; private IntPtr mWnd; public Form1() { InitializeComponent(); } private void ShowVisualization() { MyDLL.ShowVisualization(mWnd); } private void ShowVisualizationButton_Click(object sender, EventArgs e) { // Variante 1 mWnd = new IntPtr(0); ShowVisualization(); /* // Variante 2 mWnd = this.Handle; ShowVisualization(); */ /* // Variante 3 mWnd = this.Handle; mStartThread = new Thread(new ThreadStart(ShowVisualization)); mStartThread.Start(); */ } }
DLL (C++)
DLL_FUNC void __stdcall ShowVisualization(HWND hWndParent) { auto q = SendMessageTimeout(hWndParent, WM_NULL, 0, 0, SMTO_ABORTIFHUNG, 1000, nullptr); printf("WM_NULL returns %i, GetLastError: %i\n", q, GetLastError()); /* printf("Before CreateWindow()\n"); CreateWindow("D3D Window", "MyWindow", WindowStyle, x_pos, y_pos, (rc.right-rc.left), (rc.bottom - rc.top), hWndParent, LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MYMENU)), hInstance, 0); printf("After CreateWindow()\n"); */ }
-
@Quiche-Lorraine Probier es mal mit
SendMessage
. Wenn dein Programm dann einfriert, halte es im Debugger an, und schau dir den Callstack an.
-
@hustbaer
Ich habe das Problem jetzt bis auf die C++ Ebene verfolgt. Hierzu habe ich ein C++ Testprojekt aufgesetzt, welche wie die C# Anwendung aktuell nur aus einem Dialog und einem Button besteht. Und auch hier zeigt sich das gleiche Verhalten. Drücke ich den Button, bleibt in der DLL der Aufruf "SendMessage(hWnd, WM_NULL,..." hängen.Die Ursache hierfür wird aber immer konkreter: Ich warte aktiv auf das Ende der Visualisierung und blockiere damit die Message Queue.
In einem anderen Projekt nutze ich die Visualisierungsfunktion ohne DLL. Hier funktioniert das Ganze weil die Visualisierung eine Message Pump hat, welche Nachrichten ohne Berücksichtigung eines Fensterhandle abarbeitet. Ich habe da nämlich so den Verdacht dass das CreateWindow Nachrichten an das Parent Fenster schickt. Dies bestätigt sich, wenn ich in der Message Queue nur Nachrichten des Visualisierungsfenster abarbeite.
In der DLL habe ich dagegen die Visualisierungsfunktionen in einen eigenen Thread gepackt. Dadurch kommt die Message Pump der Visualisierung aber nicht mehr an die Nachrichten des Hauptfensters heran (siehe MSDN) und, so vermute ich, kommt es zu Problemen beim setzen der Parent-Child Beziehung.
Also irgentwie ein riesiges Durcheinander...
Mein Fazit: Never block the message queue
PS:
Der Callstack war sauber. Da sieht man aus dem SendMessage() und ein paar weitere Funktionsaufrufe nichts Verdächtiges.
-
@Quiche-Lorraine
SendMessage geht nur dann über die Message-Queue, wenn das Fenster einem anderen Thread gehört. Ansonsten wird die WindowProc direct aufgerufen. Weil das ja sonst auch nie gehen könnte.Wenn du natürlich eine Parent/Child Beziehung zwischen Fenstern hast die unterschiedlichen Threads gehören, dann erklärt das so ziemlich alles an Problemen was du mit Deadlocks/Hangs nur haben kannst. Das ist fummelig^10 und deswegen lässt man es auch bleiben.
Ich habe da nämlich so den Verdacht dass das CreateWindow Nachrichten an das Parent Fenster schickt.
Ja, klar. Aber was CreateWindow macht oder nicht macht is ja erstmal egal, wenn sogar ein einfaches SendMessage blockt.
Dadurch kommt die Message Pump der Visualisierung aber nicht mehr an die Nachrichten des Hauptfensters heran (siehe MSDN) und, so vermute ich, kommt es zu Problemen beim setzen der Parent-Child Beziehung.
Falls du damit meinst du versuchst Nachrichten für Fenster X welches von Thread T1 erstellt wurde aus einem anderen Thread T2 zu pumpen: korrekt, das geht nicht.