Auf was man unbedingt achten muss, wenn man an Manifesten für Assemblies herumbastelt

Ich habe für ein Projekt Manifeste zur Verwendung von Registration-Free COM Module gebaut.

Diese COM-Module wurden über ein Manifest in einer EXE geladen. Natürlich hatte jedes der COM Module wieder ein eigenes Manifest, die ich entsprechend angepasst habe. Jede COM-Class, die verwendet wird, muss ja in dem Manifest der DLL-Assembly aufgeführt werden.

Eigentümlich war, dass ich nach Änderungen der Manifeste und auch nachdem das COM Modul komplett neu gelinkt wurde, dennoch die EXE ihr Ladeverhalten nicht geändert hat. Manche COM-Klassen wurden nicht gefunden. Sobald ich aber den Rechner neu gestartet hatte funktionierte ab dann alles wie gewünscht und das geänderte Manifest schien nun wirksam zu sein.

Da gibt es einen Cache dachte ich mir. Und nach einiger Recherche im Internet stieß ich auf den folgenden interessanten Artikel von Junfeng Zang:
Windows Vista Sxs Activation Context Cache

Wie man lesen kann, wird bei jedem erfolgreichen Einlesen einer Anwendung und dem erfolgreichen Laden aller Manifeste und DLLs, die ganzen ermittelten Daten in einen Cache gespeichert. Da ich die EXE aber nicht geändert habe, wurden auch die untergeordneten Manifeste nicht neu gelesen, auch wenn diese geändert wurden.

Durch das Ändern des Datums der EXE werden die Cacheeinträge ungültig, und danach werden alle Aktivierungskontexte der Anwednung und aller anderen Assemblies neu geladen.

BUG: VS-2010 erkennt nicht die Änderung an einer Manifest Datei in einem C++ Projekt

Ich habe einige Manifest Dateien in meinen Projekten. Viele steuern COM Module und machen diese registration-free. Das ist einfach und effektiv. Dazu habe ich einfach eine entsprechende Manifestdatei angelegt mit den entsprechenden Einträgen und diese in das Projekt eingefügt. Soweit alles gut.

Jetzt stellte sich aber heraus, dass VS-2010 (auch SP1), eine Änderung der Manifest Datei nicht bemerkt und die EXE/DLL weder neu linkt, noch das Manifest-Tool anwirft, um das geänderte Manifest in das Executable einzutragen.

Dabei spielt es keinerlei Rolle, ob die Datei nur einfach in das Projekt eingefügt wurde, oder ob die Datei zusätzlich in den Projekteinstellungen für das Manifest Tool bei Additional Manifest Files eingetragen wird.

Das ist ein lästiger Fehler, der in VS-2005 und VS-2008 nicht vorhanden war.
Er hat mich mindestens 4 Stunden Arbeit gekostet, weil ich bestimmte Abhängigkeiten testen und Fehler beheben wollte aber die neuen Dateien in dem Projekt immer wieder SxS Fehler lieferten. Es dauerte eine ganze Weile bis ich merkte, dass meine Änderungen an den Manifest Dateien überhaupt nicht in meine Executables übernommen wurden und ich immer wieder nur alte Manifeste in den EXEs und DLLs hatte.
Und dann das Ganze an einem Montagmorgen und nach der Zeitumstellung… 😉

Auf Connect habe ich einen entsprechenden Bug veröffentlich und mir wurde ein Fix empfohlen, der bei mir das Problem behebt.
Changes to a Manifest file in a C++ project does not trigger a rebuild of the EXE or DLL

In der Datei %Programfiles%\msbuild\microsoft.cpp\v4.0\Microsoft.cppcommon.targets wird die folgenden Kommentarzeile gesucht:

<!-- If RC did produce an output, then force link to embed that manifest.
     This enforcement is required for projects residing on FAT32 drives. -->

Darunter wird der folgende Textblock eingefügt:

<PropertyGroup>
    <LinkSkippedExecution Condition="@(RCSourcesCompiled)!=''">
        false
    </LinkSkippedExecution>
</PropertyGroup>

Danch wird eine Änderung im Manifest korrekt erkannt und der Linker für die geänderte Ressouce angeworfen.

ComboBox DropDown Höhe wird nicht mehr durch die Ressourcen definiert

Vor Jahren habe ich für die microsoft.public.de.vc FAQ den folgenden Beitrag geschrieben:
Warum klappt meine ComboBox im DropDown-Stil in einem Dialog nicht auf?

Beim Erstellen einer ComboBox in einem Dialog Template muss auch die Größe mit angegeben werden, die die ComboBox haben soll, wenn Sie denn aufgeklappt wird. Dies kann auf zwei Methoden geschehen.

Methode 1: ComboBox aus der Werkzeugleiste einfach durch einen Mausklick einsetzen. Anschließend auf den „DropDown“-Schalter klicken und nun die gewünschte Größe einstellen.

Methode 2: ComboBox durch Ziehen eines Rechteckes auf dem Dialog einsetzen. In diesem Fall wird die Größe gleich korrekt bestimmt. Nachträgliche Änderung der Größe erfolgt dann wieder durch anklicken des „DropDown“-Schalters.

Anmerkung: Die Größe einer ComboBox mit dem Stil CBS_DROPDOWN und CBS_DROPDOWNLIST im NICHT aufgeklappten Zustand kann beim Erzeugen nicht verändert werden. Diese Größe bestimmt Windows automatisch. Die Größe die bei CreateWindow/CreateWindowEx angegeben wird ist immer die Größe des Control im aufgeklappten Zustand. Nachdem ein gültiger Windowshandle auf die ComboBox existiert kann mit CComboBox::SetItemHeight die Höhe der Items bzw. des Editfelds der ComboBox verändert werden.

Jetzt habe ich entdeckt, dass dieser Beitrag eigentlich überflüssig geworden ist, seit dem es COMCTL32 in der Version 6.0 gibt.
Wenn ein Manifest für die 6.0 Version der Common Controls vorhanden ist, dann bestimmt die COMCTL32 DLL automatisch selbst anhand der Höhe des Monitors und der Position der ComboBox wie groß der DropDown-Bereich sein kann.

Aufgefallen ist mir das, als ich in einer RC-Datei sah, dass eine ComboBox mit der Höhe gerade einmal 20 DLUs angegeben wurde. Da in der RC Datei normalerweise immer nur die DropDown-Höhe eingetragen ist, fragte ich mich warum bisher niemandem aufgefallen war, dass diese ComboBox, nicht aufklappt. Ein kurzer Test, zeigte allerdings, dass alles normal war und die Box, den halben Monitor in der Höhe einnahm.
Ein weiter Test mit und ohne Manifest zeigte mir dann schnell, dass sich das Standardverhalten von Comboboxen offensichtlich verändert hat.

Nachtrag (07.01.2011):
Nur die neuen Common Controls ab Vista und Windows 7 verhalten sich wie oben beschrieben. Windows XP (einschließlich SP3) verhält sich noch gemäß der MSDN WinAPI Doku. D.h. die Höhe wird nicht automatisch angepasst. Man kann sich eben auf nichts verlassen.

Hotfix für UseMSPrivateAssemblies.h und VC-2008

Einige nutzen ja meine Lösung für private CRT und MFC Assemblies unter VC-2005, die ich in dem diesem Artikel unter Codeproject veröffentlicht habe
http://www.codeproject.com/KB/cpp/PrivateAssemblyProjects.aspx

Das Interesse und die Nachfrage ist groß dieses Verfahren auch unter VC-2008 zu nutzen.
Da ich aber aktuell wenig Zeit habe den Artikel komplett zu überarbeiten, veröffentliche ich den relevanten Code hier erst mal vorab als „Hotfix“. Dieser Hotfix setzt voraus, dass das aktuelle Feature Pack installiert ist. Der Code ist nicht auf die RTM Version hin zugeschnitten und getestet.

UseMSPrivateAssemblies.h

// Version 2.0 by Martin Richter [WWJD]
// Supports VC-2005 and VC-2008
#pragma once    

#ifndef RC_INVOKED
// Avoid problems with the resource compiler if included    

// This defines bock the creation in the header files
#pragma message("Using private assemblies for the MS runtimes")
#define _STL_NOFORCE_MANIFEST
#define _CRT_NOFORCE_MANIFEST
#define _AFX_NOFORCE_MANIFEST
//#define _ATL_NOFORCE_MANIFEST    

// The next statements block the linker from including object files in the
// CRT and the MFC, that would create manifest pragmas too.
#ifdef __cplusplus
extern "C" {            /* Assume C declarations for C++ */
#endif    

__declspec(selectany)       int _forceCRTManifest;
__declspec(selectany)       int _forceMFCManifest;
// __declspec(selectany)    int _forceAtlDllManifest;    

// The next symbols are used by the several versions of VC 9.0
__declspec(selectany)       int _forceCRTManifestRTM;
__declspec(selectany)       int _forceMFCManifestRTM;
__declspec(selectany)       int _forceMFCManifestCUR;    

#ifdef __cplusplus
}                        /* __cplusplus */
#endif    

// We use crtassem.h with the defines there. It just gives us the
// versions and name parts for the dependencies.
// Note that there is also a MFCassem.h but this include file has the
// manifest pragma's already in it. So we can't use it
//
// Three files are controlling this crtassem.h, MFCassem.h and atlassem.h!
// Happily __LIBRARIES_ASSEMBLY_NAME_PREFIX is used in CRT, MFC and ATL!
// Doing it right would need to use _MFC_ASSEMBLY_VERSION for the MFC
// but in fact _CRT_ASSEMBLY_VERSION and _MFC_ASSEMBLY_VERSION and
// _ATL_ASSEMBLY_VERSION are the same
//  - VC-2005 SP1 8.0.50727.762
//  - VC-2008 RTM 9.0.21022.8
//  - VC-2008 Feature Pack 9.0.30411.0 (used if _BIND_TO_CURRENT_VCLIBS_VERSION
//    and _BIND_TO_CURRENT_MFC_VERSION are defined to 1)    

#include <crtassem.h>

// We don't have a seperate block for the Debug version. We just handle
// this with a extra define here.
#ifdef _DEBUG
#define __LIBRARIES_SUB_VERSION    "Debug"
#else
#define __LIBRARIES_SUB_VERSION    ""
#endif    

// Manifest for the CRT
#pragma comment(linker,"/manifestdependency:\"type='win32' "                        \
    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX "." __LIBRARIES_SUB_VERSION "CRT' "   \
    "version='" _CRT_ASSEMBLY_VERSION "' "                                          \
    "processorArchitecture='x86' \"")    

// Manifest for the MFC
#pragma comment(linker,"/manifestdependency:\"type='win32' "                        \
    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX "." __LIBRARIES_SUB_VERSION "MFC' "   \
    "version='" _CRT_ASSEMBLY_VERSION "' "                                          \
    "processorArchitecture='x86'\"")    

// #pragma comment(linker,"/manifestdependency:\"type='win32' "                     \
//     "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".MFCLOC' "                        \
//     "version='" _CRT_ASSEMBLY_VERSION "' "                                       \
//     "processorArchitecture='x86'\"")    

// Manifest for the ATL
// #pragma comment(linker,"/manifestdependency:\"type='win32' "                     \
//    "name='" __LIBRARIES_ASSEMBLY_NAME_PREFIX ".ATL' "                            \
//    "version='" _CRT_ASSEMBLY_VERSION "' "                                        \
//    "processorArchitecture='x86' \"")    

#endif // RC_INVOKED

Anmerkungen:

  • Im Endeffekt sind nur 3 Zeilen (26-28) hinzugekommen.
  • Diese Version funktioniert sowohl für VC-2005 als auch VC-2008!
  • Unter Vista wird allgemein das Problem beobachtet, das private Assemblies nur genutzt werden können, wenn diese in einem Unterverzeichnis liegen. Liegen die Assembly Dateien im gleichen Verzeichnis wie die EXE kommt es zu einem Fehler „The application failed to initialize properly (0xc0000034). „ Dieser Sache bin ich (und andere) auf der Spur.
  • Es spielt für diesen Code keine Rolle ob die beiden Defines _BIND_TO_CURRENT_VCLIBS_VERSION und _BIND_TO_CURRENT_MFC_VERSION gesetzt wurden. Werden diese Defines auf 1 gesetzt bevor UseMSPrivateAssemblies inkludiert wird, dann werden die Manifeste so erzeugt, dass die Feature Pack DLLs gezogen werden. Sind diese beiden Defines nicht gesetzt werden Manifeste für die RTM Version erzeugt.
    Ich empfehle dringend diese beiden Defines zu setzen ❗

Das ist erstmal ein Schnellschuss für alle, die die es etwas eiliger haben.

Der Vorteil gegenüber der Lösung, bei der die Manifeste manuell bearbeitet werden, wie es zum Beispiel Jochen Kalmbach in seinem Blog vorgestellt hat ist klar:
Man muss eben nichts manuell machen 🙂
Es macht wieder alles der Compiler und Linker.

Die Manifest Pest…

Hier noch mal ein paar wirklich nette Sachen, die zeigen warum ich Manifeste mittlerweile  nicht mehr ganz so mag (um es wirklich vorsichtig auszudrücken):

Was passiert eigentlich wenn man ein internes UND ein externes Manifest zu einer DLL oder EXE hinzufügt?

  1. Auf einem XP Rechner wird der Lader das externe Manifest bevorzugen, das interne Manifest wird ignoriert.
  2. Auf einem Windows 2003 oder Vista Rechner wird das interne Manifest bevorzugt und das externe Manifest wird ignoriert.
  3. Handelt es sich um eine DLL die mit LoadLibrary geladen wird, dann wird niemals ein externes Manifest berücksichtigt, selbst wenn kein internes Manifest vorliegt. Hier verhalten sich XP, 2003 und Vista gleich.

Ein Grund mehr zu meiner Empfehlung: Verwende immer nur interne Manifeste!

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 🙂

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!

Wie einen Prozess unter Vista mit gehobenen Rechten starten?

Was macht man eigentlich wenn man einen Prozess unter Vista hat, der mit normalen Rechten arbeitet, nun aber einen Prozess starten möchte, der angehobene Rechte (elevated) benutzen soll? Also quasi eine Abkürzung für „rechter Mausklick -> Als Administrator ausführen“.

Das ist ausgesprochen einfach! Man verwendet ShellExecute oder ShellExecuteEx mit dem neuen Verb „runas“.

Der nachfolgende Code startet die Systemsteuerung mit angehobenen (administrativen/elevated) Rechten. Vorher erfolgt natürlich die allseits bekannte UAC Nachricht. Ist der User kein Administrator bekommt er eine Meldung, dass er sich entsprechend anmelden soll. Ansonsten muss er dem Zugriff zustimmen. Bricht der User den Dialog ab wird nichts ausgeführt.

int _tmain(int argc, _TCHAR* argv[])
{
  SHELLEXECUTEINFO shExecInfo;
  
  shExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
  shExecInfo.fMask = NULL;
  shExecInfo.hwnd = NULL;
  shExecInfo.lpVerb = _T(„runas“);
  shExecInfo.lpFile = _T(„control.exe“);
  shExecInfo.lpParameters = NULL;
  shExecInfo.lpDirectory = NULL;
  shExecInfo.nShow = SW_SHOW;
  shExecInfo.hInstApp = NULL;
  
  ShellExecuteEx(&shExecInfo);
  return 0;
}

❗ PS: Übrigens versteht auch Windows 2003 Server und Windows XP das „runas“ Verb. In diesem Fall wird der entsprechende Anmeldedialog gezeigt, der es erlaubt den aktuellen Benutzer zu verwenden (mit evtl. eingeschränkten Rechten), oder eben einen anderen Account. Nett! 🙂
Nur scheinbar auch ein Feature, dass wieder mal nicht dokumentiert ist.

Warum man seine Libraries mit _CRT_NOFORCE_MANIFEST erzeugen sollte

Seit VC 2005 wird die DLL-Version CRT ja grundsätzlich über ein Manifest geladen. Erzeuge ich Code, der die CRT verwendet, dann wird automatisch ein #pragma comment(linker,“/manifestdependency:..“) Eintrag erzeugt. Den entsprechenden Code dazu findet man in der _crtdefs.h Datei des entsprechenden Include-Pfades.

Dieser Manifest Eintrag wird später vom Manifest Tool (MT.EXE) gesammelt und im Manifest der Applikation oder der DLL verbaut.

Gesetzt den Fall ich habe Library erzeugt, die auch die DLL-Version der CRT verwendet, dann wird automatisch die entsprechende Version der CRT im Manifest hinterlegt. D.h. bei VS 2005 RTM die CRT Version 8.0.50727.42 und bei SP1 die CRT Version 8.0.50727.762

Wird nun für ein Projekt eine Library erzeugt mit einem VS 2005 RTM, und evtl. diese LIB lange nicht verändert und dann diese Library in einem Programm verwendet, dass mit VS 2005 SP1 erzeugt wird, dann werden beide CRT Versionen im Manifest eingetragen!
Der Lader ist zwar intelligent und lädt die letzte DLL (SP1). Aber undurchsichtig wird es in jedem Fall.
Vor allem wenn man wirklich mit evtl. einer älteren Version ausliefern möchte und SP1 auf der Ziel-Maschine nicht installiert ist.

Ich habe noch nicht genau herausbekommen wie entschieden wird welches der Manifesteintrag ist, der letzten Endes zählt. Scheinbar macht es auch nichts wenn, man die Version der CRT Versionen falsch angibt. Es wird dann die CRT mit der nächst höheren CRT geladen… (aber dazu muss ich noch etwas experimentieren)…

Dieses Verhalten ist gravierend unterschiedlich zu allen Vorgängerversionen von VC. Wie bisher hält Microsoft das Versprechen der binären Kompatibilität. Theoretisch müsste auch die mit RTM erzeugte LIB sofort funktionieren. Auch der Linker wird diese LIB sofort mit der entsprechenden MSVCR80(D).DLL verbinden. Nur hat er eben nicht das letzte Wort bei den Manifesten.

Man kann das ganze verhindern, indem man _CRT_NOFORCE_MANIFEST bevor die CRT Dateien inkludiert werden (am Besten in der stdafx.h oder in den Projekteinstellungen).
In diesem Fall wird kein #pragma, dass für den Manifest Eintrag verantwortlich ist, erzeugt.
Der Effekt ist dann wie gewollt, dass nur das eigentliche Projekt, dass dann das Executable erzeugt Einfluss auf die Ausgabe des Manifestes hat. Man kann also dann auch eine Lib-Datei mit VS-2005 SP1 erzeugen und mit einem VS-2005 RTM linken. Das Manifest wurde durch diese Lib dann nicht beeinflusst und so soll es IMHO sein.

Aber aufgepasst:
Seit VS-2005 muss in jedem Fall auf noch eine strenge Trennung der Release und Debug Versionen achten. Wenn nun kein Manifest mehr zum Beispiel in einer Release-Lib erzeugt wird, weil _CRT_NOFORCE_MANIFEST verwendet wird, nun aber diese Release Lib in ein Debug Projekt eingebunden wird.  Dann wird das Executable implizit mit der MSVCR80.DLL gebunden, aber diese DLL kann nur über ein Manifest geladen werden. Das ist aber nicht vorhanden. Resultat ist das was in diesem Beitrag beschrieben steht: Manifest-Hölle „…MSVCR80.dll nicht gefunden…“ (siehe Nachtrag)