Wann Message Reflection nicht funktioniert!

TN062: Message Reflection for Windows Controls beschreibt die vielen schönen Möglichkeiten der Message Reflection.

Aber es gibt eine wichtige Voraussetzung, dass da ganze funktioniert:
Das Parent Window muss auch ein Fenster sein, das mit der MFC erzeugt wurde, oder durch die MFC gesubclassed sein.

Die Reflektion geschieht immer nur durch das Parent Fenster. Wenn dort also eine WM_CTLCOLOR Nachricht das Parent erreicht, dann wird die Nachricht an das Fenster, dass es gesendet weitergeleitet, wenn es eben über einen entsprechenden Reflektion Eintrag für WM_CTLCOLOR verfügt. Das ganze wird erledigt durch die Funktion CWnd::ReflectLastMessage, die die entsprechenden Funktion  CWnd::SendChildNotifyLastMsg im eigentlichen Control wieder aufruft. Diese Funktion ruft wiederum CWnd::OnChildNotify auf, die dann die Reflection Einträge in der Message Map sucht. Ist das Parent nun kein MFC Fenster kann auch niemand die Nachrichten an das sende Fenster zurückleiten.

Hat man also ein Fenster hat, dass durch pure Win32 API Funktionen erzeugt wurde und auch nicht durch die MFC gesubclassed wurde und in diesem Fenster wird ein MFC Fenster als Child erzeugt, dann erreichen dieses Child keine Nachrichten durch die Reflektion.

Command Routing der MFC bei Kontext Menüs mit TrackPopupMenu

Kontext Menüs  gehören zum State of  the Art.

Es ist auch nicht weiter schwer ein Menü zu laden ünd per TrackPopupMenu auf den Bildschirm zu zaubern. Entsprechender Code sieht dann meistens so aus:

void CMyView::OnContextMenu(CWnd* pWnd, CPoint pt)
{
    // Get the menu
    CMenu popups;
    if (!popups.LoadMenu(_T("AGV-IDR_MACRO_EDITOR_CONTEXT")))
        AfxThrowResourceException();
    CMenu* pSubMenu= popups.GetSubMenu(0);
    ASSERT(pSubMenu);
    if (!pSubMenu)
        AfxThrowResourceException();
    pSubMenu->TrackPopupMenu(0,pt.x,pt.y,this);
}

Oft sind in dem Menü einfache IDs von Commands verwendet worden, die irgendwo im View, Dokument, Frame oder der Applikation implementiert sind. Der Programmierer wundert sich nun warum seine Menüpunkte nicht ausgegraut werden. Alle Menüpunkte für alle IDs sind aktiv. Und man wundert sich noch mehr. Manche Commands (außerhalb des Views) werden nicht ausgeführt.

Was läuft hier falsch?

❗ Die Antwort liegt einzig und alleine im Aufruf von TrackPopupMenu! Der letzte Parameter gibt an, an wen die entsprechenden Nachrichten (WM_COMMAND, WM_INITPOPUPMENU und andere) gesendet werden. Naheliegender Weise gibt der Entwickler hier das Fenster an, in dem er das Popup Menü anzeigen will. Hier liegt der Hase im Pfeffer.
Ein normales Fenster und ein View hat keine Verwendung (Handler) für WM_INITPOPUPMENU. Solch ein Handler wäre aber für das Command Routing der MFC notwendig um die Menu-Items ein- und auszuschalten.

Das CMainFrame hat solch einen Handler, und kann damit umgehen. Also einfach das CMainFrame als Zeiger übergeben und … es funktioniert wie erwartet. Warum auch nicht. Die IDs aus dem normalen Menü werden ja auch über genau den selben Weg des Command Routings behandelt.
Die entscheidende Codezeile sieht dann so aus:

pSubMenu->TrackPopupMenu(0,pt.x,pt.y,AfxGetMainWnd());

RTM für VS-2008 Orcas doch zum Ende des Jahres

Sorry für meinen etwas schnellen Blog-Eintrag:
http://blog.m-ri.de/index.php/2007/07/16/vs-2008orcas-kommt-jetzt-doch-erst-im-februar-2008/

Ich habe Launch und RTM (Release to Market) durcheinandergebracht. Sorry!

Wie es aussieht ist es doch schon Ende des Jahres soweit: 

http://blogs.msdn.com/dseven/archive/2007/07/10/windows-server-2008-visual-studio-2008-and-microsoft-sql-server-2008-joint-launch-announced.aspx

„While the launch events are scheduled to kick off on February 27, 2008, Visual Studio 2008 will be released before the end of the year.“

http://blogs.msdn.com/somasegar/archive/2007/07/13/it-all-begins-february-27th.aspx

„While we will be launching our products together in February, we are still aiming to release Visual Studio 2008 and .NET FX 3.5 by the end of this year based on your feedbackso far.“

VS-2008/Orcas kommt jetzt doch erst im Februar 2008

KORREKTUR zu diesem Artikel hier
http://blog.m-ri.de/index.php/2007/07/17/rtm-fuer-vs-2008-orcas-doch-zum-ende-des-jahres/
Ich habe Launch und RTM durcheinander gebracht. Sorry!

„VS-2008/Orcas kommt jetzt doch erst im Februar 2008“ weiterlesen

Erstaunlich: const BYTE * != const LPBYTE

Erstaunlich! Wenn man die folgende Funktion kompiliert erhält man keine Fehlermeldung: 

void foo(const LPBYTE data)
{
    *data = 0; 
}

Man könnte denken, dass diese Funktion identisch wäre zu: 

void foo(const BYTE * data)
{
    *data = 0;        // Compiler Error
}

Ist es aber nicht! Die Funktion

void foo(const LPBYTE data);

ist identisch zu

void foo(BYTE * const data);

und eben nicht zu

void foo(const BYTE * data);

Der typedef darf selbst kein Zeiger sein! Er muss ein Basis Typ sein, nur dann führt const X * zum gewünschten Effekt.
Das ist das eigentliche Problem. Das entspricht auch mit dem Sample im Kapitel 5.4 der Stroustrup „C++ Bibel“.

Siehe auch Diskussion hier:
http://groups.google.de/group/microsoft.public.de.vc/browse_thread/thread/2568531dcf1c1d7e/215ead7f070e23d0

Wie findet die MFC 8.0 eigentlich die MFC80lll.DLL Dateien?

Es ist vielleicht schon machen aufgefallen, dass auch die sprachabhängigen Ressourcen-Dateien der MFC als Side by Side Assemblies installiert werden durch VCREDIST_X86.EXE.
Schaut man sich an wie ein Programm erzeugt wird, dass die MFC80.DLL/MFC80U.DLL verwendet, dann weiß man wie Manifeste aussehen die für das entsprechende Programm erzeugt werden. Wir finden im Allgemeinen drei Dependency-Einträge für die MFC, die CRT und die ComCtl32 DLL.

Erstaunlicherweise wird kein Eintrag für die Sprachdateien erzeugt und scheinbar auch nicht benötigt. Denn wenn man das Programm im Debugger auf einem Deutschen XP oder Vista startet sieht man in der Debug-Ausgabe, dass die entsprechende Datei MFC80DEU.DLL aus dem Side by Side Storage geladen wird.

Ein Blick in die MFC80(U).DLL zeigt auch ein Manifest. Dieses passt genau auf die Sprachdateien. Allerdings ist dieses Manifest nicht aktiv, denn es hat keine „gültige“ ID (1000), die das Betriebsystem veranlassen würde es zu berücksichtigen. Zudem hätte dieses Manifest auch nur einen Effekt, wenn die Sprach-DLLs implitzit geladen würden.
Diese Dateien werden aber explizit geladen. Der Code der dafür verantwortlich ist wird zuallererst in der DllMain und der Initialisierung der MFC80-DLL und ein zweites mal in CWinApp::InitInstance aufgerufen.

Der entsprechende Code dort, lädt das Manifest aus der DLL, erzeugt einen Aktivierungs-Kontext und führt dann den entsprechenden AfxLoadLibrary Aufruf durch. Anschließend wird der Kontext wieder zerstört. Wird die Sprach-DLL nicht gefunden wird ein zweites Mal versucht die Datei nun aus dem Programmverzeichnis bzw. über die normalen Suchpfade zu laden.

Diese Methode über das eingebaute Manifest lässt sich nicht aushebeln. Verwendet man selbst eine Technik, die die MFC-DLLs als private Assemblies lädt, dann versucht die MFC dennoch die Sprach-DLLs Side by Side zu laden. Das Ganze ist etwas ärgerlich, wenn man private Assemblies pur verwenden möchte. Einziger Trick wäre hier CWinApp::LoadAppLangResourceDLL zu überschreiben, aber das verhindert nicht den ersten Aufruf aus der DllMain der MFC-DLL heraus.

❗ Wer sich das Ganze genauer ansehen will, sollte sich die Datei appcore.cpp mal ansehen.
Entscheidend hier sind die Funktionen: CWinApp::LoadAppLangResourceDLL und AfxLoadLangResourceDLLEx.

❗ Schaut man sich die MFC-DLLs übrigends noch genauer an, dann stellt man fest, dass sie gar keine Manifeste enthalten. Und das ist auch gut so, sonst wäre es ja auch nicht möglich die MFC und die zugehörige CRT als Private Assembly zu laden. Die MFC verwendet also immer die CRT, die im Kontext der EXE geladen wird.

_UNICODE versus UNICODE und so manches Eigentümliche

Wann nimmt man eigentlich was?
Jedesmal wenn ich Code ansehe finde ich mal einen

#ifdef _UNICODE

dann mal wieder einen

#ifdef UNICODE

Wann eigentlich was?
Die Frage wurde schon oft beantwortet (siehe The Old New Thing : TEXT vs. _TEXT vs. _T, and UNICODE vs. _UNICODE).

In Kurzform:
UNICODE wird in den Windows Header-Dateien verwendet.
_UNICODE in der C-Runtime (CRT).

Interessanterweise gibt es nur den Define _MBCS aber nicht den Define MBCS. Anhand der Namensgebung kann man sehen, dass _MBCS aus/für die CRT nur Einfluss hat. Die Windows API sagt einfach, wenn nicht Unicode dann ist es MBCS!

Daraus ergeben sich einige interessante Abhängigkeiten: 

_T ist abhängig vom Define _UNICODE genauso wie _TEXT (also der CRT)
TEXT ist wiederum abhängig vom Define UNICODE.

Jetzt wird es aber ganz lustig:
Die Datei tchar.h gehört zur CRT. Und TCHAR ist eigentlich kein Define aus tchar.h und der CRT. Dort wird nämlich eigentlich _TCHAR definiert und sollte TCHAR schon definiert sein (durch die windows.h), dann wird TCHAR nicht mehr umdefiniert.

Ist also nur UNICODE definiert und nicht _UNICODE, dann muss der folgende Code einen Compiler Fehler auslösen, wenn windows.h vor der tchar.h inkludiert wird. Erwartungsgemäß meldet der folgende Code einen Compiler-Fehler, obwohl dieser perfekt und korrekt aussieht:

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
int _tmain(int argc, _TCHAR* argv[])
{
 const TCHAR szText[] = „“;
  // Must fail if UNICODE is defined and not _UNICODE, and windows.h included first
 _tcslen(szText);
 return 0;
}

In VC6 gab es noch keine Projekteinstellung für Unicode. Man musste also beide Defines setzen, als Präprozessor Definition. Andernfalls hatte man eine lustige Code Mischung und lief schnell mal in ein Problem wie oben.

Der Schalter in VC-2005 und VC-2003 für

  • „Use Unicode Character Set“ setzt beide Defines _UNICODE und UNICODE
  • „Use Multi-Byte Character Set“ setzt nur _MBCS.
  • „Not set“ setzt Erwartungsgemäß keinen der Defines und man kann das obige Beispiel und den Fehler ausprobieren.

Projektintern verwende ich grundsätzlich immer intern nur den define UNICODE um zu prüfen welche Art von Projekt hier läuft. Und ich achte darauf, dass die windows.h bzw. die afxwin.h Datei immer als erstes inkludiert werden. Dann ist TCHAR entsprechend konform für die Windows API gesetzt. Die CRT ist für mich meistens nur Beiwerk und ich vermeide Ihre Verwendung eigentlich wo ich kann.

An kritischen Stellen baue ich auch gerne den folgenden Test ein um ganz auf Nummer sicher zu gehen:

#if defined(UNICODE) ^ defined(_UNICODE)
#error Inconsitent UNICODE and _UNICODE definition
#endif

Mit dem XOR-Operator wird hier ein Fehler ausgelöst wenn nur einer der beiden Defines gesetzt ist.

Siehe auch die MSDN INFO: UNICODE and _UNICODE Needed to Compile for Unicode

Der kleine Unterschied zwischen CStatic::SetIcon und CWnd::SetIcon

So schnell legt man sich rein. Da hat man ein CStatic in einem Dialog. Man besorgt sich mit GetDlgItem das entsprechende Fenster Objekt und führt SetIcon durch. Also etwa so

GetDlgItem(IDC_MYSTATIC)->SetIcon(hIcon);

Ergebnis: 🙄  Nichts passiert.

Nach etwas Debuggen wird der Fall klar. SetIcon ist nicht virtuell und wie bei allen CWnd und anderen Fenstern nichts anderes als eine Abkürzung/Wrapper für ein SendMessage. Mit dem kleinen Unterschied, dass CWnd:SetIcon WM_SETICON sendet, während CStatic::SetIcon ein STM_SETICON versendet.

GetDlgItem liefert aber (solange kein Subclass durchgeführt wird) immer einen CWnd-Zeiger. Also wird statt der erwarteten STM_SETICON Nachricht eine WM_SETICON Nachricht verwendet, die aber CStatic nicht interessiert.
SetIcon wird nur von Top-Level Fenstern verarbeitet.

BTW: Das Gleiche kann einem auch mit CButton::SetIcon passieren. Nein BM_SETICON gibt es nicht, aber es gibt die Nachricht BM_SETIMAGE, und der kann man eine Bitmap (Wrappername CButton::SetBitmap) oder eben ein Icon übergeben (Wrappername CButton::SetIcon). Auch hier lauert die gleiche Falle!

❗ Ein typischer Designfehler in einer Library! Hier wurde offensichtlich die konsistente Namensgebung in der MFC über das Verwechselungsproblem gestellt. In einer abgeleiteten Klasse sollte niemals eine Funktion gleichen Namen mit gleicher Signatur und unterschiedlicher Funktion existieren.

CFormView in einem CSplitterWnd will keine Mouse Wheel Nachrichten

Wieder mal ein netter Bug in der MFC (VC-2003 Vers. 7.1 und VC-2005 Vers. 8.0).

Platziert man ein CFormView in einem CSplitterWnd dann werden trotz sichtbarer Rollbalken, alle Mouse Wheel Nachrichten geschluckt und ignoriert.
Nachvollziehen kann man das einfach:

  • SDI Applikation im Explorer Stil erzeugen
  • Rechtes Fenster als CFormView
  • Anwendung so klein machen, dass im CFromView ein vertikaler Rollbalken erscheint.
  • Nun versuchen mit dem Mausrad zu rollen.
  • No chance…

Ursache ist der folgende Code in CScrollView:

BOOL CScrollView::OnMouseWheel(UINT fFlags, short zDelta, CPoint point)
{
 // we don’t handle anything but scrolling
 if (fFlags & (MK_SHIFT | MK_CONTROL))
  return FALSE;
 // if the parent is a splitter, it will handle the message
 if (GetParentSplitter(this, TRUE))
  return FALSE;
 // we can’t get out of it–perform the scroll ourselves
 return DoMouseWheel(fFlags, zDelta, point);
}

Wie man unschwer sieht wird bei einem Parent, dass ein CSplitterWnd ist, die Nachricht immer ignoriert. Korrekt wäre dies nur, wenn der Scrollbar nicht dem CScrollView gehört sondern dem CSplitterWnd. Das ist aber bei einem Konstrukt wie hier selten.

Workarround ist relativ simpel!
Einfach selber einen eigenen OnMouseWheel Handler erzeugen, der so aussieht:

BOOL CMyFromView::OnMouseWheel(UINT fFlags, short zDelta, CPoint point)
{
 // we don’t handle anything but scrolling
 if (fFlags & (MK_SHIFT | MK_CONTROL))
  return FALSE;
 // we can’t get out of it–perform the scroll ourselves
 return DoMouseWheel(fFlags, zDelta, point);
}

Hier der Report in connect!
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=276053

❗ PS: Das Problem tritt natürlich auch in anderen Klassen auf die von CScrollView abgeleitet werden!

Warum man seine Libraries mit _CRT_NOFORCE_MANIFEST erzeugen sollte

Seit VC 2005 wird die DLL-Version CRT ja grundsätzlich über ein Manifest geladen. Erzeuge ich Code, der die CRT verwendet, dann wird automatisch ein #pragma comment(linker,“/manifestdependency:..“) Eintrag erzeugt. Den entsprechenden Code dazu findet man in der _crtdefs.h Datei des entsprechenden Include-Pfades.

Dieser Manifest Eintrag wird später vom Manifest Tool (MT.EXE) gesammelt und im Manifest der Applikation oder der DLL verbaut.

Gesetzt den Fall ich habe Library erzeugt, die auch die DLL-Version der CRT verwendet, dann wird automatisch die entsprechende Version der CRT im Manifest hinterlegt. D.h. bei VS 2005 RTM die CRT Version 8.0.50727.42 und bei SP1 die CRT Version 8.0.50727.762

Wird nun für ein Projekt eine Library erzeugt mit einem VS 2005 RTM, und evtl. diese LIB lange nicht verändert und dann diese Library in einem Programm verwendet, dass mit VS 2005 SP1 erzeugt wird, dann werden beide CRT Versionen im Manifest eingetragen!
Der Lader ist zwar intelligent und lädt die letzte DLL (SP1). Aber undurchsichtig wird es in jedem Fall.
Vor allem wenn man wirklich mit evtl. einer älteren Version ausliefern möchte und SP1 auf der Ziel-Maschine nicht installiert ist.

Ich habe noch nicht genau herausbekommen wie entschieden wird welches der Manifesteintrag ist, der letzten Endes zählt. Scheinbar macht es auch nichts wenn, man die Version der CRT Versionen falsch angibt. Es wird dann die CRT mit der nächst höheren CRT geladen… (aber dazu muss ich noch etwas experimentieren)…

Dieses Verhalten ist gravierend unterschiedlich zu allen Vorgängerversionen von VC. Wie bisher hält Microsoft das Versprechen der binären Kompatibilität. Theoretisch müsste auch die mit RTM erzeugte LIB sofort funktionieren. Auch der Linker wird diese LIB sofort mit der entsprechenden MSVCR80(D).DLL verbinden. Nur hat er eben nicht das letzte Wort bei den Manifesten.

Man kann das ganze verhindern, indem man _CRT_NOFORCE_MANIFEST bevor die CRT Dateien inkludiert werden (am Besten in der stdafx.h oder in den Projekteinstellungen).
In diesem Fall wird kein #pragma, dass für den Manifest Eintrag verantwortlich ist, erzeugt.
Der Effekt ist dann wie gewollt, dass nur das eigentliche Projekt, dass dann das Executable erzeugt Einfluss auf die Ausgabe des Manifestes hat. Man kann also dann auch eine Lib-Datei mit VS-2005 SP1 erzeugen und mit einem VS-2005 RTM linken. Das Manifest wurde durch diese Lib dann nicht beeinflusst und so soll es IMHO sein.

Aber aufgepasst:
Seit VS-2005 muss in jedem Fall auf noch eine strenge Trennung der Release und Debug Versionen achten. Wenn nun kein Manifest mehr zum Beispiel in einer Release-Lib erzeugt wird, weil _CRT_NOFORCE_MANIFEST verwendet wird, nun aber diese Release Lib in ein Debug Projekt eingebunden wird.  Dann wird das Executable implizit mit der MSVCR80.DLL gebunden, aber diese DLL kann nur über ein Manifest geladen werden. Das ist aber nicht vorhanden. Resultat ist das was in diesem Beitrag beschrieben steht: Manifest-Hölle „…MSVCR80.dll nicht gefunden…“ (siehe Nachtrag)