VS-Tipps & Tricks: Springe zur nächsten Klammer funktioniert auch für #if, #elif, #else und #endif

Wer sich schon durch die Windows Header gekämpft hat um herauszufinden warum welche Definition einer Struktur oder Funktion in irgend einer Windows Version so oder gar nicht vorhanden ist, der weiß auch wie einem #if, #elif, #else und #endif das Leben schwer machen können, was die Orientierung betrifft.

Netterweise hilft einem eine Funktion, die man nur von Blöcken und verschachtelten Funktionen her kennt Strg+´ (Edit.GotoBrace). Wichtig! Man darf nicht auf der Variable oder Bedingung stehen, sondern muss auf dem Schlüsselwort stehen.

Wenn man auf einer Präprozessor Direktive kann man mit den Tasten die einem zur passenden Klammer bringt zur nachfolgenden Direktive. Und mit dem Festhalten der Umschalttaste kann man den entsprechenden Block auch markieren.

Visual Studio 2010 News

Erstmal die wichtigsten News vorab:

  • Die Beta 2 ist ab heute den 19.10.2009 auf MSDN verfügbar.
  • Für alle die, die kein MSDN haben wird es die Beta 2 vorraussichtlich ab Donnerstag den 22.10.2009 frei für alle verfügbar geben.
  • Das Erscheinungsdatum des Visual Studio 2010 wird voraussichtlich der 22. 03.2010 sein ❗

Die Zusammenstellung der Visual Studio Produkte wird sich komplett ändern. Insbesondere was den Team Foundation Server (TFS) und das Team System selbst bettrifft. Es wird nur noch drei Produkte geben (Professional, Premium, Ultimate) und nicht wie bisher 7 (Standard, Professional, Team System Developer, Test, Database, Architecture  und  Team-Suite) ❗
Das bisherige System mit den unterschiedlichen Rollen in Verbindung mit dem TFS kam nicht an und war unverständlich. Die drei neuen Versionen schließen immer die Leistung aller vorhergehenden Pakete ein. Im Detail sieht das bei den neuen Visual Studio Paketen in etwas so aus:

  • Professional: Alles was man bisher eben auch von der Professional Version kennt plus einige Extras, wie Multi-Monitor Support, Managed Extensibility etc.
  • Premium: Alles was Professional hat, inkl. das was wir bisher so in der Team System Developer, Database und der Tester Version kannte.
  • Ultimate: Alles was Professional und Premium umfasst plus die Team System Architecture Funktionen. Kommt also in etwa auf das hinaus was bisher die Team Suite war.

Auffällig! Es ist keine Visual Studio Standard Version geplant. Visual Studio Standard stirbt.
Aber auch hier soll was preiswertes kommen, so dass die Professional Version erschwinglich wird. So jedenfalls meine Hintergrundinfos.

Aber das aller wichtigste ❗ ❗ ❗

  • Alle Visual Studio 2010 Versionen schließen den TFS und eine CAL mit ein ❗
    (Diese Info ist unter Vorbehalt!)
  • Der TFS bekommt eine neue Installationsform mit dem Namen Basic.

Soweit ich das verstanden habe ist das ein vollständiger TFS, wie wir ihn heute kennen aber ohne Sharepoint Overhead. Das bedeutet einfache Installation in 15 Minuten. Und das läuft auch auf einem DC oder SBS, für alle die, die sich keinen zweiten Server hinstellen wollen. Natürlich eignet sich die Basic Installation auch als Installation für den Einplatz (d.h. auch für jeden XP, Vista und Windows 7 Rechner).
Die TFS Basic Installation bietet alles und viel viel viel viel mehr,  als das was wir bisher mit VSS und Visual Studio erledigt haben.

Microsoft will also ganz klar den TFS pushen und auf jeden Entwickler dazu bringen den TFS zu nuzen. Das heißt natürlich in zweiter Linie eine klare Absage für Visual Source Safe (VSS). Man kann sicherlich den VSS auch weiterhin in 2010 nutzen, wie man das in Visual Studio 2005+2008 her kennt. Aber ich kann nur jedem raten sich schnellstmöglich mit dem TFS anzufreunden.
Der VSS Mainstream Support läuft noch bis 11.04.2011!

Anmerkung zum Schluß:
Die Beta2 schließt bereits eine Go-Live Lizenz mit ein, eigent sich also direkt auch für das produktive Entwickeln.

Nachtrag: Alle Infos zur Lizensierung und Bündelung der Pakete bitte ich noch mit etwas Vorbehalt zubehandeln. Die Informationen wie ich sie hier wiedergebe basieren auf einem Webcast vom letzten Freitag und geben wieder wie was ich verstanden habe.

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

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());
}

CMapStringTo… HashKey Implementierung in VC-2003/5/8 ist auch fragwürdig

 Hash-Algorithmen haben es mir irgendwie angetan. 1984 hatte ich das erste mal mit einem miesen Hashverfahren zu tun, das einfach versagte wenn es nur um Ziffern ging. Das ganze betraf das Betriebssystem OASIS (später TheOS) und dessen Index Dateiorganisation. Ich entwickelte hierfür einen Patch in Assembler, der damals exakt in den x-Bytes der Kernels in Z80 Assembler passen musste.
Das Problem bei Strings die nur aus Ziffern bestehen ist das die Veränderung, die immer nur die unteren 4 Bits betrifft und die höheren 4 Bits immer konstant sind mit 3 belegt sind.

Als ich gelesen habe,  dass in VS-2010 die CMap Implementierung nun auch für Strings den selben Algorithmus aus der STL erhalten, habe ich mir auch den mal genauer angesehen. (siehe Beitrag Die MFC erhält mit VC-2010 jetzt eine neue HashKey Implementierung)
Der sieht so aus:

inline UINT CMapStringToString::HashKey(LPCTSTR key) const
{
  UINT nHash = 0;
  if (key == NULL)
  {
    AfxThrowInvalidArgException();
  }
 
  while (*key)
    nHash = (nHash<<5) + nHash + *key++;
  return nHash;
}

Und auch in diesem Fall muss ich sagen, dass der Algorithmus nur scheinbar intelligent ist. Wenn man sich aber die Abstände die hier erzeugt werden etwas genauer anschaut kommt man schnell auf eine Wertekombination, in der der Algorithmus versagt.
Wie sehr er versagt zeigt das folgende Beispiel, in dem ich einfach einen String verwende der aus 5 Ziffern besteht und immer den Abstand 11 verwendet.

CMapStringToPtr myMap;
for (int i=0; i<1000; i+=11)
{
  CString strKey;
  strKey.Format(_T("%05d"),i);
  myMap[strKey] = NULL;
}

Das erschreckende Ergebnis der Verteilung ist bei einem Unicode wie auch MBCS Projekt:

TMyMap<class CMapStringToPtr>::DumpMap
Bucket  0 =  0
Bucket  1 =  0
Bucket  2 =  0
Bucket  3 =  0
Bucket  4 =  0
Bucket  5 =  0
Bucket  6 =  0
Bucket  7 =  0
Bucket  8 = 36
Bucket  9 =  0
Bucket 10 =  0
Bucket 11 =  0
Bucket 12 =  0
Bucket 13 =  0
Bucket 14 = 55
Bucket 15 =  0
Bucket 16 =  0

Wie gut, dass auch die String Variante von HashKey überarbeitet wird.

BTW: Da die internen Strukturen protected sind muss man zu einem kleinen Trick greifen um die Verteilung ausgeben zu können. Ich habe dazu ein template verwendet, dass für alle MFC Maps funktioniert.

template<class TBase> class TMyMap : public TBase
{
public:
  void DumpMap()
  {
    TRACE(__FUNCTION__ "\n");
    if (m_pHashTable)
    {
      for (size_t i=0; i<m_nHashTableSize; ++i)
      {
        int iCount = 0;
        for (CAssoc *p = m_pHashTable[i]; p; p=p->pNext)
          ++iCount;
        TRACE("Bucket %2d = %4d\n", i, iCount);
      }
    }
  }
};

Merke: Wer Hashes verwendet sollte sich über sein Hashverfahren wirklich gedanken machen.

BTW: Sollte es den Leser meines Blogs langsam langweilen weil ich mich permanent mit Hash-Algorithmen aufhalte, der sei getröstet: Dies war vorläufig mein letzter Beitrag zu CMap… :mrgreen:

Die MFC erhält mit VC-2010 jetzt eine neue HashKey Implementierung

Ich hatte die schlechte Hashkey Implementierung ja bereits in VC-2005 als Bug gemeldet (siehe Die HashKey Implementierung in der MFC in VC-2005 und VC-2008). In VC-2008 geschah dies bzgl. wieder nichts. Aber was lange währt wird manchmal gut 😉

Die neue Implementierung wird aus der aktuellen STL übernommen. Diese Implementierung sorgt für Integer und Pointer für eine sehr gute zufällige Verteilung. Und weitere gute Nachricht dazu, auch die Implementierung für Strings wird aus der STL übernommen.

Zitat:

Hello Martin,

Thanks for the report. This issue has been fixed in MFC for Visual Studio 2010. MFC now uses the STL hash functions directly when possible, or uses a new algorithm copied from STL (in the case of strings).

Pat Brenner
Visual C++ Libraries Development
Von Microsoft am 20.07.2009 um 12:15 bereitgestellt

Siehe https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=468860

Die HashKey Implementierung in der MFC in VC-2005 und VC-2008

Die Maps in der MFC werden ja auch zum Teil gerne verwendet. Auch wenn viele Programmierer eher auf die std::map bzw. std::hash_map verwenden.

Zwei interne Dinge sind jedoch vielen Entwicklern nicht klar.

  1. Die Standard-Maps der MFC werden mit 17 Hash-Buckets erzeugt. Man sollte sich also über die Anzahl der Elemente klar werden, die gespeichert werden sollen.
    Bei großen Datenmengen sollte man evtl. darüber nachdenken, die Anzahl der Buckets zu erhöhen.
  2. Ist die Hash Funktion zu beachten, die hier verwendet wird. Denn deren Effektivität entscheidet ja, wie die Einträge auf die Buckets verteilt werden.

Zu diesem zweiten Punkt macht man eine erstaunliche Entdeckung, wenn man sich ansieht, was Microsoft für eine Funktion vorgesehen hat um den Hask-Key zu erzeugen. Diese Funktion ist in der MFC für primitive Werte vordefiniert, als primitive Werte sehe ich alle numerischen Werte, Zeiger und Handle an.
Man findet eine Implementierung als Template-Funktion in afxtempl.h:

template<class ARG_KEY>
AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
 // default identity hash - works for most primitive values
 return (DWORD)(((DWORD_PTR)key)>>4);
}

Das erstaunliche ist für mich, dass hier die untersten 4 Bits einfach abgeschnitten werden. Für mich hätte für Handles, Zeiger und Integer Werte einfach ein cast gelangt.

Nehmen wir jetzt mal an, wir haben eine einfache Datenmenge, die die Integer 1-100 auf eine Struktur abbilden. Man wird die erstaunliche Entdeckung machen, dass alle Werte nur in den Buckets 0-6 abgespeichert werden. Die Buckets 7 bis 16 werden nicht verwendet.

Der Nachteil ist offensichtlich. Man nutzt das Datenrauschen auf den untersten 4 Bits nicht.
Das macht sich sogar bei Fensterhandles bemerkbar. Bekanntlich sind das immer gerade Werte, aber auch auf den Bits 1-3 finden wir hier Informationen. Auch hier werden nützliche „zufällige“ Informationen nicht genutzt. Dadurch werden mehr Kollisionen in Kauf genommen als notwendig wären.

Ich möchte nicht unerwähnt lassen, dass die MFC Maps wirklich eine Berechtigung haben, weil sie grundsätzlich einen Pool-Allocator verwenden. Häufige Allokationen und Löschungen werden weitaus schneller behandelt, als durch die std::map, oder std::hash_map mit normalen Allocator.

Ich habe diesen Bug (oder miese Verhalten) bereits in der Beta für VC-2008 gemeldet. Es wurde aber nicht mehr geändert. Nun habe ich es an die Produktgruppe erneut  für VC-10 eingereicht.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=100772
Nachtrag 22.06.2009: Da der Bug von Microsoft nicht neu geöffnet wird habe ich einen neuen für VS-2010 Beta 1 angelegt.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=468860
Wer Lust hat, der kann ja abstimmen.

BTW: Die HaskKey Funktion in afxtempl.h ist für andere Maps (z.B. CMapPtrToPtr) direkt in der Klasse definiert. Dieses Template wird nur vom CMap-Template verwendet.

VC-2010: Breaking Changes in der STL

Ich habe mein erstes größeres Projekt mal testweise in VS-2010 übernommen und bin sofort über ein Problem in der STL gestolpert. Ein std::set<FOO>::iterator liefert in VC-2010 jetzt eine const FOO & Referenz!

Das Ändern von Objekten in einem std::set war sowieso nur erlaubt, wenn sich die Reihenfolge nicht ändert.

Die Folge ist, dass der nachfolgende Code in VC-2010 nicht mehr kompiliert.

#include <set>
struct S_FOO
{
    unsigned long     m_dw1, m_dw2;
    // simplified sample without bool operator<
};

class CMySet : public std::set<S_FOO>
{
public:
    void SomethingSpecial()
    {
        // Order must not be changed!
        for (iterator it=begin(); it!=end(); ++it)
            it->m_dw1 = 0;   // <----- C3892!
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    CMySet myset;
    myset.SomethingSpecial();
    return 0;
}

Man erhält den Fehler C3892: ‚it‘ : you cannot assign to a variable that is const

Das und noch einige andere Breaking Changes  wurden gestern in VC-Blog veröffentlicht:

http://blogs.msdn.com/vcblog/archive/2009/05/25/stl-breaking-changes-in-visual-studio-2010-beta-1.aspx

MSDN Abonnenten können ab Montag den 18.05.2009 Visual Studio 2010 Beta 1 herunterladen

Jihad Dannawi kündigt in seinem Blog die Veröffentlichung von Visual Studio 2010 Beta 1 für MSDN Subscriber an:
http://blogs.msdn.com/dannawi/archive/2009/05/15/visual-studio-2010-beta-1-available-for-the-msdn-subscribers-on-monday-may-18th.aspx

Dito kann man es auf ZDNet lesen:
http://blogs.zdnet.com/microsoft/?p=2769

Ich freue mich schon drauf, ich hoffe es findet sich Zeit mal wirklich damit spielen zu können und um herauszufinden ob der Slogan „The new 10 is the next 6“ wirklich trägt… 😉