Wenn an einem Projekt der geladenen Projektmappe (Solution) im Visual Studio (VS) seit dem letzten erfolgreichen Erstellungsvorgang Veränderungen vorgenommen wurden, dann wird das vom VS erkannt und beim nächsten Erstellungsvorgang entweder automatisch oder nach Rückfrage berücksichtigt. Die entsprechenden Einstellungen, die festlegen, wie in diesem Fall vorgegangen werden soll, können im Optionen-Dialog des VS (über das Menü „Extras“ erreichbar) angepasst werden (vgl. Erstellen und Ausführen, Projekte und Projektmappen, Dialogfeld “Optionen”):

Hat man die Einstellung Zum Erstellen auffordern ausgewählt, so wird man fortan über veraltete Projektkonfigurationen folgendermaßen informiert:

In diesem Beispiel ist ein Projekt der gesamten Projektmappe veraltet.

Nun kann es in einigen Fällen aber eben doch vorkommen, dass dieser Dialog auch dann angezeigt wird, wenn an dem Projekt definitiv nichts verändert wurde. Das ist natürlich lästig, weil das Erstellen immer wieder wertvolle Zeit kostet und den eigentlichen Entwicklungsfluss mitunter stark beeinträchtigen kann. Daher wäre es sehr hilfreich, wenn man irgendwie herausfinden könnte, warum das VS der Meinung ist, dass ein neues Erstellen des Projektes erforderlich ist.

Bei einer Recherche im Internet bin ich dann auf den Artikel Enable C++ project system logging von Andrew L Arnott gestoßen, in dem beschrieben wird, wie man dem Problem auf die Spur kommen kann.

Schritt 1: System-Logging für c++-Projekte aktivieren

Zunächst muss das System-Logging für c++-Projekte aktiviert werden. Die Konfigurationsdatei devenv.exe.config aus dem Verzeichnis
%PROGRAMFILES%\Microsoft Visual Studio 10.0\Common7\IDE\
muss dazu um einige Zeilen ergänzt werden:

<?xml version ="1.0"?>
<configuration>
    <configSections>
        ...
    </configSections>
    <!-- Begin Neu -->
    <system.diagnostics>
        <switches>
            <add name="CPS" value="4" />
        </switches>
    </system.diagnostics>
    <!-- Ende Neu -->
    ...
</configuration>

Damit diese Änderung aktiv wird, muss VS neu gestartet werden.

Schritt 2: Log-Informationen abfangen

Nachdem der Logging-Mechanismus im ersten Schritt erfolgreich aktiviert wurde, muss festgelegt werden, wie die geloggten Informationen abgefangen werden können. Hierfür gibt es verschiedene Möglichkeiten. Eine Möglichkeit, die nachfolgend betrachtet wird, besteht darin, die Log-Einträge mit dem Werkzeug DebugView, das man kostenlos von der Microsoft-Internetseite herunterladen kann, abzufangen und darzustellen. DebugView kann man ohne aufwendige Installation in ein beliebiges Verzeichnis entpacken und ausführen. Startet man dann – bei laufendem DebugView – wieder das Debugging aus dem VS heraus, dann werden verschiedene Ausgaben des VS von DebugView abgefangen und direkt angezeigt. Sobald der bereits oben erwähnte Dialog mit der Information über das veraltete Projekt wieder erscheint, kann die Ausgabe des DebugView-Tools nach entsprechenden Informationen durchsucht werden. In dem hier beschrieben Fall weisen die Ausgaben darauf hin, dass über die Projektdatei auf 12 Header-Dateien verwiesen wird, die auf dem lokalen Rechner gar nicht (mehr) existieren:

Tatsächlich wurden diese Dateien erst vor kurzem entfernt. In der Projektdatei waren diese Dateien also nicht mehr zu finden. Eine weitere Suche über die Dateien (und deren Inhalte) in dem Verzeichnis des Projekts brachte dann aber einige weitere Fundstellen zu Tage. Infolgedessen wurde die gesamte Build-Ausgabe für das Projekts bereinigt, was dazu geführt hat, dass diese veralteten Dateien gelöscht wurden. Nach einem erneuten Erstellen des Projekts war das Problem dann behoben.

Links

Ein interessanter Artikel zum (zukünftigen) Umgang mit c++ bei Microsoft (von Marius Bancila): C++ Renaissance at Microsoft.

Nachdem der c++-Standard nun offiziell unter dem Namen c++11 verabschiedet worden ist, stellt sich natürlich die Frage, was wir von diesem Standard schon jetzt verwenden können. Der im Stdcxx Wiki veröffentlichte Artikel C++0xCompilerSupport zeigt, welcher Compiler welche c++11-Feutures bereits unterstützt.

Die neuen (Fenster-) Klassen der MFC, die zum MFC Feature Pack gehören, bieten von Haus aus die Möglichkeit, die aktuellen Zustände der Fenster einer Anwendung (Größe, Position etc.) nach dem Beenden eines Programms in die Registry zu schreiben. Wird die Anwendung zu einem späteren Zeitpunkt wieder gestartet, dann können die alten Fensterzustände mit diesen Registry-Einträgen wiederhergestellt werden. Was aber tun, wenn die Registry hierfür nicht verwendet werden soll?

In den Methoden

  BOOL LoadState(LPCTSTR lpszProfileName, int nIndex, UINT uiID);
  BOOL SaveState(LPCTSTR lpszProfileName, int nIndex, UINT uiID);

der Klasse CBasePane nimmt die Serialisierung der Zustände ihren Lauf. Dabei ist ein Objekt vom Typ CSettingsStore für das Speichern der Daten (in die Registry) verantwortlich:

  CSettingsStoreSP regSP;
  CSettingsStore& reg = regSP.Create(FALSE, FALSE);

Wie das genau funktioniert, kann man an den (virtuellen) Read- und Write-Methoden der Klasse CSettingsStore (vgl. afxsettingsstore.h) sehen. Das Framework gestattet es aber auch, über ein CRuntimeClass-Objekt eigene CSettingsStore-Objekte zu verwenden. Definiert man sich also eine von CSettingsStore abgeleitete Klasse (z.B. CMySettingsStore), dann kann man die Kontrolle über das Speichern der Daten (z.B. in Dateien) übernehmen, indem man die Read- und Write-Methoden entsprechend überschreibt. Voraussetzung ist, dass die eigene Klasse zuvor noch mit

  CSettingsStoreSP::SetRuntimeClass( RUNTIME_CLASS( CMySettingsSore ) );

angemeldet wird.

18. May 2011 · Write a comment · Categories: MFC

Sowohl in Kontextmenüs (CMFCPopupMenu) als auch in “normalen Menüs” (CMFCMenuBar) gibt es für jeden Menüeintrag ein Objekt vom Typ CMFCToolBarMenuButton. Handelt es sich bei einem Menüeintrag um ein Untermenü (Submenü), dann kann man über die Methode

  const CObList& CMFCToolBarMenuButton::GetCommands() const;

auf die Menüeinträge des Untermenüs zugreifen. Mit dieser Information lässt sich dann recht einfach eine rekursive Funktion implementieren, die ab einem gegebenen Menüpunkt alle untergeordneten Menüpunkte systematisch durchläuft, z.B.:

	void IterateMenu( CMFCToolBarMenuButton* pButton )
	{
		if( pButton )
		{
			// Irgendetwas mit diesem Menüeintrag anstellen:
			pButton-> ...

			// Hat der Menüpunkt ein Untermenü?
			const CObList& lCmds = pButton->GetCommands();
			for( POSITION pos = lCmds.GetHeadPosition(); pos != NULL; )
			{
				IterateMenu( (CMFCToolBarMenuButton*) lCmds.GetNext( pos ) );
			}
		}
	}

Leider besitzen die beiden genannten Menüklassen keine gemeinsame Basisklasse, die ein einheitliches Iterieren über alle Elemente der ersten Ebene ermöglichen könnte. Allerdings ist diese Funktionalität durchaus an beiden Klassen vorhanden.

Für ein CMFCPopupMenu-Kontextmenü könnte diese Funktion dann folgendermaßen eingesetzt werden:

	CMFCPopupMenu* pMenu = ...
	for( int i = 0; i < pMFCPopupMenu->GetMenuItemCount(); i++ )
	{
		IterateMenu( (CMFCToolBarMenuButton*) pMenu->GetMenuItem( i ) );
	}

Im Fall eines Menüs vom Typ CMFCMenuBar könnte das dagegen so aussehen:

	CMFCMenuBar* pMenu = ...
	for( int i = 0; i < pMenu->GetCount(); i++ )
	{
		IterateMenu( (CMFCToolBarMenuButton*) pMenu->GetButton( i ) );
	}

Zum Auffinden von Memory-Leaks kann die globale Variable _crtBreakAlloc verwendet werden. Speicher wird in Blöcken zugewiesen, und jeder Block ist durch eine Nummer eindeutig identifizierbar. Wenn der Debugger ein Speicher-Leak meldet, dann wird diese Nummer immer in geschweiften Klammern – z.B. {314} – mitgeliefert:

Mit dieser Nummer kann man dann einiges anfangen! Zuerst setzt man einen Haltepunkt in die InitInstance-Methode. Dann wird der Debugger gestartet. Wenn der Debugger in dem Haltepunkt stoppt, gibt man folgendes in das Fenster “Überwachung” ein:

Führt man nun die Debug-Sitzung fort, dann hält der Debugger an der Stelle an, an der die o.g. Nummer zugewiesen wird:

Und nun ist der Call-Stack wieder unser bester Freund.

Man sieht, dass hier ein CString-Objekt mit new angelegt wird. Das ist natürlich mehr als verdächtig! Und sucht man nach der Stelle, wo diese Objekte wieder aufgeräumt werden, dann wird man nicht fündig. Das Objekt, das diesen Speicher-Leak verursacht ist damit gefunden.

Möchte man im Hauptmenü einer MFC-basierten Anwendung einen Menübefehl mit einem gesetzten “Häkchen” versehen, so gibt es verschiedene Möglichkeiten, dies zu tun. In diesem Zusammenhang habe ich bereits des Öfteren Code der folgenden Art gefunden:

// ---------------------------------------------
// Klasse.h
// ---------------------------------------------
class CKlasse : public CWnd
{
public:
	...
	afx_msg void OnBefehl( void );
	...
	DECLARE_MESSAGE_MAP()
};

// ---------------------------------------------
// Klasse.cpp
// ---------------------------------------------

BEGIN_MESSAGE_MAP
	ON_COMMAND( ID_BEFEHL, OnBefehl )
END_MESSAGE_MAP

void CKlasse::OnBefehl(void)
{
	// Ist das Häkchen vor dem Menübefehl gesetzt?
	UINT oldstate, checkstate;
	HMENU hMenu = AfxGetMainWnd()->GetMenu()->GetSafeHmenu();
	oldstate = ::GetMenuState( hMenu, ID_BEFEHL, MF_BYCOMMAND );
	checkstate = oldstate;
	checkstate &= MF_CHECKED;
	if ( checkstate == MF_CHECKED )
	{	// Das Häkchen ist gesetzt. Entferne das Häkchen nun.
		oldstate = 0;
		::CheckMenuItem( hMenu, ID_BEFEHL, oldstate );

		...
	}
	else
	{	// Das Häkchen ist nicht gesetzt. Setze es
		oldstate ^= MF_CHECKED;
		::CheckMenuItem ( hMenu, ID_BEFEHL, oldstate);

		...
	}
}

Das ist grundsätzlich nicht falsch und funktioniert ja auch. Insgesamt bietet die MFC dafür aber einen deutlich eleganteren Mechanismus, den ich nachfolgend mal kurz beschreiben möchte.

Möglichkeiten der MFC

Die MFC bietet für die einleitend beschriebene Problemstellung einen Mechanismus an, der mit Hilfe der Message-Map-Makros folgendermaßen implementiert werden kann:

// ---------------------------------------------
// KlasseNeu.h
// ---------------------------------------------
class CKlasseNeu : public CWnd
{
	BOOL m_bBefehlIsChecked;
public:
	...
	afx_msg void OnBefehl( void );
	afx_msg void OnUpdateBefehl( void );
	...
	DECLARE_MESSAGE_MAP()
}; 

// ---------------------------------------------
// KlasseNeu.cpp
// ---------------------------------------------

BEGIN_MESSAGE_MAP(CKlasseNeu, CWnd)
	ON_COMMAND( ID_BEFEHL, OnBefehl )
	ON_UPDATE_COMMAND_UI( ID_BEFEHL, OnUpdateBefehl )
END_MESSAGE_MAP()

void CKlasseNeu::OnBefehl(void)
{
	m_bBefehlIsChecked = !m_bBefehlIsChecked;

	if( m_bBefehlIsChecked )
	{
		...
	}
 	else
 	{
 		...
 	}
}

void CKlasseNeu::OnUpdateBefehl( CCmdUI* pCmdUI )
{
	pCmdUI->SetCheck( m_bBefehlIsChecked );
}

Das ist – wie man sehen kann – nicht nur viel einfacher zu lesen, sondern vom Coding her auch wesentlich eleganter, da man auf diese Weise gar nicht direkt auf die UI-Elemente zugreifen muss.

Weitere Möglichkeiten der MFC

Der Command- und Update-Mechachnismus bietet aber noch weitere Möglichkeiten. Zum Beispiel kann man mit dem ON_UPDATE_COMMAND_UI eine einzige Methode für mehrerer Update-Aktionen verwenden. Dies hat den Vorteil, dass die man die Klassendefinitionen in vielen Fällen stark reduzieren kann, weil man nicht mehr so viele Methoden benötigt. Beispiel:

// ---------------------------------------------
// KlasseNeu.cpp
// ---------------------------------------------

BEGIN_MESSAGE_MAP( CKlasseNeu, CWnd )
	ON_UPDATE_COMMAND_UI( ID_BEFEHL1, OnUpdateMenu )
	ON_UPDATE_COMMAND_UI( ID_BEFEHL2, OnUpdateMenu )
	ON_UPDATE_COMMAND_UI( ID_BEFEHL3, OnUpdateMenu )
END_MESSAGE_MAP()

void CKlasse::OnUpdateBefehl( CCmdUI* pCmdUI )
{
	switch( pCmdUI->m_nID )
	{
		case ID_BEFEHL1:
			pCmdUI->Enable( ... )
			pCmdUI->SetCheck( ... )
			break;
		case ID_BEFEHL2:
			pCmdUI->Enable( ... )
			...
			break;
		case ID_BEFEHL3:
			...
			break;
		default:
			// Unbekannter Befehl
			break;
	}
}

Es sollte auch berücksichtigt werden, dass in einem ON_UPDATE_COMMAND_UI-Handler nicht zu komplexe und berechnungsintensive Aktionen verwendet werden, da diese Handler sehr häufig aufgerufen werden.

In Analogie zum ON_UPDATE_COMMAND_UI-Handler gibt es auch einen ähnlich aufgebauten ON_COMMAND-Handler mit dem Namen ON_COMMAND_EX, der z.B. so eingesetzt werden kann:

ON_COMMAND_EX( ID_BEFEHL1, OnBefehl );
ON_COMMAND_EX( ID_BEFEHL2, OnBefehl );
ON_COMMAND_EX( ID_BEFEHL3, OnBefehl );

Mit der Methode

afx_msg BOOL OnBefehl( UINT nCmdUI );

kann man dann vollkommen analog zu den ON_UPDATE_COMMAND_UI-Methoden das hier machen:

BOOL CKlasseNeu::OnBefehl( UINT nCmdUI )
{
	switch( nCmdUI )
	{
		case ID_BEFEHL1:
			...
			break;
		case ID_BEFEHL2:
			...
			break;
		case ID_BEFEHL3:
			...
			break;
		default:
			// Unbekannter Befehl
			break;
	}
}

Insbesondere bei sehr übersichtlichen und miteinander verwandten Befehlen (z.B. Befehle aus dem Datei-Menü etc.), bietet sich eine solche Implementierung durchaus an.

Mit Version 9.0 sind die Microsoft Foundation Classes (MFC) um das MFC Feature Pack erweitert worden. Einen Überblick über alle Klassen geben die als XPS und PDF (Download) vorliegenden MFC Hierarchy Charts:

  • MFC Hierarchy Chart Part 1 of 3: Classes Derived From CObject (pdf, xps).
  • MFC Hierarchy Chart Part 2 of 3: Classes That Derive From CCmdTarget Or CWnd (pdf, xps).
  • MFC Hierarchy Chart Part 3 of 3: Classes Not Derived From CObject (pdf, xps).
03. December 2010 · Comments Off · Categories: c++, MFC

Was bringt das Service Pack 1 speziell für c++-Entwickler? Die Frage beantwortet der Artikel VS2010 SP1 Beta: What’s in It for C++ Developers im MSDN.

Link: Using Worker Threads.