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

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

Hotfix für UseMSPrivateAssemblies.h und VC-2008

Einige nutzen ja meine Lösung für private CRT und MFC Assemblies unter VC-2005, die ich in dem diesem Artikel unter Codeproject veröffentlicht habe
http://www.codeproject.com/KB/cpp/PrivateAssemblyProjects.aspx

Das Interesse und die Nachfrage ist groß dieses Verfahren auch unter VC-2008 zu nutzen.
Da ich aber aktuell wenig Zeit habe den Artikel komplett zu überarbeiten, veröffentliche ich den relevanten Code hier erst mal vorab als „Hotfix“. Dieser Hotfix setzt voraus, dass das aktuelle Feature Pack installiert ist. Der Code ist nicht auf die RTM Version hin zugeschnitten und getestet.

UseMSPrivateAssemblies.h

// Version 2.0 by Martin Richter [WWJD]
// Supports VC-2005 and VC-2008
#pragma once    

#ifndef RC_INVOKED
// Avoid problems with the resource compiler if included    

// This defines bock the creation in the header files
#pragma message("Using private assemblies for the MS runtimes")
#define _STL_NOFORCE_MANIFEST
#define _CRT_NOFORCE_MANIFEST
#define _AFX_NOFORCE_MANIFEST
//#define _ATL_NOFORCE_MANIFEST    

// The next statements block the linker from including object files in the
// CRT and the MFC, that would create manifest pragmas too.
#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif    

__declspec(selectany)       int _forceCRTManifest;
__declspec(selectany)       int _forceMFCManifest;
// __declspec(selectany)    int _forceAtlDllManifest;    

// The next symbols are used by the several versions of VC 9.0
__declspec(selectany)       int _forceCRTManifestRTM;
__declspec(selectany)       int _forceMFCManifestRTM;
__declspec(selectany)       int _forceMFCManifestCUR;    

#ifdef __cplusplus
}                        /* __cplusplus */
#endif    

// We use crtassem.h with the defines there. It just gives us the
// versions and name parts for the dependencies.
// Note that there is also a MFCassem.h but this include file has the
// manifest pragma's already in it. So we can't use it
//
// Three files are controlling this crtassem.h, MFCassem.h and atlassem.h!
// Happily __LIBRARIES_ASSEMBLY_NAME_PREFIX is used in CRT, MFC and ATL!
// Doing it right would need to use _MFC_ASSEMBLY_VERSION for the MFC
// but in fact _CRT_ASSEMBLY_VERSION and _MFC_ASSEMBLY_VERSION and
// _ATL_ASSEMBLY_VERSION are the same
//  - VC-2005 SP1 8.0.50727.762
//  - VC-2008 RTM 9.0.21022.8
//  - VC-2008 Feature Pack 9.0.30411.0 (used if _BIND_TO_CURRENT_VCLIBS_VERSION
//    and _BIND_TO_CURRENT_MFC_VERSION are defined to 1)    

#include <crtassem.h>

// We don't have a seperate block for the Debug version. We just handle
// this with a extra define here.
#ifdef _DEBUG
#define __LIBRARIES_SUB_VERSION    "Debug"
#else
#define __LIBRARIES_SUB_VERSION    ""
#endif    

// Manifest for the CRT
#pragma comment(linker,"/manifestdependency:\"type='win32' "                        \
    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX "." __LIBRARIES_SUB_VERSION "CRT' "   \
    "version='" _CRT_ASSEMBLY_VERSION "' "                                          \
    "processorArchitecture='x86' \"")    

// Manifest for the MFC
#pragma comment(linker,"/manifestdependency:\"type='win32' "                        \
    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX "." __LIBRARIES_SUB_VERSION "MFC' "   \
    "version='" _CRT_ASSEMBLY_VERSION "' "                                          \
    "processorArchitecture='x86'\"")    

// #pragma comment(linker,"/manifestdependency:\"type='win32' "                     \
//     "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".MFCLOC' "                        \
//     "version='" _CRT_ASSEMBLY_VERSION "' "                                       \
//     "processorArchitecture='x86'\"")    

// Manifest for the ATL
// #pragma comment(linker,"/manifestdependency:\"type='win32' "                     \
//    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".ATL' "                            \
//    "version='" _CRT_ASSEMBLY_VERSION "' "                                        \
//    "processorArchitecture='x86' \"")    

#endif // RC_INVOKED

Anmerkungen:

  • Im Endeffekt sind nur 3 Zeilen (26-28) hinzugekommen.
  • Diese Version funktioniert sowohl für VC-2005 als auch VC-2008!
  • Unter Vista wird allgemein das Problem beobachtet, das private Assemblies nur genutzt werden können, wenn diese in einem Unterverzeichnis liegen. Liegen die Assembly Dateien im gleichen Verzeichnis wie die EXE kommt es zu einem Fehler „The application failed to initialize properly (0xc0000034). „ Dieser Sache bin ich (und andere) auf der Spur.
  • Es spielt für diesen Code keine Rolle ob die beiden Defines _BIND_TO_CURRENT_VCLIBS_VERSION und _BIND_TO_CURRENT_MFC_VERSION gesetzt wurden. Werden diese Defines auf 1 gesetzt bevor UseMSPrivateAssemblies inkludiert wird, dann werden die Manifeste so erzeugt, dass die Feature Pack DLLs gezogen werden. Sind diese beiden Defines nicht gesetzt werden Manifeste für die RTM Version erzeugt.
    Ich empfehle dringend diese beiden Defines zu setzen ❗

Das ist erstmal ein Schnellschuss für alle, die die es etwas eiliger haben.

Der Vorteil gegenüber der Lösung, bei der die Manifeste manuell bearbeitet werden, wie es zum Beispiel Jochen Kalmbach in seinem Blog vorgestellt hat ist klar:
Man muss eben nichts manuell machen 🙂
Es macht wieder alles der Compiler und Linker.

Der zweite Versuch: Visual C++ 2008 Feature Pack Refresh (MFCNext & TR1)

Nach dem ersten Versuch nun der zweite Versuch. Alle bisher bekannten Installationsprobleme sind gefixed.

Weitere Infos: http://blogs.msdn.com/vcblog/archive/2008/04/22/visual-c-2008-feature-pack-refresh.aspx

Download: http://www.microsoft.com/downloads/details.aspx?FamilyID=d466226b-8dab-445f-a7b4-448b326c48e7&displaylang=en

Das bereits installierte Feature Pack muss deinstalliert werden! Glücklich wer nur die Beta bisher installiert hat, der kann wie bisher einfach drüber installieren. ❗

BTW: Eine Responsezeit von 16 Tagen ist gar nicht soooo schlecht 😉

Visual C++ 2008 Libraries Extension Feature Pack Final Release (MFCNext & TR1)

❗ Es ist da, die finale Version des MFC-Feature Pack für VS-2008 (MFCNext & TR1) ❗

Das Feature Pack ist nur für die Englische VS-Version (ENU) ab Visual Studio 2008 Standard Edition verfügbar.
Andere Sprachversionen werden in Visual Studio 2008 Service Pack 1 enthalten sein.

Visual C++ 2008 Libraries Extension Feature Pack Final Release
Download link, 322.8 MB
Redistributable Package (x86)

Dokumentation:
MSDN MFC Feature Pack for Visual C++ 2008
MSDN TR1 Extensions

❗ Wichtige Anmerkung sofern ein Deutsches OS eingesetzt wird ❗

Bei mir schlug die erste Installation fehl (sowohl auf Deutschem Vista Ultimate SP1 als auch XP SP2). In der Log-Datei im %TMP% Verzeichnis konnte ich herausbekommen, dass die Datei SPInstallerResources.1031.dll vermisst wird:

Unable to load UI satellite DLL SPInstallerResources.1031.dll 

Nach einigem hin und her Tricksen mit Entpacken und Umbenennen der Datei fand ich folgenden Trick um das Feature Pack zu installieren:
Man muss einfach vorher in der Systemsteuerung in den Regions- und Spracheinstellungen, die Einstellungen für die Formate auf „Englisch (USA)“ umstellen. Zusätzlich habe ich auch den Standardort auf „Vereinigte Staaten“ eingestellt. Danach lief die Installation korrekt ab.

Es spielt übrigends keine Rolle ob die Feature Pack Beta Version zuvor installiert war!

LoadIcon macht es nicht richtig…

… oder sollte man sagen: Nicht so wie erwartet. 

In einer der Micosoft Newsgroup fand sich eine Anfrage, die sich um folgenden Code drehte:

HICON m_hIcon[2]; 
[...] 
m_hIcon[0] = AfxGetApp()->LoadIcon(IDR_MAINFRAME); 
m_hIcon[1] = AfxGetApp()->LoadIcon(IDR_SMALL); 
[...] 
SetIcon(m_hIcon[0], TRUE);   // Großes Symbol verwenden 
SetIcon(m_hIcon[1], FALSE);  // Kleines Symbol verwenden

Das Problem des Posters war, dass er sich Mühe mit dem Layout eine kleines Icons gegeben hat, aber leider immer nur ein verkleinertes 32×32 Icon verwendet wurde.

Das Problem liegt an der Verwendung von LoadIcon! LoadIcon lädt immer das Standard-Format 32×32. Bzw. in dem Format, dass durch das System als Standard-Icon-Größe vorgegeben wird.

Um es richtig zu machen, muss man das Icon wirklich in der gewünschten Größe laden. Dazu verwendet man die Funktion LoadImage. Eine kleine Helper Routine LoadIconEx kann dann wie folgt aussehen:

HICON LoadIconEx(PCTSTR pszIconName, bool bLargeIcon) 
{ 
 HINSTANCE hInstance = ::AfxFindResourceHandle(pszIconName,RT_GROUP_ICON); 
 return reinterpret_cast<HICON>(::LoadImage( 
       hInstance, 
       pszIconName, 
       IMAGE_ICON, 
       GetSystemMetrics(bLargeIcon ? SM_CXICON : SM_CXSMICON), 
       GetSystemMetrics(bLargeIcon ? SM_CYICON : SM_CYSMICON), 
       0)); 
}

VS-Tipps & Tricks: Downgrade für VC-200x Projekte

Kann man ein auf VC-2005 erstelltes Projekt einfach auch unter VC-2003 builden?
Oder ein 2008er Projekt auf VS-2005 laden?

Der Mühsame weg, ist es, das Projekt neu aufzubauen. D.h. ein leeres Projekt anzulegen und die entsprechenden Dateien aus dem alten Projekt in das neue Projekt in der niedrigeren Version aufzunehmen.
Es geht etwas leichter mit einem kleinen Hack ❗

Nehmen wir das Beispiel eines Downgrades von VS-2008 auf VS-2005.

  • Man kopiert einfach die entsprechende VCPROJ Datei unter einen neuen Namen
  • Man öffnet diese Datei mit dem Editor seiner Wahl
  • Man schaut in die Datei und findet den folgenden XML Code

<?xml version="1.0" encoding="Windows-1252"?>
 <VisualStudioProject
  ProjectType="Visual C++"
  Version="9,00"

Alles was man nun ändern muss ist die Zeile 4 mit dem Version Eintrag.
Wie ändern diesen Eintrag auf

 Version="8,00"

für VS-2005. Will man zurück auf die Version VS-2003 dann muss der neue Wert

 Version="7.10"

heißen.

Und schon sind wir fertig. Man glaubt es kaum 🙂

Klar ist, dass natürlich neue Features, wie z.B. Eigenschaften des Manifest Compilers in der vorher gehenden Version, die das z.B. gar nicht kennt untergehen. Diese Dateien bleiben in der Projektdatei, aber werden Dank XML einfach in der alten VS Version ignoriert.

Hotfix für GDI Leaks unter Windows XP-SP2/2003 Server in MFC Applikationen

Unter XP SP2 und Windows 2003 Server kann es bei eingeschalteten Themes zu GDI-Leaks kommen. Speziell wird hier auf MFC Anwendungen hingewiesen (siehe dazu meine Anmerkung unten).

Die Beschreibung und der Download-Link für den Hotfix finden sich hier:
MFC applications leak GDI objects on computers that are running Windows Server 2003 or Windows XP

Anmerkung:

Dies ist ein Hotfix für Theme Handler unter Windows 2003 und Windows XP SP2. Er greift nicht in die MFC ein ❗
Man muss also eigentlich davon ausgehen, dass der Bug im Windows Themes Kern steckt und nichts mit der MFC zu tun hat, sondern eher damit zu tun hat wie in der MFC mit Windows Ressourcen umgegangen wird.
Den Effekt bekommt man ziemlich einfach hin, indem man mit dem Wizard eine MFC-MDI Applikation anlegt. Dann einfach Strg+N festhalten und alle Fenster wieder schließen mit Strg+F4, danach findet man einige Hundert GDI Objekte ausgewiesen im Task-Manager, die vorher nicht da waren.
Ich habe einen Test mit dem MDI-Sample aus dem Petzold gemacht und dieses zeigt diesen Effekt nicht. Evtl. liegt es auch einfach nur an dem Umgang mit den Toolbars. Genaueres konnte ich nicht herausbekommen. 

  • Erstaunlich 1.: Dieser Patch ist mit 2 Jahren doch relativ alt (März 2006) und er ist mir erst durch eine Diskussion in einer Produkt-Gruppe über den Weg gelaufen …
  • Erstaunlich 2.: Dieser Patch wird nicht durch den Windows Update Service installiert, obwohl er in meinen Augen da rein gehört ❗
    Ob nun der Update Service nun den primären Fokus auf Sicherheit hat oder nicht. Ich empfinde dieses Verhalten des Theme Handlers als kritisch!