MFC


MFCProgrammierenSoftwareWindowsWindows APIMartin Richter - Do 14 Jan 2010 20:10

Ich habe eine relativ komplexe UI, die auch dynamisch Controls erzeugt. In diese Controls werden auch zum Teil Massen an Daten hineingeschoben. Damit alle Controls zeitgleich erst die Daten präsentieren verwende ich eine einfache Methode, die aus alten Windows Tagen stammt: CWnd::SetRedraw/WM_SETREDRAW. Man verwendet diese Nachricht zum Beispiel um das Flackern von Listboxen und Comboboxen zu verhindern, wenn man viele Daten einfügt.
Diese Nachricht wird von allen Fenstern unterstützt oder sollte unterstützt werden ;)

Meine Software macht nun folgendes:

  • Zuerst hat meine Ladeprozedur für die Daten, zuerst alle Controls erzeugt, oder überflüssige vernichtet und positioniert, oder evtl. nur ausgeblendet (ShowWindow(SW_HIDE). D.h. nach dem ersten Laden der Daten ändert sich am Layout evtl. nichts mehr.
  • Anschließend wurde an alle Controls CWnd::SetRedraw/WM_SETREDRAW mit FALSE gesendet.
  • Dann die Daten geladen.
  • Nach dem Laden wird einfach wieder CWnd::SetRedraw/WM_SETREDRAW mit TRUE gesendet und ein Invalidate durchgeführt.

Das funktioniert für alle Controls, mit einer Ausnahme: Das RTF Control. Wenn man WM_SETREDRAW TRUE an ein RTF Control sendet, das nicht sichtbar ist, dann wird dieses sichtbar. Der Stil WS_VISIBLE wird also verändert. :eek:

Um das Problem zu isolieren habe ich hier ein kleines Testprogramm geschrieben. Der kritische Code sieht so aus. Das gesamte Projekt kann man hier auch herunterladen: Demoprojekt.

void CTestRTFSetRedrawDlg::OnBnClickedBtDoit()
{
 bool bWasVisible = (m_wndEdRTF.GetStyle() & WS_VISIBLE)!=0;
 m_wndEdRTF.SetRedraw(FALSE);
 m_wndEdRTF.SetWindowText(_T("Line 1\r\nLine 2\r\nLine 3\r\nLine 4"));
 m_wndEdRTF.SetSel(0,0);
 m_wndEdRTF.SetRedraw(TRUE);
 m_wndEdRTF.Invalidate();
 bool bIsVisible = (m_wndEdRTF.GetStyle() & WS_VISIBLE)!=0;
 
 // Check if the visible state changed
  if (bIsVisible!=bWasVisible)
  AfxMessageBox(_T("The visible state of the RTF control changed!"));
}

Nachtrag 16.01.2010 (Danke Sven für Deinen produktiven Kommentar):
Auch andere Controls wie Button-, Static- und Edit-Controls verändern den Visible Status wenn WM_SETREDRAW angewendet wird. Einzig Listbox- und Combobox-Controls behalten den Visiblestatus korrekt bei :!:

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++MFCProgrammierenSonstigesWindows APIMartin Richter - Mo 04 Jan 2010 17:43

Die Frage um die Eingabe-Taste in Dialogen und wie man diese “missbraucht” (sage ich mal provokant) ist eine regelmäßige Frage in allen Foren.

Die Intention ist oft klar. Man möchte mit der Eingabetaste eine bestimmte Aktion verbinden, die evtl. sehr oft ausgeführt werden soll und nicht den Dialog schließen.
Die üblichen Wege sind schon mehrfach diskutiert worden und auch in meinem Blog finden sich dazu ein Artikel .

Zu kurz kommt bei dieser Diskussion die Funktion CDialog::SetDefID bzw. DM_SETDEFID Nachricht.
Was macht diese Funktion/Nachricht?
Sie definieren die Button-ID, die als Default-Aktion in einem Dialog ausgelöst werden soll und das ist nichts anderes als die Aktion die geschehen, soll wenn die Eingabe-Taste gedrückt wird.
Viele Entwickler definieren einfach OnOK um. Aber das eigentlich tolle ist mit SetDefID den Button in Abhängigkeit der Daten umzusetzen und das hat auch einen visuellen Effekt für den Nutzer.

Mal ein Beispiel:
Wir haben einen Dialog mit zwei List Views. Links Elemente die zur Auswahl stehen, rechts die Elemente in der Reihenfolge, die der Benutzer ausgewählt hat.
Der Mausschubser wird einfach die Einträge auf der linken oder rechten Seite doppelklicken und damit auswählen oder entfernen. Entsprechende Buttons für Hinzufügen und Entfernen wird es auch geben. Man kann also auch links oder rechts markieren und dann den Hinzufügen oder Entfernen Schalter nutzen.

Dem Tastaturnutzer können wir helfen indem wir intelligent CDialog::SetDefID / DM_SETDEFID verwenden. Die Vorgehensweise ist einfach.

  • Wir richten uns nur danach in welchem List View wir uns befinden, d.h. befinden wir uns im linken List View steuern wir den Hinzufügen Schalter, und im rechten List View steuern wir den Entfernen Schalter.
  • Wird also im linken List View ein Item ausgewählt, setzen wir mit CDialog::SetDefID / DM_SETDEFID die ID des Hinzufügen Schalters.
  • In dem Moment wird der Hinzufügen Schalter zum Default-Button. Der Nutzer kann nun die Eingabe-Taste drücken und die Items werden in die rechte Box verschoben.
  • Links liegt jetzt nun noch der Fokus, aber es sind keine Items mehr markiert. D.h. wir setzen nun den Default Button zurück auf IDOK.
  • Jetzt kann der Nutzer erneut ein Item markieren. Der Default-Button wird wieder der Hinzufügen/Entfernen Schalter und die Eingabetaste macht was der Nutzer gerne hätte.
  • Ist kein Item mehr markiert schließt die Eingabetaste wieder über IDOK den Dialog.

Ohne Maus kann man also mit den Pfeiltasten, der Leertaste (evtl. Strg-Taste) und der Eingabetaste diesen Dialog bedienen. Und das sogar intuitiv, denn der entsprechende Default Button wird ja in der UI schön umrandet und hervorgehoben.
Das freut jeden Tastaturfreund. Und man muss gar nichts groß machen mit der Behandlung Eingabetaste.

Damit das Ganze nicht so abstrakt ist, habe ich ein kleines Sample gebaut, dass diese Anwendung zeigt. Es hat keine Implementierung für Drag&Drop aber es macht deutlich, wie man dem Tastaturnutzer entgegen kommen kann indem man die Controls geschickt aktiviert.

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++MFCProgrammierenWindows APIMartin Richter - Do 03 Dez 2009 20:57

Jeder MFC Entwickler kennt AfxMessageBox. In der Klasse CWnd finden wir auch die Memberfunktion CWnd::MessageBox.
Mancher Entwickler wird sich nun fragen: Wann nehme ich denn nun was?

Kurze Antwort: Meide CWnd::MessageBox und nutze immer AfxMessageBox :!:

Lange Antwort:
CWnd::MessageBox ist einfach nur ein direkter Wrapper für Windows API Funktion MessageBox.
Der Grund warum man AfxMessageBox verwenden sollte liegt darin was AfxMessageBox einfach noch mehr leistet:

  1. AfxMessageBox benutzt die virtuelle Funktion CWinApp::DoMessageBox. Damit kann man zentral eine andere Behandlung für Fehlermeldungen einbauen.
  2. AfxMessageBox sorgt dafür, dass OLE Controls benachrichtigt werden, dass Ihre nicht modalen Dialoge nun deaktiviert werden müssen. Es wäre ja ziemlich heftig, wenn man solche Dialoge trotz aktiver MessageBox noch benutzen könnte. (siehe CWinApp::DoEnableModeless und Implementierung von CWnd::GetSafeOwner)
  3. CWnd::MessageBox benutzt das aktuelle Fensterhandle aus dem es aufgerufen wird als Parent-Handle für die API Funktion MessageBox. Die wenigsten Entwickler kennen, lesen oder beachten den netten Zusatz in der MessageBox Doku:
    If you create a message box while a dialog box is present, use the handle of the dialog box as the hWnd parameter. The hWnd parameter should not identify a child window, such as a control in a dialog box.
    Es ist also gerade zulässig CWnd::MessageBox aufzurufen, denn oft genug haben wir es mit Child-Windows zu tun.
    Netterweise beachtet AfxMessageBox genau das. Es ermittelt das aktuelle aktive Top-Level Fenster und setzt es intern für den Aufruf von MessageBox.
    Das wird auch ganz besonders wichtig, wenn man evtl. mehrere UI Threads hat. AfxMessageBox verwendet automatisch den richtigen. CWnd::MessageBox verwendet den aktuellen Thread aber evtl. als Fenster das Fenster-Handle aus einem anderen Threads.
  4. CWnd:MessageBox hat keine Implementierung für die F1-Taste und die Hilfeimplementierung der MFC.
  5. Ich muss mich nicht um den Titel der MessageBox kümmern, denn AfxMessageBox benutzt den hinterlegten Applikationsnamen (CWinApp::m_pszAppName) der in der MFC hinterlegt und definiert wurde.
  6. Netterweise setzt AfxMessageBox passende Icons wenn nur die Reaktionsform definiert wird, also kein Icon. (MB_OK und MB_OKCANCEL benutzt MB_ICONEXCLAMATION, MB_YESNO und MB_YESNOCANCEL benutzt MB_ICONQUESTION).

HTH

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++DebuggingMFCProgrammierenMartin Richter - Sa 05 Sep 2009 10:52

Neulich bei einem Codereview, lief mir Code in den beiden nachfolgenden Formen über meinen Monitor:

BOOL CMyDialog::PreTranslateMessage(MSG *pMsg)
{
    // Translate Messages (ON_KEYDOWN -> ON_COMMAND)
    TranslateAccelerator(m_hWnd,m_hAccel,pMsg);
    // Let the ToolTip process this message.
    m_tooltip.RelayEvent(pMsg);
    return __super::PreTranslateMessage(pMsg);
}

- bzw.  -

BOOL CMyWnd::PreTranslateMessage(MSG *pMsg)
{
  BOOL bResult = __super::PreTranslateMessage(pMsg);
  DoSomething();
  return bResult;
}

Sieht OK aus, aber hier tritt ein grundsätzliches Problem auf:

In beiden Funktionen wird evtl. eine Nachricht behandelt. Allerdings wird in diesem Fall nicht umgehend die Funktion verlassen. Durch das Behandeln der Nachricht kann nämlich das Fenster/Objekt, zu dem PreTranslateMessage gehört, bereits zerstört sein. In diesem Fall kehrt PreTranslateMessage zurück und der this Zeiger ist bereits ungültig.

Es ist also imminent wichtig in dem Moment in dem erkannt wird, dass die Nachricht behandelt wurde, auch umgehend die Funktion mit TRUE zu verlassen und keine weitere Memberfunktion oder gar Membervariable mehr zu nutzen. Beides könnte zu einem üblen Crash führen.

Der korrekte Code sähe also so aus:

BOOL CMyDialog::PreTranslateMessage(MSG *pMsg)
{
  // Translate Messages (ON_KEYDOWN -> ON_COMMAND)
  if (TranslateAccelerator(m_hWnd,m_hAccel,pMsg))
    return TRUE;
  // Let the ToolTip process this message.
  m_tooltip.RelayEvent(pMsg);
  return __super::PreTranslateMessage(pMsg);
}

- oder -

BOOL CMyWnd::PreTranslateMessage(MSG *pMsg)
{
  if (__super::PreTranslateMessage(pMsg))
    return TRUE;
  DoSomething();
  return FALSE;
}

PS: Beide Codeteile wurden durch Crashdumps aus WinQual gefunden. Regelmäßig, alle 2 Monate schaue ich mir Dumps an, von den Top-Crashes, die dort verzeichnet sind, und mache entsprechende Code-Reviews.
Der erste Code, stammte aus einem speziellen nicht modalen Dialog, der durch Drücken bestimmter Tastenkombinationen geschlossen und zerstört wurde.
Der zweite Code stammte aus einem Popup-Fenster, dass auch durch Mausaktivitäten oder Tastendrücke zerstört wurde.
Ich kann jedem nur raten WinQual auch zu nutzen, es dient der Qualtitätssicherung und man findet viele kleine Bugs, die manchen User ärgern, die aber nie sonst gemeldet würden.

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++CRTDebuggingMFCProgrammierenVS 2008VS 2010VS-Tipps&TricksMartin Richter - So 30 Aug 2009 16:10

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.

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
DebuggingMFCProgrammierenVS 2008VS 2010VS-Tipps&TricksWindows APIMartin Richter - So 09 Aug 2009 18:50

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());
}
  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++MFCProgrammierenVS 2008VS 2010Martin Richter - Sa 01 Aug 2009 18:26

 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:

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++MFCProgrammierenVS 2008VS 2010Martin Richter - Mi 29 Jul 2009 17:50

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

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
MFCProgrammierenVS 2008VS 2010Martin Richter - So 14 Jun 2009 12:51

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.

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter
C++CRTDebuggingMFCProgrammierenMartin Richter - So 07 Jun 2009 12:08

Ich verwende gerne ASSERT’s in meinem Code.
Sie sind ein wirksames Mittel Zustände abzufragen und bereist in der Testphase unzulässige Konstellationen oder Funktionsaufrufe zu entdecken.

Nun gibt es ja auch if, else oder switch Blöcke an denen das Programm normalerweise nicht vorbei kommen sollte. So eine Stelle versieht man dann schon mal mit einem

_ASSERT( FALSE );
// oder wer die MFC benutzt eben ASSERT,
// obwohl dies auch nur ein Synonym für den CRT _ASSERT makro ist
ASSERT(FALSE);

Jetzt müsste man noch einen Kommentar davor setzen, damit klar wird was hier schief läuft. Man kann das Ganze aber auch einfach kombinieren und noch einen Zusatznutzen erreichen indem man den etwas unbekannteren Makro _ASSERTE verwendet:

_ASSERTE( !"MyFuncFooHandler: This is not allowed in my special Foo Handler" );

Die Negation macht aus dem Zeiger auf den konstanten String FALSE, und damit schlägt der ASSERT an.
Wenn man jetzt wie hier gezeigt noch den _ASSERTE Makro verwendet, dann wird diese Expression, also der Text, sofort mit anzeigt. Man sieht dann sofort was das Problem ist sobald der ASSERT Dialog angezeigt wird.

  • Add to favorites
  • Google Bookmarks
  • del.icio.us
  • MisterWong.DE
  • Technorati
  • Facebook
  • Live
  • Twitter

Nächste Seite »