VS-Tipps&Tricks: Einfache Debug-Ausgabe mit TRACE auch in der Release Version

Wer wollte nicht schon immer mal gerne TRACE (Debug)-Ausgaben in seinem Release Programm haben ohne dafür überall OutputDebugString reinschreiben zu müssen.

Die nachfolgene kleine Klasse macht es möglich, den gewohnten Syntax des MFC TRACE Makros zu verwenden und direkt auf die Debugausgabe umzuleiten:

//    CTraceToOutputDebugString
//        Is a nice replacment class for TRACE
//        Easy to use with:
//            #undef TRACE
//            #define TRACE    CTraceToOutputDebugString()

class CTraceToOutputDebugString
{
public:
    // Non Unicode output helper
    void operator()(PCSTR pszFormat, ...)
    {
        va_list ptr;
        va_start(ptr, pszFormat);
        TraceV(pszFormat,ptr);
        va_end(ptr);
    }

    // Unicode output helper
    void operator()(PCWSTR pszFormat, ...)
    {
        va_list ptr;
        va_start(ptr, pszFormat);
        TraceV(pszFormat,ptr);
        va_end(ptr);
    }

private:
    // Non Unicode output helper
    void TraceV(PCSTR pszFormat, va_list args)
    {
        // Format the output buffer
        char szBuffer[1024];
        _vsnprintf(szBuffer, _countof(szBuffer), pszFormat, args);
        OutputDebugStringA(szBuffer);
    }

    // Unicode output helper
    void TraceV(PCWSTR pszFormat, va_list args)
    {
        wchar_t szBuffer[1024];
        _vsnwprintf(szBuffer, _countof(szBuffer), pszFormat, args);
        OutputDebugStringW(szBuffer);
    }
};

Durch den obenstehenden Code kann man auch in einer Release Version Trace Ausgaben erzeugen und z.B. mit DebugView.exe (Sysinternals) sichtbar machen, ohne evtl. weitere Anpassungen vornehmen zu müssen:

// Activate my special tracer
#undef TRACE
#define TRACE    CTraceToOutputDebugString()

void Foo()
{
     // Sometime usefull to see the output in a release version too
     TRACE(__FUNCTION__ " called at %d\n", GetTickCount());
}

Auslösung: DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!

Am 17. Januar habe ich den folgenden Artikel geschrieben: DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!

Ich möchte Euch die Auflösung des Problems nicht vorenthalten.

Eigentlich ist es keine Lösung sondern nur der Fakt, dass auch XP unter gleichen Bedingungen genauso lahm ist wie Vista.
Es liegt an den erweiterten Spracheinstellungen, die unter XP optional sind aber eben nicht mehr unter Vista. Dort sind die immer mit installiert.

So sieht das ganze bei einer normalen XP Installation aus, mit der entsprechenden Performance:
XP-DrawText-Fast

Man kann sehen, dass die zwei unteren Checkboxen aus sind. Wenn man diese nun einschaltet und die entsprechenden Module nachinstalliert werden, dann erlebt man unter XP nach einem Neustart die selben Geschwindigkeitseinbruch wie unter Vista:
XP-DrawText-Slow

Mein Testprogramm läuft fast 50mal langsamer als bei der Standardinstallation und damit genauso schnell/lahm wie unter Vista. Nimmt man die zwei Checks wieder heraus, dann hat man den alten gewohnten Speed.

Wenn man unter Vista in den entsprechenden Dialog der Systemeinstellung sieht, kann man auch sehen, dass man hier nichts mehr beschleunigen kann durch eine eventuelle Deinstallation, denn offensichtlich gehören diese Bestandteile bei Vista zum Inventar:
Vista-DrawText-Slow

So und damit ist auch diese Supportanfrage bei Microsoft „ungelöst“, aber zumindest „erklärt“ geschlossen.

Ich frage mich dennoch warum eine solche EInstellung solche Auswirkungen haben muss. Letzten Endes sind das auch nur Fonts mit denen umgegangen werden muss. Ich finde diesen extremen Unterschied auffällig, allerdings wird sich vermutlich nichts daran ändern…

Ich wünsche allen Lesern einen schönen Juli und verziehe mich jetzt erstmal für die nächsten 2 1/2 Wochen ohne Laptop und PC an die Nordsee, zum Radfahren, Baden und Drachen steigen lassen… 😉

Die Funktion ReportFault unter Vista kehrt nicht mehr zurück, entgegen der Dokumentation

Wer unter Windows XP angefangen die FaultRep.dll für Crash-Reports zu verwenden, wird unter Vista eine üble Erfahrung machen. Entgegen der Dokumentation kehrt ReportFault unter Vista nicht zurück.

Das ist besonders lästig, wenn man nach dem Melden des Fehlers aufgrund des Feedbacks des Kunden noch selber Aktivitäten in der eigenen Software vorgesehen hatte.

Wieder ein Fall wo es schwierig ist zwischen verschiedenen Windows-Betriebssystemen kompatibel zu bleiben. Da hat man sich auf Windows XP eingelassen und unter Vista ist das entsprechende Interface schon wieder deprecated.

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 😉

Standby Modus in einem Programm auch unter Vista verhindern

Manchmal kann man den Standby Mode wirklich nicht gebrauchen. Besonders, wenn man ein Programm hat, dass evtl. viel im Hintergrund mit einer Datenbankverbindung macht.

Wie macht man es unter Windows Vista richtig, denn BROADCAST_QUERY_DENY bei einem PBT_APMQUERYSUSPEND oder PBT_APMQUERYSUSPENDFAILED zurückzugeben funktioniert nicht.
Man muss SetThreadExecutionState verwenden!

Äußerst dumm nur, dass man dies auch wirklich für Windows Vista und Windows 7 berücksichtigen muss. Ich hatte natürlich für Windows 2000 und XP entsprechenden Code vorgesehen, die den Standby verhindern, aber wie schon erwähnt, das geht eben unter Vista nicht mehr.
Es gibt Inkompatibilitäten zwischen des OSen, die man wirklich explizit behandeln muss. Es lohnt sich ein Blick in die Dokus, den ich selber oft genug nicht mache… 😉

// Every 30 secs we disallow the standby and hibernate!
// We have to use ES_AWAYMODE_REQUIRED on Vista and Windows Server 2008
// If we allow standby and hibernation we get problems with the DB connection.
DWORD dwAwaymodeRequired = 0;
OSVERSIONINFO os;
::ZeroMemory(&os,sizeof(os));
os.dwOSVersionInfoSize = sizeof(os);
if (::GetVersionEx(&os) && os.dwMajorVersion>=6)
    dwAwaymodeRequired = ES_AWAYMODE_REQUIRED;
VERIFY(::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED |
	dwAwaymodeRequired));

LVM_GETSUBITEMRECT mit LVIR_ICON liefert andere Ergebnisse unter Vista als unter XP

Das damit auch die Funktion CListCtrl::GetSubItemRect aus der MFC betroffen ist, ist dann auch  klar.
Manche Sachen ärgern einen einfach. Vor allem wenn man nichts am Code ändert und doch falsches Verhalten erntet.

Wieder mal ist die Vista UI eigentümlich ungereimt, in diesem Fall bei einem List View.

Folgendes ist gegeben:

  • Ein List View (SysListView32) in einem Dialog oder anderen Fenster
  • Der List View hat den Stil LVS_REPORT
  • Der List View hat hat mehr als eine Spalte.
  • Dem List View wurde eine Imagelist zugewiesen.

Führt man nun auf Windows XP LVM_GETSUBITEMRECT /CListCtrl::GetSubItemRect mit LVIR_ICON aus, dann erhält man immer ein Rectangle zurück mit der entsprechenden Weite der Imagelist Symbole. Das Verhalten ist:

  • vollkommen unabhängig ob ein Manifest für COMCTL32.DLL Version 6.0 vorhanden ist oder nicht
  •  es ist auch unabhängig ob LVS_EX_SUBITEMIMAGES gesetzt ist oder nicht.

Macht man das ganze unter Vista, dann liefert LVM_GETSUBITEMRECT /CListCtrl::GetSubbItemRect ein RECT / CRect mit der Weite der Symbole immer dann wenn:

  • kein Manifest für COMCTL32.DLL Version 6.0 vorhanden ist
  • oder LVS_EX_SUBITEMIMAGES gesetzt ist

Das heißt in dem Fall

  • ein Manifest für COMCTL32.DLL Version 6.0 ist
  • und LVS_EX_SUBITEMIMAGES ist nicht gesetzt .

erhält  man ein Rectangle mit der Weite 0 (Null) 😕

Anmerkung:
 Man kann sich natürlich streiten was nun richtig ist. Wenn LVS_EX_SUBITEMIMAGES nicht gesetzt ist, dann macht LVIR_ICON zugegebenermaßen wenig Sinn. Aber es leuchtet irgendwie nicht ein, dass ohne Manifest und ohne LVS_EX_SUBITEMIMAGES, wieder ein Wert zurückgeliefert wird. Entweder ist die Weite von LVS_EX_SUBITEMIMAGES abhängig oder eben nicht.
Das Ganze ist in jedem Falle mal ungereimt und nicht kompatibel ❗

Nachtrag 26.03.2009:
Das List-Control liefert für das Subitem 0 immer ein korrektes Rectangle für LVIR_ICON! Nur wenn wirklich ein Subitem (>0) abgefragt wird, tritt das Problem auf.

Memory Dumps on the fly

Ich hatte in einem unserer Release-Kandidaten ein massives Problem. In bestimmten nicht reproduzierbaren Situationen, blieb zeitgleich auf allen angeschlossenen Arbeitsstationen das Programm stehen. Und nun?

Der Deadlock, der auftrat war so fatal, dass ich nicht mal mehr über eine versteckte Funktion einen Speicherdump auslösen konnte. Dazu verwende intern üblicherweise eine reservierte Tastenkombination. Nur wenn keine Nachrichten mehr abgearbeitet werden, gibt es auch keine Funktionen, die man per Tastatur aufrufen kann.

Glücklicherweise wurde auf allen betroffen Rechner Windows Vista eingesetzt. Und die Lösung für diesen Fall ist unter Vista so einfach wie genial. Im Task-Manager unter Vista findet sich im Kontextmenü ein unauffälliger Menüpunkt: „Abbilddatei erzeugen“:

Memory dump on demand

Jupp! Er macht genau was ich brauchte. Durch diesen netten Befehl wird im %TEMP% Verzeichnis des Benutzers ein voller Speicherdump erzeugt.

Ich musste von 6 Dumps genau 2 durchsehen, bis ich das Problem lokalisiert hatte.
Eine wirklich nette und nützliche Funktion des Taskmanagers.

Unter Windows XP kann man ähnliches machen nur ist es hier ungleich komplizierter, aber es geht auch mit dem mitgelieferten symbolischen Debugger NTSD und den folgenden Schritten:

  • PID über den Task-Manager ermitteln (entsprechende Spalte einblenden lassen)
  • NTSD starten mit der entsprechenden PID
    NTSD -p 4656
  • Dump erzeugen:

    0:001> .dump /f c:\temp\crash\full.dmp
    Creating c:\temp\crash\full.dmp – user full dump
  • Wird der Debugger mit Quit (q) verlassen wird auch der Prozess beendet.

Selbst reingelegt beim Test von „XP oder später“

Manche Codezeilen schreibt man ja einfach im Schlaf so in etwa wie diesen hier

static const OSVERSIONINFO &GetOSVersionInfo()
{
 static OSVERSIONINFO osVersionInfo;
 if (osVersionInfo.dwOSVersionInfoSize==0)
 {
  osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo);
  ::GetVersionEx(&osVersionInfo);
 }
 // return pointer to struct
 return osVersionInfo;
}

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
     osvi.dwMajorVersion>=5 &&
     osvi.dwMinorVersion>=1;
}

Der Sinn und Zweck ist eindeutig. Ich benötige diese Funktion um zu Testen ob Windows XP oder ein späteres OS wie Vista oder Windows 7 installiert ist. Dumm nur das dieser Code dämlich falsch ist.
Die Betriebssysteme haben die folgenden internen Versionsnummern
5.0 – Windows 2000
5.1 – Windows XP
5.2 – Windows Server 2003
6.0 – Windows Vista
6.1 – Windows 7 (Anmerkung: idotisch, dass hier nicht 7.0 verwendet wird)

Als ich den Code schrieb war Windows XP gerade draußen und selbst Windows Server 2003 gerade am Horizont. Dämlicherweise schrieb ich in dem Test osvi.dwMajorVersion>=5 && osvi.dwMinorVersion>=1.
ohne natürlich daran zu denken, dass ein späteres OS wieder mit einer 0 als minor Version kommen könnte.
Dadurch ergibt sich natürlich das der Test für alle Betriebssysteme nach Windows XP funktioniert nur nicht für Windows Vista  weil eben die Minor Version hier 0 ist. Ich war drauf und dran mal wieder einen Bug einzureichen bis mir schlagartig klar war, dass nicht Vista einen Fehler hat sondern mein eigener Code.

Der korrekte Test muss natürlich so lauten:

bool OSIsWinXP()
{
 // Check if OS is XP or later
 const OSVERSIONINFO &osvi= GetOSVersionInfo();
 return (osvi.dwPlatformId & VER_PLATFORM_WIN32_NT)!=0 &&
   (osvi.dwMajorVersion>5 ||    // May be vista or later
       (osvi.dwMajorVersion==5 &&    // that's XP
        osvi.dwMinorVersion>=1));
}

Code wie im Schlaf zu schreiben bringt es manchmal eben nicht. 😉

Tooltips und Customdraw

Customdraw ist für mich erste Wahl, wenn es um das Anpassen von Ausgaben in Controls geht, zudem ein subclassing von WM_PAINT mit Erhalt der Grundfunktionen eigentlich nicht möglich ist (ich werde dazu demnächst noch mal schreiben).

Liest man die Anleitung zu NM_CUSTOMDRAW und Tooltips, bekommt man den Eindruck, dass man wie bei einem List Control an jeder Stelle eingreifen kann. Vor dem Zeichnen, nach Löschen des Hintergrundes und so weiter: CDRF_NOTIFYITEMDRAW, CDRF_NOTIFYPOSTERASE, CDRF_NOTIFYPOSTPAINT, CDRF_NOTIFYSUBITEMDRAW werden in der Doku erwähnt.

Diese Informationen sind komplett irreführend, denn nur CDRF_NOTIFYPOSTPAINT wird vom Tooltip akzeptiert und beachtet. Man kann z.B. nicht auf die Ausgabe des Textes alleine übernehmen und nur das Löschen des Hintergrundes dem Control überlassen. Es wird wirklich nur CDRF_NOTIFYPOSTPAINT berücksichtigt ( ich habe mich durch den Assembler-Code der COMCTL32.DLL durch gedebuggt).
Auch die Rückgabe von CDRF_NEWFONT kann man sich sparen. Man muss nur einen neuen Font selektieren und er wird berücksichtigt.

Was in der Dokumentation auch zu kurz kommt, ist, dass NM_CUSTOMDRAW mit CDDS_PREPAINT zweimal kurz hintereinander aufgerufen wird. Beim ersten Mal ist DT_CALCRECT in NMTTCUSTOMDRAW::uDrawFlags gesetzt ist und beim zweiten mal nicht mehr. Man hat dadurch die Möglichkeit die Größe des Controls mit NMCUSTOMDRAW::rc zu kontrollieren. Gut beschrieben ist das nicht in der MSDN sondern in einem alten Artikel des MSJ aus dem Otkober 1996, wer weiß wann der verschwinden wird.

Die Implementierung hier ist einfach halbherzig, leider. Die Möglichkeiten wären so genial, hier ein bisschen Fettgedrucktes, dort noch mal ein Icon… Schade…

Slow DrawText performance in Vista and Windows 7. Please comment…

Sorry for blogging in English. I know that I am breaking my own rules 😉

Reasons for this article:
In the last years I found it more an more complicated to get bugs fixed in current software produced by Microsoft. My experience showed me, that the Microsoft product teams always look for new versions and new features and the Microsoft support teams and engineers don’t produce enough pressure that well known bugs get fixed in the currently used software products.

The reason why the Microsoft support rejected a request for a fix was very often that not enough customers are affected by the problem or bug. And this is often a problem for me and my company. We are just a small ISV so we can’t point to millions of affected users. And yes I know that hotfixes are expensive.
So usually we have to be flexible and have to find work arrounds.
But my feeling is that there would be enough impact and pressure for a hotfix, if it would be known to the public what problems exist and that there are others who have a request to get a special problem fixed.
Also a lot of programmers tend to avoid the creation of a support request, because the experience  shows: You put energy, time (and money) into a support request and the answers are in most cases: Will not get fixed, by design, and so on…

Another experience in the last years was, that even being an MVP doesn’t help to get anything fixed by Microsoft even if a lot of MVPs point out that there is a real need for a change or fix. 

So I just want to choose a new way, because there is no way to get votes of other people to a standard bug in a Microsoft product like it is possible on Microsoft Connect. But Connect is absolutely worthless if the product is already shipped!
I want to collect votes for this problem via my blog. I also informed the Microsoft support engineer who cares about the current incident.

If you agree and if you see a larger problem in this bug too, or if you have the feeling or knowledge that it has impact on your own software, than please write a comment to this blog article. Please leave your real email address or homepage URL. The email address will not be visible to the public, and I will forward it only to Microsoft if the poster agrees.

Description of the bug:

On January 17th, 2009 I reported a reproducible bug in Windows Vista in the article DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!  (The article is written in German, you find a description in English below in the next lines)

Fact:
Windows Vista and Window 7 are up to 50 times slower in the execution of a simple DrawText function with DT_CALCRECT set compared to Windows XP.
You can find the source code for a sample code (VS-2005 MFC project) that shows the bug here:  TestDrawText.zip. If you are just interested in the executable you can find it here TestDrawTextExe.zip.

And as far as I can see the overall performance of DrawText is affected.

My request:
If you find it important that this need a fix, or if you have the feeling or the knowledge, that you software might be effected. Please leave a comment here ❗

Or feel free to vote on the bug I filed for Windows 7 on Microsoft Connect:
https://connect.microsoft.com/windows7/feedback/ViewFeedback.aspx?FeedbackID=401007

Thank you

Final Note (11.07.2009):
Read on here
http://blog.m-ri.de/index.php/2009/07/11/ausloesung-drawtext-unter-vista-gegenueber-xp-um-bis-zu-faktor-50-langsamer/