Mein erster Codeproject Artikel…

Nun habe ich es endlich mal geschafft und meinen ersten kleinen Artikel auf Codeproject geschrieben:

http://www.codeproject.com/cpp/PrivateAssemblyProjects.asp

 Er schließt sich nahtlos an über alles das was ich hier achon über die Manifest-Hölle geschrieben haben…

_MFC_NOFORCE_MANIFEST und _ATL_NOFORCE_MANIFEST

In meinem Blog habe ich bereits über Libraries und die Verwendug von _CRT_NOFORCE_MANIFEST geschrieben (siehe Link unten).

Wenn man nun eine Library erzeugt, die die MFC oder die ATL benutzt, sollte man sich auch noch der beiden Defines _MFC_NOFORCE_MANIFEST und _ATL_NOFORCE_MANIFEST bewusst sein. Diese beiden Defines verhindern, dass durch die Verwendung der ATL bzw. MFC Include-Dateien #pragma comment(linker,”/manifestdependency:..”) Statements erzeugt werden.

Werden diese Defines konsequent verwendet, dann hat der Benutzer der Library die volle Kontrolle welche CRT, MFC bzw. ATL Version angebunden wird.

Warum man sich mit diesen Defines beim erzeugen einer Library auseinenadersetzen sollte kann man in diesem Artikel nachlesen: Warum man seine Libraries mit _CRT_NOFORCE_MANIFEST erzeugen sollte!

❗ BTW: Durch diese Defines kann man allerdings nicht verhindern, dass überhaupt Manifest-Einträge erzeugt werden. Selbst wenn man sein Programm mit den entsprechenden Defines kompiliert. Die Objektdateien haben dann zwar keine Manifest-Einträge, aber spätestens in dem Moment, in dem man das Programm linkt werden aus der CRT, MFC bzw. ATL Libraries Objektdateien gezogen die wieder entsprechende #pragma comment(linker,”/manifestdependency:..”) Einträge haben. Entsprechend bekommt das Manifest Tool dann auch Futter. Dazu mehr in einem späteren Artikel 🙂

Wie findet die MFC 8.0 eigentlich die MFC80lll.DLL Dateien?

Es ist vielleicht schon machen aufgefallen, dass auch die sprachabhängigen Ressourcen-Dateien der MFC als Side by Side Assemblies installiert werden durch VCREDIST_X86.EXE.
Schaut man sich an wie ein Programm erzeugt wird, dass die MFC80.DLL/MFC80U.DLL verwendet, dann weiß man wie Manifeste aussehen die für das entsprechende Programm erzeugt werden. Wir finden im Allgemeinen drei Dependency-Einträge für die MFC, die CRT und die ComCtl32 DLL.

Erstaunlicherweise wird kein Eintrag für die Sprachdateien erzeugt und scheinbar auch nicht benötigt. Denn wenn man das Programm im Debugger auf einem Deutschen XP oder Vista startet sieht man in der Debug-Ausgabe, dass die entsprechende Datei MFC80DEU.DLL aus dem Side by Side Storage geladen wird.

Ein Blick in die MFC80(U).DLL zeigt auch ein Manifest. Dieses passt genau auf die Sprachdateien. Allerdings ist dieses Manifest nicht aktiv, denn es hat keine „gültige“ ID (1000), die das Betriebsystem veranlassen würde es zu berücksichtigen. Zudem hätte dieses Manifest auch nur einen Effekt, wenn die Sprach-DLLs implitzit geladen würden.
Diese Dateien werden aber explizit geladen. Der Code der dafür verantwortlich ist wird zuallererst in der DllMain und der Initialisierung der MFC80-DLL und ein zweites mal in CWinApp::InitInstance aufgerufen.

Der entsprechende Code dort, lädt das Manifest aus der DLL, erzeugt einen Aktivierungs-Kontext und führt dann den entsprechenden AfxLoadLibrary Aufruf durch. Anschließend wird der Kontext wieder zerstört. Wird die Sprach-DLL nicht gefunden wird ein zweites Mal versucht die Datei nun aus dem Programmverzeichnis bzw. über die normalen Suchpfade zu laden.

Diese Methode über das eingebaute Manifest lässt sich nicht aushebeln. Verwendet man selbst eine Technik, die die MFC-DLLs als private Assemblies lädt, dann versucht die MFC dennoch die Sprach-DLLs Side by Side zu laden. Das Ganze ist etwas ärgerlich, wenn man private Assemblies pur verwenden möchte. Einziger Trick wäre hier CWinApp::LoadAppLangResourceDLL zu überschreiben, aber das verhindert nicht den ersten Aufruf aus der DllMain der MFC-DLL heraus.

❗ Wer sich das Ganze genauer ansehen will, sollte sich die Datei appcore.cpp mal ansehen.
Entscheidend hier sind die Funktionen: CWinApp::LoadAppLangResourceDLL und AfxLoadLangResourceDLLEx.

❗ Schaut man sich die MFC-DLLs übrigends noch genauer an, dann stellt man fest, dass sie gar keine Manifeste enthalten. Und das ist auch gut so, sonst wäre es ja auch nicht möglich die MFC und die zugehörige CRT als Private Assembly zu laden. Die MFC verwendet also immer die CRT, die im Kontext der EXE geladen wird.

Der kleine Unterschied zwischen CStatic::SetIcon und CWnd::SetIcon

So schnell legt man sich rein. Da hat man ein CStatic in einem Dialog. Man besorgt sich mit GetDlgItem das entsprechende Fenster Objekt und führt SetIcon durch. Also etwa so

GetDlgItem(IDC_MYSTATIC)->SetIcon(hIcon);

Ergebnis: 🙄  Nichts passiert.

Nach etwas Debuggen wird der Fall klar. SetIcon ist nicht virtuell und wie bei allen CWnd und anderen Fenstern nichts anderes als eine Abkürzung/Wrapper für ein SendMessage. Mit dem kleinen Unterschied, dass CWnd:SetIcon WM_SETICON sendet, während CStatic::SetIcon ein STM_SETICON versendet.

GetDlgItem liefert aber (solange kein Subclass durchgeführt wird) immer einen CWnd-Zeiger. Also wird statt der erwarteten STM_SETICON Nachricht eine WM_SETICON Nachricht verwendet, die aber CStatic nicht interessiert.
SetIcon wird nur von Top-Level Fenstern verarbeitet.

BTW: Das Gleiche kann einem auch mit CButton::SetIcon passieren. Nein BM_SETICON gibt es nicht, aber es gibt die Nachricht BM_SETIMAGE, und der kann man eine Bitmap (Wrappername CButton::SetBitmap) oder eben ein Icon übergeben (Wrappername CButton::SetIcon). Auch hier lauert die gleiche Falle!

❗ Ein typischer Designfehler in einer Library! Hier wurde offensichtlich die konsistente Namensgebung in der MFC über das Verwechselungsproblem gestellt. In einer abgeleiteten Klasse sollte niemals eine Funktion gleichen Namen mit gleicher Signatur und unterschiedlicher Funktion existieren.

Selbstgemachter Manifest-Ärger…

In dem C++.de Forum kam dieser Thread Sehr spezielles Problem – (DLL + Manifest) auf!

Man muss den Thread nicht ganz lesen, ich will hier eine kurze Übersicht geben, wie man ein Projekt verbiegen kann, dass es nur noch Ärger macht, den man nur schwer (oder nach langem Suchen) in den Griff bekommt.

Ursprüngliches Problem:
Es ging mal wieder darum eine EXE so zu schreiben, dass Sie nicht von einer VC-2005 Runtime Library Installation (CRT+MFC) abhängig ist. Es ging um ein spezielles Plugin (also eine DLL).

Die Empfehlung war, wie so oft bei Manifest Problemen:
Linke sowohl CRT als auch MFC statisch.

Begründung:
Die DLL benötigt keine weiteren DLLs oder Third Party Libs. Ist also mehr oder weniger standalone. Also warum noch zusätzliche Abhängigkeiten schaffen. Auch wenn dies etwas mehr Hauptspeicher kostet, wenn andere Applikationen die 8.0 Runtime-Libs auch gleichzeitig verwenden können.
Es erübrigt die Installation der VCRedist_x86.exe oder auch andere, komplexere Tricks wie die applikationslokale Installation der Runtime-Libs.

Folgeproblem:
Sowohl für die MFC als auch für die CRT wurde nun statisches Linken im Projekt eingestellt. Aber dennoch wurde weiterhin ein Manifest erzeugt, das die CRT anforderte.
Das erzeugte doch nun einiges Rätselraten bei mir. 😕
Auch die Projektdatei, die mir zugesandt wurde brachte auf den ersten Blick keinen Aufschluss über das Problem.
Weitere Prüfung mit DUMPBIN ergab, dass die Objekt Dateien alle mit einem Manifest Eintrag kompiliert wurden, oder in anderen Worten, das im Sourcecode ein #pragma comment manifest Eintrag drin steht.

Lösung:
Einze kurze Recherche in der crtdefs.h ergab, dass die #pragma Einträge für die Manifeste nur erzeugt werden wenn, _DLL definiert wird. Die Doku sagt klar:

  1. Das ist eine interne, vordefinierte Präprozessor Variable
  2. Diese ist nur bei den Kompileroptionen /MD und /MDd definiert

Derjenige, der dieses Projekt erzeugt hat, hat auch die Präprozessor Variable _DLL vordefiniert. Das Resultat war natürlich, dass Manifest-Einträge erzeugt wurden und weiterhin die CRT-DLLs genutzt wurden.

Merke: Pfusche nie mit internen Nachrichten, Präprozessor-Variablen rum, die einen nichts angehen. Oft genug sind schnelle Workarrounds die man mit so etwas erreicht ein Schuss ins eigene Knie. 🙂

Ach ja! Man hätte übrigens noch weiter Pfuschen können und _CRT_NOFORCE_MANIFEST definieren können :mrgreen: ! Dann hätten wir den einen Pfusch mit einem anderen behoben!

Erste (durchaus positive) Kontakte mit Orcas Beta1 VPC

Ich habe mir mal die Orcas Beta 1 für VPC heruntergeladen und versuche gerade mal, ein paar meiner größeren Projekte damit zu kompilieren.

1. Erfahrung: Ich habe ein VC-2005 Projekt geöffnet um es zu konvertieren. Orcas stürzt ohne Fehlermeldung ab. Erst beim zweiten Versuch klappt es. Gleicher Effekt bei einem anderen Projekt. Scheint irgendwie an den bestehenden temporären Projektdateien zu liegen. Bugreport wurde gesendet. Dem muss ich nochmal nachgehen.

2. Erfahrung: Der Platform SDK Include-Pfad ist in den VS-Einstellungen nicht definiert.
Resultat: Die Datei msdaguid.h wurde nicht gefunden bei einem Programm, dass die ATL OLE-DB Client Templates verwendet. Scheint ein Known-Bug des verwendeten Vista SDKs zu sein. Also am Besten die entsprechenden Einträge konform wie in VS-2005 erzeugen. Man sollte auch gleich das BIN und LIB Verzeichnis kontrollieren. Sonst wundert man sich beim Linken und wenn andere SDK Tools – wie mc.exe – verwendet werden sollen.

3. Erfahrung: Ein Breaking Change für mich. CWnd::GetMenu war bisher nicht virtuell. Jetzt wurde diese Funktion in der neuen MFC 9.0 virtuell. Die Folge: Eine meiner Fensterklassen definierte selbst GetMenu mit HMENU als Returnwert. Ja so was soll man nicht machen, aber man kann es, solange die Funktion nicht virtuell ist. Das Projekt kompiliert nicht mehr.

4. Erfahrung: Ein mittelgroßes Projekt das von VC-2005 umgestellt wurde (ca. 400 Source und Header-Dateien) kompilierte ohne weiteren Fehler. Das macht doch Hoffnung 🙂

Download der Beta1 hier: Visual Studio Code Name „Orcas“ Downloads

CFormView in einem CSplitterWnd will keine Mouse Wheel Nachrichten

Wieder mal ein netter Bug in der MFC (VC-2003 Vers. 7.1 und VC-2005 Vers. 8.0).

Platziert man ein CFormView in einem CSplitterWnd dann werden trotz sichtbarer Rollbalken, alle Mouse Wheel Nachrichten geschluckt und ignoriert.
Nachvollziehen kann man das einfach:

  • SDI Applikation im Explorer Stil erzeugen
  • Rechtes Fenster als CFormView
  • Anwendung so klein machen, dass im CFromView ein vertikaler Rollbalken erscheint.
  • Nun versuchen mit dem Mausrad zu rollen.
  • No chance…

Ursache ist der folgende Code in CScrollView:

BOOL CScrollView::OnMouseWheel(UINT fFlags, short zDelta, CPoint point)
{
 // we don’t handle anything but scrolling
 if (fFlags & (MK_SHIFT | MK_CONTROL))
  return FALSE;
 // if the parent is a splitter, it will handle the message
 if (GetParentSplitter(this, TRUE))
  return FALSE;
 // we can’t get out of it–perform the scroll ourselves
 return DoMouseWheel(fFlags, zDelta, point);
}

Wie man unschwer sieht wird bei einem Parent, dass ein CSplitterWnd ist, die Nachricht immer ignoriert. Korrekt wäre dies nur, wenn der Scrollbar nicht dem CScrollView gehört sondern dem CSplitterWnd. Das ist aber bei einem Konstrukt wie hier selten.

Workarround ist relativ simpel!
Einfach selber einen eigenen OnMouseWheel Handler erzeugen, der so aussieht:

BOOL CMyFromView::OnMouseWheel(UINT fFlags, short zDelta, CPoint point)
{
 // we don’t handle anything but scrolling
 if (fFlags & (MK_SHIFT | MK_CONTROL))
  return FALSE;
 // we can’t get out of it–perform the scroll ourselves
 return DoMouseWheel(fFlags, zDelta, point);
}

Hier der Report in connect!
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=276053

❗ PS: Das Problem tritt natürlich auch in anderen Klassen auf die von CScrollView abgeleitet werden!

vcredist_x86.exe VC-2005 SP1 gibt es jetzt auch als separaten Download

Visual C++ 2005 Express Edition Nutzer mussten bislang in die Röhre sehen.
vcredist_x86.exe in der SP1 Version gab es nicht als separaten Download.
D.h. sie konnten den SP1 für VC++ 2005 Express Edition installieren und nutzen, aber Ihre Programme nicht mit der neuen SP1 CRT ausliefern.

Das ist nun vorbei:
http://www.microsoft.com/downloads/details.aspx?FamilyID=200B2FD9-AE1A-4A14-984D-389C36F85647&displaylang=en

Alle Nutzer des Visual Studios 2005 finden die Merge Module in Ihrem Verzeichnis C:\Programme\Gemeinsame Dateien\Merge Modules
bzw. hier als Executable
C:\Programme\Microsoft Visual Studio 8\SDK\v2.0\BootStrapper\Packages\vcredist_x86\vcredist_x86.exe

BTW: Die neue Datei benötigt auch nur die 2.0 Version des MSI und nicht die 3.0 Version die die RTM Version!
Weitere Infos und Links zu den 64bit Versionen gibt es auch auf Nikolar Dudars Blog.

Bug in CPropertyPage und „Certified for Vista“

Der von mir gemeldete Bug Bug in der MFC71.DLL bzgl. CPropertySheet/CPropertyPage führt dazu, dass ein Programm, dass den genannten Voraussetzungen entspricht, bei den Test für „Certified for Vista“ durchfällt!

Der Application Verifier führt bei den Basic Test einen Page Boundary Check durch. Dabei werden alle Allokationen so durchgeführt, dass sie immer am Ende einer Memory Page erfolgen. Jeder Zugriff über die Grenze hinweg führt dann zu einem Crash.

Im realen Leben ist der Crash wirklich selten, aber mit dem Application Verifier kracht es sofort. Das Problem ist, dass dieser Basic Test Bestandteil von „Certified for Vista“ ist.

Die Folge man rasselt mit 100% Sicherheit durch!

Bug Report hier:
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=270493

Dämlicherweise wurde auch dieser Bug von Microsoft als „Gelöst (Nicht reproduzierbar)“ markiert. Da kann man wirklich nurmit dem Kopf schütteln.

Und ein kleines Demo findet ihr hier:
Testprogramm für CPropertyPage Bug in MFC 7.1

Probleme bei der CTime Serialisierung mit MFC 7.1 und MFC 8.0

Der operator<< und operator>> in MFC 7.1 serialisiert grundsätzlich nur 32bit Werte des CTime. Das wurde offensichtlich gemacht um rückwärtskompatibel zu sein zu time_t obwohl mittlerweile ja intern __time64_t verwendet wird.
Gleichzeitig wurde Serialize64 eingeführt.
Man muss also in VS2003 mit MFC 7.1 Serialize64 verwenden um alle 64bit zu speichern, also den komplettem Wertebereich von CTime.

In VS2005 mit der MFC 8.0 ging man jetzt einen Schritt weiter. Die Operatoren operator<< und operator>> sollten auch 64bit verarbeiten können. Man wollte 64bit speichern und 32bit Daten beim Einlesen erkennen wenn es eben aus alten Dateien stammt und nicht mit der neuen MFC geschrieben wurde.

Dazu wird bei Speicherung von 64bit CTime über den operator<< zuerst ein DWORD gespeichert mit dem Wert cTime64Mark (der Wert ist INT_MIN+10). Danach der 64bit Wert.
Beim Lesen wird zuerst ein DWORD gelesen. Ist dieses nicht cTime64Mark (also INT_MIN+10), dann wird dieser Wert als 32bit CTime Wert gelesen.
Ansonsten werden die 64bit in den CTime Wert gelesen.

operator>> bläht also auch die Ausgabe um weitere 32bit auf. Ein 64bit CTime Wert wird in der MFC 8.0 als 32+64bit Wert gespeichert, also 4Bytes mehr.

Speicherst man mit MFC 7.1 und dem operator<<, dann kannst man die Daten in MFC 7.1 und MFC 8.0 mit dem operator>> lesen, denn der Wert wird wegen des fehlenden Leadin-Bytes als 32bit Wert in der MFC 8.0 erkannt.
Speicherst man aber mit MFC 8.0 und dem operator<< dann ist das für MFC 8.0 kompatibel. Und kannst es in MFC 8.0 mit dem operator>> lesen. Allerdings speichert – wie schon gesagt – MFC 8.0 zuerst ein DWORD (das Leadin für 64bit) und dann den 64bit Wert.
Das versteht nun MFC 7.1 gar nicht, denn das liest immer nur ein DWORD mit dem operator>>.

❗ Ein Problem liegt also im Schreiben unter MFC 8.0 mit dem operator<< und im Lesen der Daten mit einem Programm das die MFC 7.1 verwendet und den operator>>!

❓ Was kann man machen um komplett kompatibel zu sein?

  • Würde in beiden Programmen Serialize64 verwendet gibt es kein Problem!
    Wichtig Serialize64 verwendet kein Leadin weder unter MFC 7.1 noch unter MFC 8.0!
  • Kompatibel zu beiden Versionen von MFC 7.1 und MFC 8.0 ist man auch wenn in der MFC 7.1 und in der MFC 8.0 immer nur ein DWORD gespeichert wird (das kann ich aber wirklich nicht mehr empfehelen). Unter MFC 8.0 muss das manuell gemacht werden.
  • Wenn man also Code-kompatibel (nicht binärkomaptibel) für alle MFC Versionen ab 7.0 bleiben will sollte man immer Serialize64 verwenden was einem auch ein kompakteres Format beim Speichern der Daten garantiert.

Eigentümlich ist, dass dies nicht als Breaking Change dokumentiert ist.
Oder ich habe es (wie so manches) einfach überlesen, aber hier steht es nicht:
ATL and MFC Changes: ATL 7.1 and MFC 7.1
ATL and MFC Changes: ATL 7.0 and MFC 7.0

PS: Ich habe CTime Aufgrund der vielen Unzulänglichkeiten nie verwendet. Mein Favorit ist seit eh und je COleDateTime bzw. DATE und die dazugehörige COleDateTimeSpan Klasse. Diese ist wirklich einfach in der Kalkulation und war durch das Design nie von einem begrenzten Wertebereich betroffen.