Wie man den Namen einer RegisterWindowMessage bekommt

Manchmal muss man Software verstehen. D.h. auch andere Software, die man selbst nicht geschrieben hat 😉

In meinem Fall war es hier ein Client, den ich geschrieben habe, der eine andere Software startet. Diese Software verwendete interne Nachrichten zur Kommunikation, die mit RegisterWindowMessage registriert wurden. Ich wollte nun hier einen Eingriff machen, der ein Fehlverhalten unter Windows 7 und Vista vermeiden soll.

Hilfreich wäre für mich nun gewesen an den Namen der registrierten Nachrichten zu kommen. Spy++ kann es auch und der importiert auch keine mystischen Funktionen. Also muss es einfach gehen.

Und ein wenig Recherche und ein Verweis eines Community Eintrags brachte mich auf diesen Thread:
http://groups.google.it/group/microsoft.public.vc.mfc/browse_thread/thread/f83f7c12c80e4ada/460bc4c43a844a37

Siehe da GetClipboardFormatName löst das Problem. Der nachfolgende Code lieferte mir nun im Detail, was das so hin und her läuft und der Name der Nachrichten war zum Glück sprechend. Ich konnte das Problem lösen.

if (uiMsg>=0xC000)
{
  TCHAR szName[MAX_PATH];
  ::GetClipboardFormatName(uiMsg,szName,MfxCountOf(szName));
  TCHAR szOut[MAX_PATH*2];
  _stprintf(szOut,_T(__FUNCTION__) _T(" %s, wp=0x%08x, lp=0x%08x\n"),
            szName, wParam, lParam);
  OutputDebugString(szOut);
}

VS-Tipps & Tricks: Format Specifier in den Debugger Fenstern

Beim Debuggen Variablen im Watch-Window oder im Quick-View anzeigen zu lassen ist gängige Praxis und jeder etwas fortgeschrittene Entwickler wird diese Funktionen des Visual-Studios nutzen.

Üblicherweise wählt der Debugger eine Darstellungsform, die für die Variable geeignet ist. Besonders für STL Datentypen hat sich hier einiges getan seit VC-2005.

Dennoch kann man dem Debugger für manche Datentypen noch einen Format Specifier mitgeben, der einem die Arbeit beim Debuggen extrem erleichert.
Format Specifier erlauben es eine Variable entsprechend Ihrer Verwendung zu interpretieren. Typisch hier wäre eine Windows Nachricht. Als Integer sagt einem 0x0129 nicht viel, aber WM_NCCREATE einiges. Wenn man hinter die Variable nMsg im Watch-Fenster einfach aus nMsg,wm erweitert erhält man sofort die Nachricht als symbolischen Wert angezeigt.

Ich will hier nicht alle aber wenigstens ein paar sehr nützliche und weniger bekannte Format Specifier aufzählen:

! – Raw format
hr – HRESULT in Klartext
su -Unicode
s8 – UTF8
wm – Windowsnachricht
wc – Fensterstil
<n> – Anzahl der Arrayelemente

Am schönsten sieht man die Wirkung an dem folgenden Code und den nachfolgenden Bildern der Watch-Windows:

int g_ai[] =
{
  4711,
  815,
  1234
};

int _tmain(int argc, _TCHAR* argv[])
{
  std::list lst;
  lst.push_back(1);
  lst.push_back(2);
  DWORD dwHResult = 2147943623;
  void *szUnicode = L"Unicode ÄÖÜäöü";
  char *szUTF8Code = "Umlaute AE=\xc3\x84 OE=\xc3\x96 UE=\xc3\x9c.";
  UINT winmsg = 125;
  DWORD winstyle = 0xA6730000;
  int *pi = g_ai;

  DebugBreak();
  return 0;
}

Hier das Ganze die Daten im Watchwindow ohne Formatspecifier:

Watch1

Hier das Ganze mit:

Watch2

Weitere Links dazu:
http://msdn.microsoft.com/en-us/library/75w45ekt.aspx
http://blogs.msdn.com/vcblog/archive/2006/08/04/689026.aspx

Crash durch unsachgemäßen Umgang mit PreTranslateMessage

Neulich bei einem Codereview, lief mir Code in den beiden nachfolgenden Formen über meinen Monitor:

BOOL CMyDialog::PreTranslateMessage(MSG *pMsg)
{
    // Translate Messages (ON_KEYDOWN -> ON_COMMAND)
    TranslateAccelerator(m_hWnd,m_hAccel,pMsg);
    // Let the ToolTip process this message.
    m_tooltip.RelayEvent(pMsg);
    return __super::PreTranslateMessage(pMsg);
}

– bzw.  –

BOOL CMyWnd::PreTranslateMessage(MSG *pMsg)
{
  BOOL bResult = __super::PreTranslateMessage(pMsg);
  DoSomething();
  return bResult;
}

Sieht OK aus, aber hier tritt ein grundsätzliches Problem auf:

In beiden Funktionen wird evtl. eine Nachricht behandelt. Allerdings wird in diesem Fall nicht umgehend die Funktion verlassen. Durch das Behandeln der Nachricht kann nämlich das Fenster/Objekt, zu dem PreTranslateMessage gehört, bereits zerstört sein. In diesem Fall kehrt PreTranslateMessage zurück und der this Zeiger ist bereits ungültig.

Es ist also imminent wichtig in dem Moment in dem erkannt wird, dass die Nachricht behandelt wurde, auch umgehend die Funktion mit TRUE zu verlassen und keine weitere Memberfunktion oder gar Membervariable mehr zu nutzen. Beides könnte zu einem üblen Crash führen.

Der korrekte Code sähe also so aus:

BOOL CMyDialog::PreTranslateMessage(MSG *pMsg)
{
  // Translate Messages (ON_KEYDOWN -> ON_COMMAND)
  if (TranslateAccelerator(m_hWnd,m_hAccel,pMsg))
    return TRUE;
  // Let the ToolTip process this message.
  m_tooltip.RelayEvent(pMsg);
  return __super::PreTranslateMessage(pMsg);
}

– oder –

BOOL CMyWnd::PreTranslateMessage(MSG *pMsg)
{
  if (__super::PreTranslateMessage(pMsg))
    return TRUE;
  DoSomething();
  return FALSE;
}

PS: Beide Codeteile wurden durch Crashdumps aus WinQual gefunden. Regelmäßig, alle 2 Monate schaue ich mir Dumps an, von den Top-Crashes, die dort verzeichnet sind, und mache entsprechende Code-Reviews.
Der erste Code, stammte aus einem speziellen nicht modalen Dialog, der durch Drücken bestimmter Tastenkombinationen geschlossen und zerstört wurde.
Der zweite Code stammte aus einem Popup-Fenster, dass auch durch Mausaktivitäten oder Tastendrücke zerstört wurde.
Ich kann jedem nur raten WinQual auch zu nutzen, es dient der Qualtitätssicherung und man findet viele kleine Bugs, die manchen User ärgern, die aber nie sonst gemeldet würden.

VS-Tipps & Tricks: Direkter Break in den Debugger bei einem ASSERT

ASSERTs in der MFC und in der CRT sind tolle Hilfsmittel, aber nicht selten verfälschen sie auch das Problem alleine dadurch, dass ein Fenster aufpoppt, wenn der ASSERT zuschlägt. Hat man nun einen Code, der in einem Tooltipp etwas Böses macht, dann wird der Tooltipp selbst aber schon wieder durch das erscheinen der ASSERT Meldung zerstört. Oder es wird ein neuer ASSERT ausgelöst. Der Callstack wird dadurch oft schwer zu lesen.
Besonders heikel kann dies auch noch werden wenn man mehrere Threads hat. Gleichfalls problematisch ist, dass in dem Moment in dem die ASSERT Box auftaucht nun auch wieder alle Timer weiterlaufen und sehr eigentümliche Seiteneffekte weiter auslösen können, dito. Probleme in WM_PAINT Handlern, denn auch die lösen evtl. schon wieder Aktionen aus, die Variablen verändern.

Nett ist am ASSERT-Dialog natürlich die Möglichkeit Ignorieren zu sagen und das Programm weiter laufen zu lassen. Ganz besonders wenn man Debug Versionen im Testfeld mit Anwendern testet.

Dennoch bin ich bei Debug-Versionen dazu übergegangen ASSERTs direkt  crashen zu lassen, bzw. direkt einen Debug-Break auszulösen. Das erleichtert das Lesen des Crashdumps bzw. hilft auch beim Debuggen, weil man direkt an der Stelle steht wo es hakt und alle Fenster und Variableninhalte exakt noch so sind, wie Sie es beim Auftreten des Problems waren (Tooltips, Popups, Menüs etc.).

Der Code um das zu erreichen ist relativ simpel. Man verwendet dazu _CrtSetReportHook2. In dem Hook sagt man einfach was man gerne hätte. Nämlich bei einem ASSERT oder ERROR keinen Dialog sondern einen Break (INT3).

#ifdef _DEBUG
int __cdecl DebugReportHook(int nReportType, char* , int* pnRet)
{
  // Stop if no debugger is loaded and do not assert, cause a crash
  // - returning TRUE indicates that we handled the problem, so no other hook
  //   needs to perform any action
  // - setting the target of *pnRet to TRUE indicates that the CRT should
  //   execute an INT3 and should crash or break into the debugger.
  return *pnRet = nReportType==_CRT_ASSERT ||
                  nReportType==_CRT_ERROR ?
                            TRUE : FALSE;
}
#endif

void SetBreakOnAssert(BOOL bBreakOnAssert/* =FALSE */)
{  
// Need to disable the ASSERT handler?
#ifdef _DEBUG  
  if (bBreakOnAssert)   
    _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, DebugReportHook); 
  else   
    _CrtSetReportHook2(_CRT_RPTHOOK_REMOVE, DebugReportHook);
#else
  UNUSED_ALWAYS(bBreakOnAssert);
#endif
}

Durch diese kleine Funktion SetBreakOnAssert kann man dieses Verhalten nun einfach ein- und ausschalten. Nähere Details stehen im Kommentar der Hook-Funktion.

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.

Tipps & Tricks: Wie man einen permanenten ASSERT direkt mit einem Kommentar versieht

Ich verwende gerne ASSERT’s in meinem Code.
Sie sind ein wirksames Mittel Zustände abzufragen und bereist in der Testphase unzulässige Konstellationen oder Funktionsaufrufe zu entdecken.

Nun gibt es ja auch if, else oder switch Blöcke an denen das Programm normalerweise nicht vorbei kommen sollte. So eine Stelle versieht man dann schon mal mit einem

_ASSERT( FALSE );
// oder wer die MFC benutzt eben ASSERT,
// obwohl dies auch nur ein Synonym für den CRT _ASSERT makro ist
ASSERT(FALSE);

Jetzt müsste man noch einen Kommentar davor setzen, damit klar wird was hier schief läuft. Man kann das Ganze aber auch einfach kombinieren und noch einen Zusatznutzen erreichen indem man den etwas unbekannteren Makro _ASSERTE verwendet:

_ASSERTE( !"MyFuncFooHandler: This is not allowed in my special Foo Handler" );

Die Negation macht aus dem Zeiger auf den konstanten String FALSE, und damit schlägt der ASSERT an.
Wenn man jetzt wie hier gezeigt noch den _ASSERTE Makro verwendet, dann wird diese Expression, also der Text, sofort mit anzeigt. Man sieht dann sofort was das Problem ist sobald der ASSERT Dialog angezeigt wird.

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. 😉

DrawText unter Vista gegenüber XP um bis zu Faktor 50 langsamer!

❗ Ja Ihr habt ganz richtig gelesen und ich irre mich nicht ❗

Wir haben in einem unserer C++/MFC Programmen eine komplexe Anzeige von Reports, die alle unterschiedliche Zeilenhöhe haben und in einem speziellen Fenster angezeigt werden (eigene Entwicklung).
Nun stellten wir fest, dass das Rollen in diesem Programmteil auf Vista-Rechnern mit viel Nachlauf funktionierte und auch einige andere Operationen länger als gewohnt dauerten. Bei meinem Suchen, Profilen und Testen kam ich auf ein sehr lahmes Abarbeiten der Funktion DrawText  mit DT_CALCRECT.

Um das Problem zu isolieren schrieb ich ein kleines Testprogramm, das erstaunliches zu Tage brachte.
Hier die Ergebnisse von 4 verschiedenen Rechnern (Durchlauf von 100×100 DrawText Calls):

  • Pentium Quad-Core Q8200 mit Vista: 48.111 msec
  • Pentium Core2Duo T2310 mit Vista: 78.953 msec
  • Pentium Dual Core 3,2Ghz mit XP: 1.516 msec
  • Alter Pentium 3,0Ghz HT mit XP: 1.922 msec

😯 Der Code ist teilweise um den Faktor 50 langsamer auf Windows-Vista im Vergleich zu Windows-XP

Wer Lust hat es selbst zu testen, der findet hier das Beispielprogramm im Sourcecode (VS-2005):  TestDrawText.zip
Wer das Programm als Exe möchte findet es hier: TestDrawTextExe.zip

Beschreibung:
Dieses Beispielprogramm führt gemäß einem angegebenen Zähler je 100mal DrawText mit DT_CALCRECT für einen längeren mehrzeiligen Text aus. Hierbei werden immer zufällige Textlängen verwendet. Der Algorithmus liefert jedoch immer die gleiche Zufallssequenz, damit der Test auch Vergleichbar bleibt.
Der Zufallsmechanismus wurde nur eingebaut, um für für meinen Fall realitätsnahe Daten zu liefern.

Anmerkung:
Eine entsprechende Supportanfrage bei Microsoft läuft ❗
Was dabei herauskommt werde ich berichten…

Zuletzt angemerkt:
Windows 7 soll ja schneller sein als Windows Vista. Wer weiß? Ich habe es noch nicht getestet.
Vieleicht haben die nur diesen Bug gefixed und einen gigantischen Performanceschub erhalten :mrgreen: … SCNR…

Nachtrag (11.07.2009): 
Die Auiflösung zu diesem Problem findet sich hier in diesem Artikel 
http://blog.m-ri.de/index.php/2009/07/11/ausloesung-drawtext-unter-vista-gegenueber-xp-um-bis-zu-faktor-50-langsamer/