MFC


MFCProgrammierenVista / Windows 7VS 2008Martin Richter - Do 14 Apr 2011 10:12

Betroffen sind:

  • Alle Programme die mit MFC 8.0 oder MFC 9.0 erzeugt wurden und dynamisch an die MFC DLLs gelinkt sind.
  • Alle Betriebssysteme ab Windows XP aufwärts. 32bit wie 64bit
  • Alle Programme, die nicht die MFC und CRT DLLs applikationsnah (d.h. im Programmverzeichnis, siehe dazu auch die Fußnote in meinem Artikel) installiert haben. Also alle Programme die WinSxS benutzen und die VCRedist_x86.exe ( VCRedist_x64.exe)  mit ausliefern.
  • Alle Programme, die lokalisiert sind und die MFC90xxx.DLL bzw. MFC80xxx.DLL Sprach-DLLs verwenden und das OS nicht auf Englisch eingestellt ist

Betrifft die folgenden Fixes vom 12.04.2011:

Für VS-2005 SP1 http://support.microsoft.com/kb/2465367 und http://support.microsoft.com/kb/2467175
Für VS-2008 SP1 http://support.microsoft.com/kb/2465361 und http://support.microsoft.com/kb/2467174

Effekt:

Die MFC Statelite DLLs werden nicht geladen. Teile der Anwendung erscheinen in englischer Sprache.

Hintergrund:

Um das Laden von falschen Satelite DLLs zu verhindern (Binary Planting), wurde intern in appcore.cpp in der Funktion _AfxLoadLangDLL geprüft, ob die DLLs in aus einem Activation Context geladen werden oder nicht. Sollte ein Activation Context vorhanden sein, dann kann man gefahrlos die Sprach DLLs (MFCDEUxxx.DLL etc.) ohne Pfadnamen laden. Ist kein Activation Context vorhanden wird der Pfad der Anwendung verwendet und LoadLibrary mit vollem Pfadnamen durchgeführt.

Der Code der dazu verwendet wird sieht so aus (Leerzeilen entfernt):

...
TCHAR *pszFilename = ::PathFindFileName(szLangDLL);
ACTCTX_SECTION_KEYED_DATA data;
if (FindActCtxSectionString(
    FIND_ACTCTX_SECTION_KEY_RETURN_HACTCTX,
    NULL,
    ACTIVATION_CONTEXT_SECTION_DLL_REDIRECTION,
    pszFilename,
    &data) )
{
    // Load using the dll name only...
    hInstance = ::LoadLibraryEx(pszFilename, NULL, 0);
}
else
{
    // Load using the full path...
    hInstance = ::LoadLibraryEx(szLangDLL, NULL, 0);
}
...

Eigentlich sieht der Code prima aus. Und er verträgt sich auch mit der Doku von FindActCtxSectionString dort wird der letzte Parameter als __out definiert.

BOOL FindActCtxSectionString(
  __in   DWORD dwFlags,
  __in   const GUID *lpExtensionGuid,
  __in   ULONG ulSectionId,
  __in   LPCTSTR lpStringToFind,
  __out  PACTCTX_SECTION_KEYED_DATA ReturnedData
);

Aber die Doku zu ACTCTX_SECTION_KEYED_DATA sagt was anderes:

Callers should initialize the ACTCTX_SECTION_KEYED_DATA structure as such:
“ACTCTX_SECTION_KEYED_DATA askd = { sizeof(askd) };”
which initializes all members to zero/null except the size field which is set correctly.

(BTW: Auch ein krasser Doku-Bug in meinen Augen)

Und jetzt sieht man was dem Code fehlt: data.cbSize wird nicht gesetzt :!:
Daraus ergeben sich nun drei Varianten, da data.cbSize nun zufälligen (nicht initialisierten) Inhalt hat:

  1. data.cbSize ist größer als  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In diesem Fall wird korrekt ermittelt ob ein Activation Context vorhanden ist. Das Programm läuft normal. Mit Activation Context ist kein voller Pfadname nötig. Die MFC90xxx.DLL wird evtl. aus dem WinSxS (Side by Side) geladen, oder in einem der Suchpfade gefunden.
  2. data.cbSize ist kleiner als  sizeof(ACTCTX_SECTION_KEYED_DATA):
    In diesem Fall liefert FindActCtxSectionString einen Fehler und nun wird es spannend. Die DLL wird nun versucht mit dem vollen Pfadnamen zu laden um Binary Planting zu verhindern. Das Problem ist aber dass bei korrekter Installation im WinSxS, dass im Applikationsverzeichnis keine dieser Daten liegen. Die DLL wird nicht gefunden.
    Sollten die private applikationsnahe Assemblies in einem Unterverzeichnis installiert sein, werden diese auch nicht gefunden.
  3. Für die Zukunft.
    Ein zukünftiges OS vergrößert ACTCTX_SECTION_KEYED_DATA und data.cbSize hat zufälligen Inhalt und ist größer als sizeof(…):
    Ein Buffer-Overrun!

Ich empfehle nicht ohne Grund seit VS-2005 private Assemblies zu verwenden, und die MFC Dateien in das Anwendungsverzeichnis zu kopieren. Dazu habe ich auf Code-Projekt einen entsprechenden Artikel geschrieben und ein Hotfix für VS-2008 existiert auch :!:
Create projects easily with private MFC, ATL and CRT assemblies
Hotfix für UseMSPrivateAssemblies.h und VC-2008

Was ist zu tun?

Deinstallation aller hier erwähnten Sicherheitspatches mit den entsprechenden Arikelnummern:
Runtime-2005: KB2467175, Runtime-2008: KB2467174
VS-2007 SP1: KB2465367, VS-2008 SP1: KB2465361).

Weitere Anmerkungen:

Die betroffenen C/C++ Runtimes des Visual Studio haben die folgenden Versionsnummern
- VC-2005 8.0.50727.5592 (KB2467175)
- VC-2008 9.0.30729.5570 (KB2467174)

Mein Kommentar dazu:
Das Leben in der DLL-Hölle war fast angenehmer als das hier. Ohne Worte :(

Herzlichen Dank auch an meinem Mit-MVP Mike Ryan, der mit mir zusammen auf diese gesamte Problematik gestoßen ist :!:

Was meine ich mit “application local”?
Einige Entwickler installieren die MFC runtme Dateien im Applikationsverzeichnis. In diesem Fall werden diese DLLs nicht verwendet wenn eine neuere Version der DLLs im WinSxS Verzeichnis liegen. Das ist für mich keine applikationsnahe Instalation! Diese Manifeste im Programmverzeichnis haben immer noch einen publicKey Eintrag. Aber durch die Existenz der lokalen Dateien wird dieses hier beschriebene Problem umgangen, weil die lokalen Dateien eine Art Fallback bilden.
Meine Artikel beschriben wie man eine Anwendung wirklich applikationslokal macht und damit unabhängig von solchen “kaputten” Security Patches. Dazu muss der publicKey Token aus den Manifesten entfernt werden. (Lesen Sie meinen Artikel aufCodeproject).
(Danke an Co-MVP David Ching der mich um Kläurung gebeten hat)

C++MFCProgrammierenVS 2008Martin Richter - Mi 13 Apr 2011 22:48

Wer VS-2005 SP1 oder VS-2008 SP1 installiert hatte und bei dem auch die entsprechenden Patches von gestrigen Tag (12.04.2011) durchlaufen wurden, der hat nun auch veränderte statische Libraries.

Sollte man nun also EXEs oder DLLs mit den neuen Libararies statisch linken, dann sind diese genausowenig lauffähig unter Windows 2000. wie auch die EXEs und DLLs die gegen die MFC 8.0 bzw. MFC 9.0 DLLs gelinkt werden :!:

Das Ganze ist hier aufgelistet:
für VS-2005 SP1 http://support.microsoft.com/kb/2465367
für VS-2008 SP1 http://support.microsoft.com/kb/2465361

Die LIBs sind aufgeführt und auch diese verwenden auch die Funktion FindActCtxSectionStringA, die natürlich nicht unter Windows 2000 vorhanden ist.

Siehe auch:
http://blog.m-ri.de/index.php/2011/04/13/bug-schwarzer-patchday-fur-windows-2000-mfc-8-0-vc-2005-und-mfc-9-0-vc-2008-dlls-sind-nicht-mehr-lauffahig-nach-installation-von-kb2467175-bzw-kb2467175/

PS: Ich kann nur raten die entsprechenden Patches zu deinstallieren sofern man noch für Windows 2000 entwickelt und warten bis neue Securitypatches vorhanden sind.

Nachtrag:
Die betroffenen C/C++ Runtimes des Visual Studio haben die folgenden Versionsnummern
- VC-2005 8.0.50727.5592 (KB2467175)
- VC-2008 9.0.30729.5570 (KB2467174)

MFCProgrammierenVS 2008Martin Richter - Mi 13 Apr 2011 17:35

Ein wirklich übler Patchday von Microsoft für ale Windows 2000 Nutzer.
In den Sicherheitsfixes für Visual Studio 2005 und 2008 wurden auch die Runtime Module der MFC 8.0 und MFC 9.0 “gefixed”.
Siehe http://support.microsoft.com/kb/2467175 für VS-2005
Siehe http://support.microsoft.com/kb/2467174 für VS-2008

Es wurde ein Fix eingebaut, den wir schon in VS-2010 gesehen haben, der vermeiden soll, dass Satelite DLLs nur noch aus dem Verzeichnis geladen werden, in dem auch die MFC DLLs liegen
(gegen ‘Binary Planting’, d.h. unterschieben gefakter DLLs aus anderen Verzeichnissen).

Leider wurde dabei die Funktion FindActCtxSectionStringA mit eingebaut und verwendet. Eine Funktion die aber erst ab Windows XP vorhanden ist. Sofern man also eine Anwendung unter Windows 2000 hat, die die MFC80.DLL, MFC80U.DLL, MFC90.DLL oder MFC90U.DLL aus dem WINDOWS\SYSTEM32 Verzeichnis verwendet, dann hat man seit dem dem Patchday von gestern ein massives Problem. Die DLLs können nicht mehr geladen werden. :shock:

Einziger Rat ist hier, die neuen Dateien löschen und die ungepatchten Versionen installieren und das am besten direkt in den Programmverzeichnissen :!:
Alte Runtimes für VS-2005 SP1 http://www.microsoft.com/downloads/en/details.aspx?FamilyID=200b2fd9-ae1a-4a14-984d-389c36f85647
Alte Runtimes für VS-2008 SP1 http://www.microsoft.com/downloads/en/details.aspx?FamilyID=a5c84275-3b97-4ab7-a40d-3802b2af5fc2
Alle Versionen mit den VCRedist_x86.exe Versionen und dem Datum vom 13.04.2010 sind für Windows 2000 tabu.
Das heißt auch, dass man diese DLLs nicht in seinem eigenen Setup für Windows 2000 ausrollen sollte.

Nicht betroffen sind Applikationen, die diese DLLs applikationsnah, d.h. im Programmverzeichnis installieren.
Eine Methode, die ich seit Jahren empfehle!

Nur noch einmal zur Klarstellung: Der Sicherheitspatch ist nur problematisch für Windows 2000 Rechner :!:

PS: Was bin ich froh, dass wir Windows 2000 schon lange nicht mehr unterstützen :mrgreen:

PPS: Bei solch einem Fehler frage ich mich ob hier irgend jemand überhaupt etwas getestet hat, außer das die Installation durchgeführt wird.

Nachtrag:
Die betroffenen C/C++ Runtimes des Visual Studio haben die folgenden Versionsnummern
- VC-2005 8.0.50727.5592 (KB2467175)
- VC-2008 9.0.30729.5570 (KB2467174)

Workaround:
Workaround für Patchday Bug vom 12.04.2011: Wenn unter Windows 2000 der Einsprungpunkt FindActCtxSectionStringA nicht gefunden wird

C++MFCProgrammierenVS 2010Martin Richter - Do 17 Mrz 2011 21:08

Ich habe mir mal die Mühe gemacht alle Änderungenan der MFC, die im VS-2010 SP1 enthalten sind, hier im Detail aufzuführen.

Viele Änderungen sind es nicht, wie man schnell sieht. Ich empfehle als weitere Quelle für Infos über den SP1 die bekannten Blogs des MS-C++ Teams und die KB.
http://blogs.msdn.com/b/vcblog/archive/2011/03/10/10139062.aspx
http://support.microsoft.com/kb/983509

Neue Samples finden sich nach dem SP1 Setup auf der Festplatte im folgenden Ordner:
C:\Program Files\Microsoft Visual Studio 10.0\Samples\1033\VC2010SP1Samples.zip

Für entsprechende bekannte Fixes habe den Text aus den Blogs übernommen. Alle anderen Änderungen habe ich durch einen Vergleich des Sourcecode mit WinMerge ermittelt. Codeänderungendie ich nicht direkt einem Fehler zuordnen konnte habe ich mit dem Prefix Change, markiert und natürlich die neuen Features entsprechend.

In einem späteren Artikel werde ich mich noch die Änderungenan CRT und STL genauer unter die Lupe nehmen.

Neues Feature: Direct2D Unterstützung (dokumentiert)

Direct2D, a hardware-accelerated, immediate-mode, 2-D graphics API that provides high performance and high-quality rendering for 2-D geometry, bitmaps, and text. For more information, visit the following Microsoft website: Direct2D.
http://msdn.microsoft.com/en-us/library/dd370990.aspx
http://msdn.microsoft.com/en-us/library/gg482719.aspx

Geänderte Dateien:

  • afx.h, afxglobals.h, afxrendertarget.h, afxwin.h
  • afxglobals.cpp, afxrendertarget.cpp, appui3.cpp, wincore.cpp

Anmerkung: Die Verwendung von Direct2D führt dazu, dass CoInitialize ausgeführt wird.

Neues Feature: Windows Animation Manager Unterstützung (dokumentiert)

Windows Animation Manager, which enables rich animation of user interface elements. For more information, visit the following Microsoft website: Windows Animation Manager.
http://msdn.microsoft.com/en-us/library/gg482719.aspx

Geänderte Dateien:

  • afxanimationcontroller.h, afxanimationhelper.h, afxwin.h
  • afxanimationcontroller.cpp

Bugfix: Korrektur eines Fehlers in den RDX Kompontenten (dokumentiert)

In the CDatabase/Crecordset MFC, the “DoFieldExchange” variable does not work correctly in Visual Studio 2010.
http://connect.microsoft.com/VisualStudio/feedback/details/574974

Geänderte Dateien:

  • dbrfx.cpp

Bugfix:  Umgang mit SPI_GETNONCLIENTMETRICS in unterschiedlichen Windows Versionen (nicht dokumentiert)

Der interne Umgang mit unterschiedlichen Windows Versionen und SPI_GETNONCLIENTMETRICS wurde gefixed.

Geänderte Dateien:

  • afxglobals.cpp, afxribbonbar.cpp, afxvisualmanageroffice2007.cpp, afxvisualmanagerwindows7.cpp

Change: Cleanup der MFCNext Klassen (nicht dokumentiert)

Das Cleanup für die neuen MFC-Next Klassen wurde geändert.

Geänderte Dateien:

  • afxcontrolbarutil.h
  • afxglobals.cpp, afxwinappex.cpp, ctlmodul.cpp

Bugfix: Fehler bei Anzeige in CFormView Klassen (nicht dokumentiert)

VC-2010 MFC CFormViewzeichnet Buttons beim Rollenfalsch, es erscheinen schwarze Blöcke
http://blog.m-ri.de/index.php/2010/08/28/bug-vc-2010-cformview-zeichnet-buttons-beim-rollen-falsch-es-erscheinen-schwarze-bloecke/

Geänderte Dateien:

  • afxext.h
  • viewform.cpp

Bugfix: CImageList::DrawIndirect wurde korrigiert (nicht dokumentiert)

CImageList::DrawIndirect funktioniert nicht korrekt weil cbSize nicht initialisiert wurde
http://blog.m-ri.de/index.php/2010/07/21/bug-in-der-mfc-von-vc-2010-in-cimagelistdrawindirect/

Geänderte Dateien:

  • winctrl7.cpp

Change: Änderung des Suchalgorithmus für MFC Satellite DLLs (nicht dokumentiert)

Das Handling des Suchalgorithmus für die MFC Resource DLLs wurde geändert

Geänderte Dateien:

  • appcore.cpp, dllinit.cpp

 

PS:
Erstaunlich, dass zwei der Bugs, die ich zur MFC gemeldet habe auch direkt in diesem SP1 gefixed wurden. Das ist eine Quote, die ich in den letzten 12 Jahren bei einem SP noch nie hatte :)

Nachtrag und Ergänzungen am 18.03.2011
durch Sven von http://www.speedproject.de

Infos zum Laden der dwmapi.dll

Die Datei dwmapi.dll wird nun explizit aus dem Systemverzeichnis geladen.

Geänderte Dateien:

  • afxglobals.cpp

Infos zum Suchalgorithmus der MFC DLLs

Die Änderung des Suchalgorithmus für die zusätzlichen Sprachressourcen ist ebenfalls eine Schutzmaßname gegen das ‘Binary Planting’. Die Dlls werden jetzt nur noch aus dem Verzeichnis geladen, in dem sich die MFC-Dll befindet.

Infos zu dem geänderten Clennup der MFCNext Klassen

Der Grund für die Änderung beim Aufräumen ist wohl ein mögliches Speicherleck beim Beenden:
http://connect.microsoft.com/VisualStudio/feedback/details/577870/cmfcbutton-causes-memory-leak

Danke Sven!

C++CRTMFCProgrammierenVS 2008VS 2010Martin Richter - Mo 27 Dez 2010 09:33

In VS-2005 hielt die Unicode und UTF-8 Unterstützung Einzug in die CRT und damit auch in die MFC. Jochen und ich haben dazu gebloggt.

Leider ist die Implementierung nicht fehlerfrei. Wenn man eine UTF-8 Datei mit ccs=UNICODE öffnet dann liefert ftell zum Teil auch negative Werte. Das ftell nicht die exakte Position in der Datei liefert ist dokumentiert, nur sollte es mit dem Wert zumindest möglich sein wieder fseek aufzurufen um an eine alte Dateiposition zurück zu gelangen.
Das ist mit der jetzigen Implementierung nicht möglich.

Ein Sample dazu ist schnell gebaut:

int _tmain()
{
  FILE *file = NULL;
  if (_tfopen_s(&file,_T("Test.txt"),_T("rt") _T(", ccs=UNICODE")))
    return -1;
 
  TCHAR szBuffer[_MAX_PATH];
  while (_fgetts(szBuffer,_countof(szBuffer),file))
  {
    int iPos = static_cast(ftell(file));
    _tprintf(_T("%d\n"),iPos);
    _ASSERTE(fseek(file,iPos,SEEK_SET)==0);
  }
 
  fclose(file);
  return 0;
}

Ein entsprechendes Sample kann man hier herunterladen.

Der Fehler betrifft natürlich auch die MFC mit CStdioFile und GetPosition!

Hintergrund:
Ich kam auf den Fehler, weil wir ein Programm benutzen, dass sich bestimmte Dateipositionen merkte um einen Datenimport bei einer bestimmten Bedingung, ab einer definierten Stelle, neu aufnehmen zu können. Bisher hatten wir hier nur pure ASCII Dateien erlaubt, jetzt wollten wir auch UTF-8 Dateien unterstützen.
Sicher man kann auch alles im Speicher zwischenhalten, aber wenn es eine simple Dateiposition ist das Ganze so weitaus Ressourcen schonender.

Soweit ich das übersehen kann, betrifft der Fehler alle VS-Versionen ab VS-2005. Ein entsprechende Bug wurde von mir schon vor längerer Zeit auf Connect geöffnet. Getan hat sich bisher noch nichts:
https://connect.microsoft.com/VisualStudio/feedback/details/591030/ftell-returns-negative-value-for-utf-8-files-opened-with-textmode-and-ccs

C++MFCProgrammierenWindows APIMartin Richter - Sa 04 Dez 2010 17:46

Wenn man eine Windowsnachricht bearbeitet dann ruft man in den meisten Fällen die Funktion der Elternklasse auf. Was aber, wenn man die Nachricht nicht nur entgegennehmen will, sondern sie “verändert” an die Basisfunktion weitergeben will. Was dann?
Kein Problem denkt man. Man ändert die Parameter eben und gibt diese neuen Werte an die Basisklasse weiter.

Nehmen wir mal als Beispiel ein Eingabe Control, das sich möglichst intelligent verhalten soll. D.h. in diesem Fall, wenn ein Datum eingegeben wird, dann sollte auch bei der Eingabe auf numerischen Ziffernbock, das Komma in einen Punkt gewandelt werden, oder bei englischem Datumsformat in einen Slash…

Na OK. Man überschreibt als CMyWnd::OnChar. Man prüft ob ein bestimmtes Zeichen ankommt und wenn es mir passt, dann ändere ich den Parameter und gebe diesen dann an die Basis Funktion weiter.

:Eeek: aber was ist das? Der Code hat keine Wirkung? Egal was wir an die Basisfunktion übergeben, es ändert sich nichts. Warum?

Die Antwort liegt in dem für mich etwas eigentümlichen Design der MFC. Alle CWnd Nachrichten Routinen rufen letzten Endes immer genau ein und die selbe Funktion auf: CWnd::Default(); Aber was macht CWnd::Default? Es nimmt die ursprünglichen Parameter, die Windows mal gesendet hat und übergibt die an die Window-Proc des Fensters. D.h. alle tollen Manipulationen an der WM_CHAR Nachricht sind weg in dem Moment in dem CWnd::OnChar aufgerufen wird.

Was also tun, wenn man nun aber wirklich die WM_CHAR Nachricht manipulieren will?
Eigentlich nicht schwer. Man macht das Gleiche, dass eben auch CWnd::Default macht. Man ruft DefWindowProc mit den passenden wParam und lParam Werten auf.

Aber jetzt wird es dumm :!: Damit umgehen wir alle anderen geerbten Funktionalitäten des Controls. Ich habe keine Ahnung was die Entwickler der MFC in der Anfangszeit dazu getrieben hat immer CWnd::Default aufzurufen? War es Ihnen zu kompliziert wieder aus den Parametern wParam und lParam zusammen zu bauen?

C++MFCProgrammierenVS 2010Windows APIMartin Richter - Sa 06 Nov 2010 20:32

Wer Double Buffering benötigt und die MFC nutzt, der kennt auch CMemDC. Vermutlich eine der meist genutzten und kopierten Klassen, die auf CodeProject und CodeGuru vorgestellt wurden.
http://www.codeproject.com/KB/GDI/flickerfree.aspx
http://www.codeguru.com/cpp/misc/misc/flickerfreedrawing/article.php/c389/Flicker-free-drawing-using-memory-DC.htm

Ich habe meine Erweiterung hier im Blog vorgestellt und die liegt normalerweise in einem separaten Namespace, wie alle meine Tool-Klassen.

Nicht schlecht staunte ich, als ich keinen Compilerfehler bekam obwohl ich CMemDC nutzte aber keinen Namespace angab. Siehe da: CMemDC hat in einer eigenen Implementierung den Weg in die MFC gefunden. Man findet sie in:
C:\Program Files\Microsoft Visual Studio 10.0\VC\atlmfc\include\afxcontrolbarutil.h

Im Großen und Ganzen ist es die bekannte Standard-Implementierung, allerdings verfügt diese CMemDC Version auch Code, der auf Windows Vista und Windows 7, die fest im Betriebssystem verankerten Funktionen nutzt: BeginBufferedPaint, EndBufferedPaint
Siehe http://msdn.microsoft.com/en-us/library/bb773178(VS.85).aspx
Diese Funktionen werden innerhalb des Themeings von Windows Vista und Windows 7 verwendet und in dieser Funktionsgruppe ist auch Alphablending direkt verankert. (BufferedPaintSetAlpha). Ich vermute sogar, dass diese integrierten Klassen effektiver arbeiten, als die eigenen Klassen (ein Test steht noch aus), denn Windows weiß intern natürlich viel besser was wie zu puffern und zu zeichnen ist, als wir, wenn WM_PAINT aufgerufen wird.

Vielleicht ein guter Grund, die eigene CMemDC Klasse auch auf Vista/Windows 7 Funktionen zu erweitern oder die integrierte Klasse in der MFC zu verwenden.

Tipp: Übrigens hat die MFC CMemDC Klasse einen statischen Member, der es auf einfache Weise erlaubt das Double-Buffering abzuschalten (CMemDC:: m_bUseMemoryDC), dass ist besonders interessant beim Debuggen von grafischen Operationen, deren Ergebnisse man auch gleich sehen will, allerdings wird dieser Member nicht benutzt wenn das interne Windows Double-Buffering genutzt werden kann, schade eigentlich.

PS: Aber eigentlich muss man sich auch die Frage stellen, warum die Entwickler genau diesen Klassennamen verwendet haben, denn er provoziert ja auch direkt den Konflikt mit existierendem Code.

ATLC++DebuggingMFCProgrammierenMartin Richter - Fr 10 Sep 2010 17:41

In ATL und MFC steckt ein ziemlich ausgeklügelter Trace-Mechanismus. Wenn man sich das MFC – ATL Trace Tool ansieht kann man zu allen möglichen Kategorien Informationen in Debug Fenster ausgeben lassen.
Alleine die MFC hat 14 verschiedene Trace Kategorien. Darunter besonders interessante wie CommandRouting, AppMsg und WinMsg. Die ATL hat weitere 27 Kategorien.
Es lohnt sich mal einen Blick in dieses Tool und die entsprechenden Ausgaben zu machen. Es gehört zu den oft unbekannten netten Helferlein, die leider mangels Bekanntheit selten benutzt werden.

Um Fehler zu finden und einzugrenzen, sind mir jedoch oft eher zu viele Ausgaben vorhanden, als zu wenige. Zudem finde ich es manchmal unhandlich mit dem Trace-Tool die Nachrichten ab einem bestimmten Moment einzuschalten und wieder auszuschalten.
Ich habe eine kleine Hilfsklasse gebaut,mit der man in jedem Szenario, jederzeit zu einem bestimmten Moment das Tracing im Code ein- und automatisch wieder ausschalten kann.

class CDebugEnableTraceForCategory
{
public:
  CDebugEnableTraceForCategory(ATL::CTraceCategory &category,
           PCSTR pszPrompt=NULL,
           UINT uiLevel=4,
           ATL::ATLTRACESTATUS eStatus=ATL::ATLTRACESTATUS_ENABLED)
    : m_category(category)
    , m_uiSaveLevel(category.GetLevel())
    , m_eSaveStatus(category.GetStatus())
    , m_strPrompt(pszPrompt)
  {
    if (!m_strPrompt.IsEmpty())
      TRACE("%s - Tracelevel %d\n", m_strPrompt.GetString(),uiLevel);
    m_category.SetLevel(uiLevel);
    m_category.SetStatus(eStatus);
  }
  ~CDebugEnableTraceForCategory()
  {
    m_category.SetLevel(m_uiSaveLevel);
    m_category.SetStatus(m_eSaveStatus);
    if (!m_strPrompt.IsEmpty())
      TRACE("%s - Tracelevel %d\n", m_strPrompt.GetString(), m_uiSaveLevel);
  }
private:
  // Data fields
  ATL::CTraceCategory &m_category;
  ATL::ATLTRACESTATUS m_eSaveStatus;
  UINT m_uiSaveLevel;
  CStringA m_strPrompt;
  // no copy operator
  CDebugEnableTraceForCategory(const CDebugEnableTraceForCategory &);
  CDebugEnableTraceForCategory& operator=(const CDebugEnableTraceForCategory &);
};

Mit dieser Klasse kann ich zum Beispiel alle Windows-Nachrichten an MFC Fenster bei einer bestimmen Aktion ausgeben lassen. Und wenn die Aktion fertig ist stopp auch das Tracing wieder. 

Hier als Beispiel um alle Fensternachrichten in der Aktion LoadFrame zu Tracen: 

void CMyApp::InitInstance()
{
...
    CDebugEnableTraceForCategory trace(traceWinMsg,"messages in LoadFrame");
    pMainFrame->LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW);
...
}

Weitere Infos in der MSDN findet man unter ATL::CTraceCategory und ATLTRACE2 http://msdn.microsoft.com/en-us/library/dhxsse89(VS.100).aspx

C++MFCProgrammierenVista / Windows 7VS 2010Windows APIMartin Richter - Sa 28 Aug 2010 10:36

In der MFC 10.0 hat sich ein Bug eingeschlichen, der sich unter Windows Vista und Windows 7  bemerkbar macht. Unter Windows XP tritt der Fehler nicht auf. Das Problem tritt in jedem Stil auf, der DWM verwendet. D.h. nicht wenn Windows klassisch ausgewählt wird.

Wenn auf einen CFormView mehrere Buttons liegen und der CFormView gerollt wird, dann kommt es unter Umständen zu Fehlern beim Neuzeichnen von Buttons. Dies schließt alle Button-Formen ein: Check-Buttons, Radio-Buttons und normale Buttons.

Das Ganze sieht nach dem Rollen in etwa so aus:

Der Text einiger Buttons erscheint nach dem Rollen als schwarze Blöcke. Es kann auch vorkommen, dass nur Teile der Buttons falsch gezeichnet werden.

Um das Problem gezielt nachzuvollziehen habe ich ein kleines Sample gebaut. Man kann durch zwei Schalter den CScrollView gezielt nach oben oder unten Rollen. Beim Rollen nach unten und bei bestimmten Fenstergrößen tritt dann der Fehler auf. Ich habe das Main-Window entsprechend beim Start in der Größe angepasst.

Das Problem liegt in einer Implementierung von WM_PRINTCLIENT in CScrollView (CScrollView::OnPrintClient), die ein Double-Buffering verwendet, dass entweder falsch ist oder sich eben mit der Standardimplementierung eines Dialoges beißt. Auf den ersten und zweiten Blick konnte ich in der Implementierung selbst keinen Fehler sehen. Deshalb vermute ich, dass sich dieses Double-Buffering mit dem auch vorhandenen Double-Buffering in den Standardimplementierungen der Dialogklasse beißt bzw. nicht korrekt berücksichtigt, dass auch Child-Windows neu gezeichnet werden müssen.

Die Lösung ist entsprechend einfach:

  • Man fügt einfach einen Handler für WM_PRINTCLIENT in seiner von CFormView abgeleiteten Klasse ein.
  • Dieser Handler ruft dann nicht die Implementierung der Basisklasse CFormView auf, sondern die Implementierung in CView (CView::OnPrintClient).
...
ON_MESSAGE(WM_PRINTCLIENT,&CScrollDialogMFCView::OnPrintClient)
...
 
LRESULT CScrollDialogMFCView::OnPrintClient( WPARAM wp, LPARAM lp )
{
  // Bypass the CScrollView::OnPrintClient implementation
  return CView::OnPrintClient(wp,lp);
}

Dieser Fehler ist in keiner der vorhergehenden MFC Versionen vorhanden (auch nicht in MFCNext), weil einfach kein entsprechender Handler vorhanden war..

Es stellt sich wohl bei einigen jetzt die Frage warum dieser Handler eingebaut wurde. Auch die Antwort ist einfach:
Seit Windows Vista wird stark von AnimateWindow Gebrauch gemacht und auch DWM intern scheint des öfteren WM_PRINT/WM_PRINTCLIENT zu verwenden. Entsprechend haben fast alle Klassen in der MFC entsprechende Handler ergänzt bekommen.

Das Sample kann man hier herunterladen: ScrollDialogMFC – VS-2010.
Ich habe in der stdafx.h einen define FIX_CSCROLLVIEW_PROBLEM eingebaut mit dem man den Fix einfach aktivieren und deaktivieren kann.

ATLC++MFCProgrammierenVS 2008VS 2010Martin Richter - Di 17 Aug 2010 18:56

Wieder einmal ein Bug, der seit VC-2005 bekannt ist und in meinen Augen unmöglich als by design abgetan werden darf.

Das Problem ist simpel, wenn man die Funktion SetAtIndex in den Klassen CSimpleMap und CSimpleArray angewendet wird, dann wird für das bestehende Element in der Map oder im Array kein Destruktor aufgerufen. D.h. eine CSimpleMap<CString,…> bzw. ein CSimpleArray<CString> führt mit der Verwendung von SetAtIndex sofort zu Leaks.

Tückisch in reinen ATL Projekten, weil hier der CRT Heap nicht benutzt wird und die Speicherleaks durch die CRT nicht entdeckt werden, weil der Win32 Heap zum Einsatz kommt.

Hier ist der Bug zu finden, inkl. der in meinen Augen korrekten Implementierung:
https://connect.microsoft.com/VisualStudio/feedback/details/298324/csimplemap-setatindex-do-not-call-a-ditructor

Für mich ist besonders diese Antwort äußerst fragwürdig:

CSimpleMap wasn’t really designed to work for types that needed destruction. The docs state it is limited and you should use CAtlMap instead.

Dieser Satz und dieser Hinweis ist in keiner Doku zu finden. Die Doku liest sich ganz anders:
http://msdn.microsoft.com/en-us/library/d1xc3983(VS.100).aspx

CSimpleMap provides support for a simple mapping array of any given type T, managing an unordered array of key elements and their associated values.

Man kann nur raten SetAtIndex nicht zu verwenden, sondern nur SetAt oder den operator[] :!:

« Vorhergehende SeiteNächste Seite »