AfxOleInit versus CoInitializeEx in MFC Programmen

Immer wieder mal, taucht MFC Code auf und es wird CoInitializeEx verwendet.
Nicht unbedingt ein Fehler, aber ich denke man sollte auch hier die MFC-Art-und-Weise verwenden: AfxOleInit. Und man sollte sich klar sein, welche Apartment Modes man verwenden darf!

AfxOleInit selbst ist ein per Thread Wrapper für ::OleInitialize(NULL);.
Zudem sorgt AfxOleInit dafür, dass bei Beenden des Threads automatisch AfxOleTerm aufgerufen wird.

::OleInitialize(NULL) wiederum ist ein Wrapper für den Aufruf von CoInitializeEx im STA Kontext:
Siehe MSDN http://msdn.microsoft.com/en-us/library/ms690134(VS.85).aspx 

OleInitialize calls CoInitializeEx internally to initialize the COM library on the current apartment. Because OLE operations are not thread-safe, OleInitialize specifies the concurrency model as single-thread apartment.

Die MFC benutzt und verlangt genau diesen STA Modus. Sofern man also nicht wirklich weiß was man mit CoInitializeEx macht, sollte man es in der MFC vermeiden. Zudem man sich eben auch nicht mehr um den Cleanup kümmern muss, da die MFC dies selber macht.

Siehe auch MSDN Doku zu AfxOleInit
http://msdn.microsoft.com/de-de/library/e91aseaz(VS.80).aspx

MFC applications must be initialized as single threaded apartment (STA). If you call CoInitializeEx in your InitInstance override, specify COINIT_APARTMENTTHREADED (rather than COINIT_MULTITHREADED). For more information, see PRB: MFC Application Stops Responding When You Initialize the Application as a Multithreaded Apartment (828643) at http://support.microsoft.com/default.aspx?scid=kb;en-us;828643.

Aber auch hier sei erwähnt, dass man sicherlich auch andere Apartment Modes verwenden kann. Allerdings muss man dann wirklich wissen was man macht und darf sich nicht auf die MFC Funktionen stützen. Für solche Fälle verwende ich hier komplett ATL in meinen MFC Programmen.
Man sollte aber in diesen Fällen wirklich wissen was man macht 😉

BTW: Automatisch aufgeräumt bei einem gestarteten Thread wird nur dann, wenn auch AfxBeginThread verwendet wird. Siehe auch: AfxBeginThread versus _beginthreadex

Refactoring mit Hilfe des Compilers kann eine tückische Sache werden

Wieder mal eine nette Falle: Implizite Konvertierungen und ein Refactoring-Versuch.

Folgende Methoden wurden in einer Klasse verwendet:

...
bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet, long lIdProject,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);
bool GetTableCoreData(long lIdAddrSet,
            CDataConnection &dataConnection,
            CAgvipTableCoreData &coreData);
...

Die dritte Methode passte mir nicht von der Reihenfolge der Argumente. und ich änderte sie wie folgt um:

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            CDataConnection &dataConnection);

Ich habe mich nun einfach darauf verlassen, dass der Compiler mir alle entsprechenden Code Stellen schon anmeckern wird, an denen hier was nicht passt. Da ich noch einiges anderes an der Klasse geändert hatte, dauerte es noch eine Weile bis ich den nächsten Build angeworfen habe, und ehrlich gesagt, habe ich das Refactoring dieser Funktion vergessen.
Typischer Fall von: Zu viel auf einmal & Der Compiler macht einfach nicht was ich will 😉

Was passierte? Nichts ❗
Ich bekam keine Fehlermeldung zu dieser Änderung, denn CDataConnection hat eine implizite Konvertierung auf bool. Die Folge war, dass die erste Signatur der Funktion auch dieser Folge von Argumenten entsprach.

bool GetTableCoreData(long lIdAddrSet,
            CAgvipTableCoreData &coreData,
            bool bSilent=false);

Logisch, dass diese Funktion natürlich eine anderes Verhalten hatte und hier nicht mehr das passierte was ich eigentlich wollte.
Dämlicherweise rutschte diese Änderung auch noch durch die Tests und eine ganze Funktionsgruppe unserer Software wurde lahmgelegt und so ausgeliefert… Ein Bug, dazu noch von der Kategorie vermeidbar.
Was lernen wir:

  1. Es gibt keine fehlerfreie Software!
  2. Die kleinen Änderungen bringen die größten Fehler!
  3. Sich beim Refactoring auf den Compiler zu verlassen kann tückisch werden!

Manche Änderungen in den UIs machen einen wahnsinnig, heute das MS-SQL Management Server Studio

Ich nutze viel das MS-SQL Server Management Studio für MS-SQL 2005. Besonders um eben auch SQL-Statements zu testen und zu entwerfen.

Häufig lasse ich mir dazu einfach eine aktuelle Tabelle anzeigen. Mit Strg+3 kann man sich nun das SQL-Statement anzeigen lassen und es ändern. Also z.B. eine WHERE Bedingung hinzufügen.
Jeder der den Enterprise Manager gewohnt ist haut nun auf die F5-Taste, es passiert natürlich nichts 😮 . OK times are changing, wir benutzen also brav Strg+R um das neue Statement auszuführen.

OK nun klicken wir auf den Datenbanknamen und wählen hier im Kontextmenü Neue Abfrage aus. Ein bisschen SQL Statement schreiben und wir drücken Strg+R . 😮 Was denn nun? Wir bemühen also das entsprechende Menü und finden das hier der entsprechende Hotkey F5 ist.

Wer nun wieder mit MS-SQL Server 2008 arbeitet wird nun mit Strg+R kein Glück haben. Jetzt ist alles wieder gut und hier funktioniert nur noch F5. Jetzt ist dafür der Abfrage-Designer ist nun ein separater Dialog geworden, der das entsprechende Statement in das Abragefenster überträgt.
(Nachtrag 2009-05-06) Wenn man aus im Management Studio 2008 den Punkt Zeilen bearbeiten auswählt, hat man das selbe miese Verhalten wie im Management Stusio 2005. Man kommt nur nicht mehr so offensichtlich in diesen Bearbeitungsmodus und landet eher auf einer Abfrage, in der man dann aber das Ergebnis nicht bearbeiten kann.

Es gibt Momente in denen ich mich beherrschen muss nicht in die Tischplatte zu beißen… 😡

Das Web Browser Control stiehlt den Fokus wenn ein Dokument geladen wurde

Wenn man ein Web Browser Control einbindet und dieses eine Seite lädt, dann wird der Fokus in dieses Browser Control gesetzt. Dagegen ist kein Kraut und keine Notification gewachsen.
Genaugenommen ist nicht das Webbrowser Control schuld, sondern der Scriptcode der auf der Seite läuft und den Fokus umsetzt. Das sieht man schnell wenn man den Moment der WM_KILLFOCUS Nachricht im Debugger abpasst und sich den Stacktrace ansieht.

0013b84c 4a570824 USER32!NtUserSetFocus 
0013b858 4a5ce628 mshtml!CDoc::TakeFocus+0x2a 
0013b880 4a63fc0b mshtml!CElement::BecomeCurrent+0x167 
0013b8b4 4a63fb72 mshtml!CElement::focusHelper+0xcc 
0013b8c0 4a587c85 mshtml!CElement::focus+0x1d 
0013b8cc 4a5d7477 mshtml!Method_void_void+0x17 
0013b94c 4a57fae8 mshtml!CBase::ContextInvokeEx+0x462 
0013b97c 4a575413 mshtml!CElement::ContextInvokeEx+0x72 
0013b9b0 76fa5295 mshtml!CElement::ContextThunk_InvokeEx+0x44 
0013b9e8 76fa5208 jscript!IDispatchExInvokeEx2+0xa9 
0013ba20 76fa5323 jscript!IDispatchExInvokeEx+0x56 
0013ba90 76fa577b jscript!InvokeDispatchEx+0x78 
0013bad8 76fa57c6 jscript!VAR::InvokeByName+0x1c1 
0013bb18 76fa4ab0 jscript!VAR::InvokeDispName+0x43 
0013bb3c 76fa5a14 jscript!VAR::InvokeByDispID+0xfb 
0013bd30 76fa46d8 jscript!CScriptRuntime::Run+0x195b 
0013bdf4 76fa506e jscript!ScrFncObj::Call+0x69 
0013be6c 76fa5f6a jscript!CSession::Execute+0xb8 
0013bf6c 76fa672f jscript!NameTbl::InvokeDef+0x183 
0013c040 76fa5295 jscript!NameTbl::InvokeEx+0xd2

Dummerweise gibt es kein Event mehr, das danach gefeuert wird, wenn der Skript-Code abläuft. Das letzte Event bevor das aktive Fenster den Fokus verliert ist OnDocumentComplete.

Es gibt auch einige Threads die dieses Thema behandeln, aber keine vernünftige Lösung. Von so manchen Timerlösungen halte ich nichts, die da so vorgeschlagen werden, wer weiß schon wann eine Seite geladen ist?
Besonders ärgerlich auch, wenn man das Browser Control nicht mal auf einem sichtbaren Fenster hat, sondern nur in einem versteckten Fenster hält. Auch in diesem Fall verliert das aktive Fenster den Fokus.

Aber mit einem kleinen Trick bekommt man es doch hin ( 🙂 warum sonst schreibe ich den Artikel )

  1. Man baut einen OnDocumentComplete Handler ein.
  2. Wenn das Event eintritt, besorgt man sich mit GetFocus das Fenster, dass aktuell noch den Fokus inne hat.
  3. Nun sendet man mit PostMessage eine selbst definierte Nachricht (#define WM_RESTOREFOCUS (WM_APP+x)) an den Container des Webbrowser Controls und übergibt als wParam einfach das Handle des Fensters, dass man soeben mit GetFocus ermittelt hat.
  4. Nach diesem Event wird der Skript-Code ausgeführt, der den Fokus stiehlt. Das stört uns nicht.
  5. Irgendwann kommt die Messageloop jetzt wieder an die Reihe und zieht die benutzerdefinierte Nachricht WM_RESTOREFOCUS aus der Queue.
  6. Man hat natürlich einen Handler für diese Nachricht im Container des Webbrowser Controls. Dieser macht nun nichts anderes als einen SetFocus auf das HWND Handle auszuführen, das im wParam übergeben wurde. Ein Test zuvor mit IsWindow versteht sich von selbst.

Dadurch, dass die Nachricht in der Message-Queue gepostet wird, wird sie zeitnah ausgeführt sobald wirklich der User wieder eine Chance selbst Eingaben zu machen. Problem zufriedenstellend gelöst.

Das sollte sich sogar mit C# oder VB hinbekommen lassen 😉