VS Tipps & Tricks: Fehler in Custom Build Steps ermitteln

Manchmal macht man ja schon komplexere Sachen in den Custom-Build-Steps, wie zum Beispiel auch das Erzeugen der HTML-Help Include Datei. Aber es geht ja noch viel komplizierter, zumindest benutze ich solche komplexen Custom-Build Funktionen ziemlich häufig!

Was nun, wenn man eine Fehlermeldung erhält in solch einem Custom-Build-Step, aber gar nicht weiß wo der Batch eigentlich aussteigt, oder was er gerade getan hat ❓ Besonders wenn die Ausgabe im Build-Fenster so informativ ist wie:
Das System kann den angegebenen Pfad nicht finden.

Das eigentliche Problem ist, dass die Batch-Zeilen bei der Ausführung nicht angezeigt werden.
Man könnte meinen, dass Visual Studio dies mit einem mystischen Trick macht. Sieht man sich jedoch die Build-Log Datei an, so sieht man, dass vor einen Custom-Build-Step einfach immer ein @echo off eingebaut wird.
Was spricht eigentlich dagegen als erste Zeile ein @echo on in den Custom-Build einzusetzen? Nichts!

Gesagt getan!
Die Folge: Jede Zeile des Batches, der im Custom-Build durchlaufen wird, erscheint brav in der Build-Ausgabe. Einen Fehler zu finden, bei man z.B. einen Pfad falsch mit Makros zusammengebaut hat ist nun denkbar einfach.

Microsoft Visual Studio 2008 Service Pack 1 Release Candidate wurde veröffentlicht

Seit dem 06.07.2008 steht für Visual Studio 2008 auf Microsoft Connect ein Download für den Release Candidate des Microsoft Visual Studio 2008 Service Pack 1 zur Verfügung.

Dann kann man ja hoffen, das es bis zur Freigabe des SP1 nicht mehr lange dauert… 😉

Anmerkung: Es ist möglich, dass dieser Download nur Teilnehmern des SP1 Beta Programms zur Verfügung steht.

MFC Fenster in anderen Applikationen verwenden…

Manch ein Programmierer kommt auf die Idee und entwickelt ein Plugin mit der MFC für ein anderes Programm. Dass Plugin oder die entsprechende Funktionalität soll alleine in einer Standard-DLL leben. Die Schnittstelle wird bewusst schmal und einfach gehalten. Manche dieser kleinen Tools müssen/wollen nun auch ein Fenster nicht modal anzeigen.
Kein Problem, einfach CWnd::Create, oder CDialog::CreateIndirect und schon hat man sein Fenster.

Eines muss an dieser Stelle klar sein ❗
Man verlässt sich in einem solchen Fall, dass die hostende Anwendung eine Messageloop bereitstellt. Andernfalls bekommt das Fenster keine Nachrichten. OK!

Was einem aber auch klar sein muss in diesem Fall ❗
Man kann PreTranslateMessage nicht mehr verwenden. Das würde nur gehen, wenn man eine Extension DLL hat, oder die hostende Anwendung als auch die DLL beide die MFC als Shared DLL in derselben Version verwenden.
Damit ist nun auch verbunden, dass evtl. Tooltips in diesem Fenster ein sehr eigenwilliges Leben führen werden.

Es gibt einfach keinen vernünftigen Weg sich in die Messageloop eines Hosts auf einfache und vernünftige Art einzuklinken.
Ein Ansatz an dieser Stelle wäre sicherlich ein entsprechender WH_GETMESSAGE Hook. Denkbar wäre dann die entsprechende Nachricht abzufangen und an die eigenen Fenster via PreTranslateMessage anzubieten, sofern eben die eigenen Fenster den Focus haben oder die Nachricht für eines dieser eigenen Fenster oder Kindfenster bestimmt sind. Eben genauso wie es die MFC in seiner Messageloop macht. Dieses Verfahren wird auch für ActiveX Controls in der MSDN im KB Artikel 194292 empfohlen.

Siehe auch Wann Message Reflection nicht funktioniert!

Tipps & Tricks: MFC Command Routing in komplexen UIs

UIs werden immer komplexer. Und manche UI passt sicher nicht mehr in ein simples und einfaches Frame/Document/View Modell. Oft genug wird hier noch ein Tool-Window gedockt, dort noch ein Ausgabefenster und hier noch ein nicht modaler Dialog mit Status Infos.

Bringt man nun in einem solchen Fenster Buttons oder Toolbars unter, dann wünscht man sich in manchen Fällen, dass ein Command-Handler ausgelöst werden soll, der in einem anderen Objekt (z.B. dem aktiven View) liegt.
Oder man möchte, dass eben auch in solch einem Fenster ein Command-Handler berücksichtigt wird.
Der Ansatz andere Fenster in das Command Routing einzubauen ist die Funktion CCmdTarget::OnCmdMsg. Diese Methode hat zwei Funktionen.

  1. Sie dient dazu, dass in Menüs (auch Kontextmenüs) und auch in Toolbars Menüpunkte und Buttons ein- und auszuschalten. (ON_UPDATE_COMMAND Handler)
  2. Sie leitet die eigentliche WM_COMMAND Nachricht zur Ausführung an den entsprechenden zuständigen Handler weiter. (ON_COMMAND Handler).

Zum besseren Verständnis des Command Routings das die MFC als Standard vorsieht empfehle ich als Lektüre TN021. Um abenteuerliche Konstrukte und üble Verbiegungen der guten Vorgaben in der MFC zu vermeiden sollten die Grundlagen des Routings in Fleisch und Blut übergegangen sein.
Und dann ist es an sich nicht schwer, Anpassungen vorzunehmen, die es wirklich einfach machen auch andere Fenster in das eigene Routing aufzunehmen ohne die MFC zu verbiegen.

Nun zum Eingemachten:
Will man also Handler in anderen assoziierten Fenstern nutzen, dann muss man diese Fenster in die eigene angepasste OnCmdMsg Routing Struktur einbauen. Der beste Punkt dafür ist in vielen Fällen das hostende CMainFrame. Der Grund ist einfach: Das CMainFrame ist in 99% aller Fälle die erste Adresse, an die ein WM_COMMAND ausgeliefert wird.

Eine entsprechende Funktion kann so aussehen:

BOOL CMainFrame::OnCmdMsg(UINT nID,int nCode,void* pExtra,AFX_CMDHANDLERINFO* pHandlerInfo)
{
    // Give a special window the first chance to handle the command
    if (m_pSomeWindow->GetSafeHwnd() &&
        m_pSomeWindow->IsWindowVisible() &&
        m_pSomeWindow->IsActiveInSomeWayOrHasFocus())
    {
        if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
            return TRUE;
    }

    // Do the standard routing (View, Frame, Application)
    if (__super::OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
        return TRUE;

    // If not handled up to this point just give another window a chance
    if (m_pSomeOtherWindow->GetSafeHwnd() &&
        m_pSomeOtherWindow->IsWindowVisible() &&
        m_pSomeOtherWindow->IsActiveInSomeWayOrHasFocus())
    {
        if (m_pSomeWindow->OnCmdMsg(nID,nCode,pExtra,pHandlerInfo))
           return TRUE;
    }

    // not handled
    return FALSE;
}

Anmerkungen:

  • Meistens ist es bei diesen Fällen wichtig, dass Routing nicht immer durchzuführen. Z.B. nur dann, wenn das Fenster sichtbar ist, den Fokus hat oder ähnliches (siehe Beispielcode).
  • Weiterhin ist es für Kontextmenüs wichtig als Parent-Fenster ein von CFrameWnd abgeleitetes Fenster zu verwenden. Geschieht dies nicht, dann wird WM_INITMENUPOPUP nicht behandelt, und die Menüpunkte werden nicht enabled bzw. disabled. Wird das obige Verfahren mit OnCmdMsg korrekt angewendet, kann man als Parent Fenster für Kontextmnüs immer AfxGetMainWnd verwenden.
  • Toolbars funktionieren nur dann korrekt wenn hier der korrekte Owner gesetzt wird. Das geschieht mit CToolBar::SetOwner. Auch hier sollte das äußere Frame Window die WM_COMMAND Nachrichten erhalten, die dann über den normalen weiteren (angepassten) Weg geroutet werden.

Siehe auch Command Routing der MFC bei Kontext Menüs mit TrackPopupMenu  

Rätselhafte Auswahl bei Microsoft…

Immer wieder staune ich über die rätselhafte Auswahl von Funktionen, die es als Wrapper in die MFC geschafft haben. Warum werden nicht alle Funktionen in die MFC Wrapper aufgenommen?

CImageList::GetIconSize sucht man vergeblich, man muss die API Funktion ImageList_GetIconSize verwenden… OK, wenn diese Funktion nicht in allen Windows Versionen verfügbar wäre. Aber gerade diese Funktion existiert schon seit der aller ersten COMCTL32 Version!

Man könnte meinen, dass mit der Zeit fehlende Funktionen irgendwann mal in neueren Funktionen nachgerüstet werden, aber da hofft man scheinbar vergebens.
Immer wieder verwirrt mich das. 😕

Die Unsitte GetAsyncKeyState statt GeyKeyState zu verwenden…

Immer wieder wird in Foren gefragt, wie man  feststellen kann, ob z.B. die Umschalt- oder Strg-Taste gedrückt ist.
Nur zu oft liest man als Antwort: Nimm GetAsyncKeyState ❗

Zwei Dinge stören mich an dieser Antwort:

  1. Dem Frager ist meistens gar nicht bewusst, dass es für die entsprechenden Tasteneingaben immer auch eine entsprechende Nachricht WM_KEY… Nachricht gibt.
    Hier wäre ein Grundstudium der Windows API angesagt!
  2. Ist den meisten Fragern (und Antwortenden) nicht klar, das Nachrichten chronologisch einlaufen aber GetAsyncKeyState den aktuellen Zustand der Taste zurückgibt, eben asynchron, wie der Name es schon andeutet.

In den meisten Fällen ist diese Antwort also falsch!

Im Detail will ich das auch erklären:
GetAsyncKeyState liefert den Zustand der Umschalt-Taste zu dem Zeitpunkt an dem die Funktion aufgerufen wird. Im Gegensatz dazu liefert GetKeyState den Zustand der Taste zu der Zeit, als die Nachricht die aktuell bearbeitet wird einlief, bzw. erzeugt wurde. (Genau genommen geht es hier um die letzte Windows-Nachricht)
Das Problem wird offensichtlich, wenn das OS durch hohe Prozessorauslastung Nachrichten etwas verzögert abarbeitet. Es ist klar, dass GetAsyncKeyState zu falschen Ergebnissen führen muss, bzw. ein evtl. gedrückte Strg-Taste evtl. nicht beachtet wird. Die Folge könnte sein, dass aus einer Kopieraktion, ein Verschieben wird. Fatal für den Benutzer.

Eigentlich fällt mir gar kein vernünftiger Grund ein GetAsyncKeyState zu verwenden! Außer man pollt, und pollen ist das Letzte was ich in eine Windows Applikation machen wollte.

Vermutlich hat irgendwann irgendein etwas unterbemittelter Programmierer angefangen einen Tipp mit GetAsyncKeyState zu geben und dieser Unsinn kursiert nun auf ewig durch die Netze…

In welcher Reihenfolge werden Ressourcen in unterschiedlichen Sprachen gefunden?

Immer wieder taucht die Frage auf, wie die Funktionen zum Laden von Ressourcen (LoadImage, LoadCursor, LoadMenu etc.) eigentlich arbeiten und entscheiden aus welcher LANGID-Sektion die Ressource geladen wird.

Die Dokumentation von FindResource – auf der alle diese Funktionen basieren – gibt leider keine Auskunft.

Wenn man jedoch etwas sucht wird man in der Dokumentation von Developing International Software fündig. Dort findet sich der folgende Abschnitt mit dem Titel Multiple Language Resources, in dem auch beschrieben wird wie FindResource arbeitet und wie eine Ressource gesucht wird:

If the FindResource and FindResourceEx functions do not find any resources that match the language ID’s primary language, they search for resources tagged as „language-neutral.“ This language ID is useful for resource elements such as icons or cursors that are identical for all languages. If a bitmap or an icon will differ for some languages, you can define one language-neutral bitmap as the default and specify language IDs for as many other customized bitmaps as required. For example, bidirectional applications might require bitmaps with right-to-left directionality. Because the FindResource and FindResourceEx functions always search for specific language IDs first, they will always find a bitmap tagged with that language ID before they find one tagged as language-neutral. The search algorithm they follow is summarized in the following list:

1. Primary language/sublanguage
2. Primary language
3. Language-neutral
4. English (skipped if primary language is English)
5. Any

Videos für das C++ Community Event vom 17.04 in Bad Homburg statt

Wie schon angekündigt sind nun die Videos des Community Events in Bad Homburg veröffentlicht worden.
Boris Jabes aus Redmond hatte uns Einblicke aus dem VC++ Featurepack in die MFCNext, die neuen Marshalling Features und TR1 gegeben und nicht zu vergessen einige Ausblicke auf VC++ 10.

Alles findet sich auf dem Blog von Christian Binder, der das Ganze mit Dariusz Parys gefilmt hat:

http://blogs.msdn.com/cbinder/archive/2008/05/27/videos-das-neue-c-feature-pack-und-vsts-f-r-native-c-developer.aspx

VS Tipps & Tricks: Goto Dialog im Class View

Eventuell ist dies auch gar kein Tipp für Euch, weil Ihr diese Funktion schon läääängst kennt. In diesem Fall ist dieser Artikel die Erkenntnis, dass ich (selbst nach Jahren intensiver Arbeit) immer wieder Funktionen entdecke, die ich gerne früher gekannt hätte ;). In diesem Fall habe ich ihn erst Anfang dieser Woche entdeckt.

Es ist eine Funktion, die nur MFC/ATL Entwickler nutzen können (oder Ihrer bedürfen):

  • Man hat ein MFC Projekt
  • Wähle im Classview eine von CDialog abgeleitete Klasse
  • Dann öffnet man das Kontextmenü
  • und findet den Befehl Goto Dialog

Führt man diesen Befehl aus, öffnet sich der Ressourcen-Editor mit dem entsprechenden Dialog.

Schön, dass es diese Funktion gibt, denn IDD_’s heißen oft genug so anders als die Klassen, dass es bei manchen großen Projekten immer erst eines Blickes in die Header Datei bedarf um den richtigen Dialog zu finden.

Wunderbar! Hätte ich diesen Befehl zuvor gekannt, dann hätte In den letzten Jahren bestimmt 10 Minuten Zeit sparen können, in denen ich verzweifelt Dialoge in den Ressourcen suche… 😉

CB_ADDSTRING/CB_INSERTSTRING+CBS_UPPERCASE+Unicode+const String Pointer == das große Erstaunen

Es gibt immer wieder Momente in denen einen die Win32 API in größtes Erstaunen versetzt.
Meistens ist dies allerdings in diesen Momenten nichts positives. Das zweite Erstaunen folgt, dann wenn man in der MSDN nachliest und das Verhalten  als dokumentiert vorfindet. Meistens endet solch eine Kette dann in einem Kopfschütteln und dem Gedanken: Das darf doch gar nicht sein.

Genug der langen Vorworte:

  • Gegeben ein normales MFC Projekt. (WinAPI pur tut es auch 😉 )
  • Ein Dialog
  • Darin eine Combobox als Dropdown mit Eingabemöglichkeit
  • Die Combobox wird mit einigen Variablen Werten gefüllt mit CComboBox::Addstring/InsertString, was letzten Endes nur ein Wrapper für CB_ADDSTRING/CB_INSERTSTRING ist.
  • Das ganze sieht also in OnInitDialog in etwa wie folgt aus
cb.AddString(CString(MAKEINTRESOURCE(IDS_DATA1)));
cb.AddString(CString(MAKEINTRESOURCE(IDS_DATA2)));
cb.AddString(CString(MAKEINTRESOURCE(IDS_DATA3)));
cb.AddString(strSomeDynamicData);
cb.InsertString(0,_T(""));

Alles OK… Als letztes noch den Stil CBS_UPPERCASE dazu – weil hier eben nur Eingaben in Großbuchstaben erlaubt sein sollen und Sinn machen – und… Peng!
Das Programm schmiert ab. 😮

Was ged’n hier ab Alder ❓

In den tiefen des Aufrufs von cb.InsertString(0,_T(„“)); schmiert mein Programm ab. UAE
Nun aber doch großes Erstaunen, denn der selbe Code funktioniert in einem MBCS Programm.
Es liegt eindeutig an der Nutzung von CBS_UPPERCASE und Unicode.

Jetzt habe ich schon gedacht einen Bug in Vista und XP gefunden zu haben, denn auf beiden schmiert bersagter Code ab. Und wahrscheinlich ist es auch ein Bug! Aber das genauere Nachlesen der MSDN belehrt mich eines besseren. Dieses dämliche Verhalten ist dokumentiert, zumindest für CB_ADDSTRING (der Zusatz fehlt in der CB_INSERTSTRING Doku):

Comclt32.dll version 5.0 or later: If CBS_LOWERCASE or CBS_UPPERCASE is set, the Unicode version of CB_ADDSTRING alters the string. If using read-only global memory, this causes the application to fail. ❗

Unfassbar! Die Nachricht ist dabei selbst so beschrieben, wie es sich jeder vernünftige Entwickler auch denkt und vor allem erwartet, eben mit einem LPCTSTR:

lResult = SendMessage(      // returns LRESULT in lResult
     (HWND) hWndControl,    // handle to destination control
     (UINT) CB_ADDSTRING,  // message ID
     (WPARAM) wParam,      // = 0; not used, must be zero
     (LPARAM) lParam       // = (LPARAM) (LPCTSTR) lParam;
);

Aber was nützt schon ein vernünftiger Gedanke eines Entwicklers wenn es um das irrwitzige Eigenleben der Win32 API geht.
Und denke nie einen Bug gefunden zu haben, bevor Du nicht jede Zeile der MSDN studiert hast 😉