...someplace, where there isn't any trouble? Do you suppose there is such a place, Toto?

Visual Studio 2005 Runtimes - Part 2: Wie man sich um den winSxS-Folder herumbescheißt...

Vor ein paar Wochen hat Andre Stille, MVP, in der Newsgroup microsoft.public.de.vc skizziert, wie man erreichen kann, daß man auch unter XP eine applikationsprivate CRT und MFC bekommen kann, und zwar unter vollständiger Mißachtung des winsxs-Folders. Das soll heißen: Selbst wenn die offiziellen MSI-Pakete installiert sind, wird trotzdem die eigene CRT und MFC der Applikation verwendet (diese Problematik habe ich ja hier schon einmal versucht, zu erläutern). Der Ruf nach einer Anleitung wurde schnell laut, aber Andre scheint der Sache noch nicht nachgekommen zu sein. Drum springe ich hier jetzt kurzerhand in die Bresche. Wenn vom nachfolgenden Text dem Leser beim testweisen Nachvollziehen irgendetwas erfolgreich gelingt, dann ist dies ausschließlich Andres Verdienst, etwaige fehlerhafte Dokumentation und daraus resultierende verweigerte Applikationsstarts sind allein meine Schuld:

Wir starten, indem wir eine MFC-Applikation namens testapp (wie sinnig) erzeugen und zwar in ein Unterverzeichnis unter c:\. Das Verzeichnis c:\ hat ja bestimmt a jeda und so sieht das dann bei mir aus (ein Klick auf ein Bild öffnet es in einem neuen Browserfenster in der Originalgröße):

Wir klicken auf OK und die zweite Wizardseite erscheint:

Dort ändern wir nichts, wir klicken nur auf "Finish". Dann wird das Projekt für uns generiert und wir könnten es jetzt einmal builden. Tun wir aber nicht. Stattdessen machen wir einen rechten Mausklick auf das Projekt im "Solution Explorer" und wählen den untersten Eintrag, "Properties", an. Es öffnet sich nun der moduslose Dialog mit den Projekteigenschaften. Dort auf der linken Seite wählen wir als Konfiguration erstmal den Release-Build und versuchen dann im Baum den Punkt "Configuration Properties"-"Manifest Tool"-"input and Output" zu orten und klicken drauf. In der rechten Hälfte des Dialogs können wir nun festlegen, ob ein Manifest in das Binary hineinkompiliert werden soll, oder als separate Datei erzeugt werden soll. Wir wollen letzteres und wählen wie im folgenden Screenshot gezeigt, "No" für die Einstellung "Embed Manifest":

So, und jetzt builden wir das Projekt in all seiner Schönheit und Pracht, und zwar im Releasebuild (das muß ich jetzt nicht weiter erklären, oder?). Als Ergebnis dieses Builds erhalten wir im Unterverzeichnis C:\testapp\release nun die testapp.exe selber sowie ihr Manifest, die Datei testapp.exe.manifest. Spaßeshalber können wir testapp.exe jetzt auch 'mal starten und uns beispielsweise mit dem "Process Explorer" (procexp.exe) davon überzeugen, daß der DLL-Lader die runtime (msvcr80.dll) sowie die MFC (mfc80u.dll) aus irgendeinem Verzeichnis mit einem Namen, der total funky ist, unterhalb von %systemroot%\winsxs lädt.

Als nächstes erzeugen wir zwei Unterverzeichnisse unterhalb von C:\testapp\release, nämlich Microsoft.VC80.CRT und Microsoft.VC80.MFC. In das Verzeichnis Microsoft.VC80.CRT kopieren wir nun die drei runtime-Dateien msvcm80.dll, msvcp80.dll und msvcr80.dll. Wir finden sie durch eine Suche unterhalb des %systemroot%\winsxs folders. Bei mir sind sie beispielsweise im Verzeichnis

c:\WINDOWS\WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd

Ein Blick auf deren Versionsinfo-Ressource zeigt, daß alle drei Dateien die Version 8.0.50727.42 haben, das wird später wichtig. In unseren Lieblingstexteditor verfrachten wir jetzt nur noch folgendes:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
    <assemblyIdentity type="win32" name="Microsoft.VC80.CRT"
version="8.0.50727.42" processorArchitecture="x86">
    </assemblyIdentity>
    <file name="msvcr80.dll"></file>
    <file name="msvcp80.dll"></file>
    <file name="msvcm80.dll"></file>
</assembly>


(hach, wie ich das hasse, spitze Klammern in HTML zu schreiben...)
und erzeugen uns mit diesem Inhalt eine Datei namens Microsoft.VC80.CRT.Manifest im zuvor erzeugten Unterverzeichnis c:\testapp\release\Microsoft.VC80.CRT. Etwas ähnliches machen wir mit dem Verzeichnis C:\testapp\release\Microsoft.VC80.MFC. Da drin erzeugen wir eine Datei mit dem Namen Microsoft.VC80.MFC.Manifest, die folgenden Inhalt hat:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
   manifestVersion="1.0">
    <assemblyIdentity type="win32" name="Microsoft.VC80.MFC"
version="8.0.50727.42" processorArchitecture="x86">
    </assemblyIdentity>
    <file name="mfc80.dll"></file>
    <file name="mfc80u.dll"></file>
    <file name="mfcm80.dll"></file>
    <file name="mfcm80u.dll"></file>
</assembly>

Und natürlich kopieren hier hin die ganzen MFC-Redistributables, nämlich mfc80.dll, mfc80u.dll, mfcm80.dll und mfcm80u.dll. Wir finden sie auch wieder durch eine Suche unterhalb des %systemroot%\winsxs folders. Bei mir waren sie zu finden unter
c:\WINDOWS\WinSxS\x86_Microsoft.VC80.MFC_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_dec6ddd2
Und auch für diese Dateien stellen wir fest, daß sie die Version 8.0.50727.42 haben.

Starten wir nun testapp.exe erneut, stellen wir fest, daß bisher alle Bemühungen umsonst waren, die runtime und MFC werden nachwie vor aus dem winsxs-Folder geladen. Ist aber auch ganz logisch, schließlich haben wir ja das Manifest von testapp.exe selber, die Datei testapp.exe.manifest aus dem Ordner c:\testapp\release, noch unberührt gelassen. Das ändert sich jetzt und wir laden diese Datei in unseren Lieblingseditor. Sie hat folgenden Inhalt:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" 
      version="8.0.50608.0" 
      processorArchitecture="x86"
      publicKeyToken="1fc8b3b9a1e18e3b">
      </assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC80.MFC" 
      version="8.0.50608.0" 
      processorArchitecture="x86" 
      publicKeyToken="1fc8b3b9a1e18e3b">
      </assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" 
      name="Microsoft.Windows.Common-Controls" 
      version="6.0.0.0" processorArchitecture="x86" 
      publicKeyToken="6595b64144ccf1df" 
      language="*"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>

Anhand des Manifestes ist deutlich erkennbar daß drei "fusionized" Assemblies referenziert werden, die CRT, die MFC und die Common Controls. Anders als in Andre Stilles Usenet Posting beschrieben, würde ich an den Common-Controls-Manifest-informationen nichts drehen, denn die Common Controls DLL, die hier referenziert wird, ist nicht redistributable. Anders die runtime (CRT) und die MFC: Für deren Einträge im Applikationsmanifest (das haben wir noch im Editor, richtig?) ändern wir jetzt beidesmal die Versionsnummer von 8.0.50608.0 auf 8.0.50727.42 ab, schließlich ist das die Version, die wir in den beiden Unterverzeichnissen haben. Dann werfen wir die beiden Einträge publicKeyToken="1fc8b3b9a1e18e3b" kurzerhand raus und speichern die Datei sicherheitshalber einmal, denn eigentlich sind wir jetzt schon fertig. Die Datei sollte jetzt so ausschauen:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" 
  manifestVersion="1.0">
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC80.CRT" 
      version="8.0.50727.42" 
      processorArchitecture="x86" ></assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" name="Microsoft.VC80.MFC" 
      version="8.0.50727.42" 
      processorArchitecture="x86" ></assemblyIdentity>
    </dependentAssembly>
  </dependency>
  <dependency>
    <dependentAssembly>
      <assemblyIdentity type="win32" 
      name="Microsoft.Windows.Common-Controls" version="6.0.0.0" 
      processorArchitecture="x86" 
      publicKeyToken="6595b64144ccf1df" 
      language="*"></assemblyIdentity>
    </dependentAssembly>
  </dependency>
</assembly>

Aber halt: Wenn wir jetzt erneut builden, wird unser kunstvoll manipuliertes Aplikationsmanifest wieder mit einem Defaultmanifest überschrieben. Ich kopiere es deswegen ins übergeordnete Verzeichnis (c:\testapp) und ergänze einen "Post-Build Event" mit dem Kommando

copy c:\testapp\testapp.exe.manifest c:\testapp\release

wie folgender Screenshot zeigt:


Damit wird dann nach jedem Build das gerade generierte Applikationsmanifest durch unser eigenes ersetzt. Falls irgendjemand hierfür eine elegantere Variante kennt, oder weiß, wie man um die hartkodierten Pfade im obigen Beispiel drumrumkommt, bin ich natürlich für jeden sachdienlichen Hinweis dankbar.

Fazit: Mit dem hier beschriebenen Verfahren kann man auch mit den "fusionized" Assemblies aus Visual Studio 2005 unter XP ein eigenes side-by-side von Applikation und runtimes erreichen. Das Ganze wurde von mir getestet unter Windows XP RTM und im Vista Build 5381 und jedesmal wurden die runtimes aus dem Applikationsverzeichnis geladen, egal ob die runtime mit dem offiziellen MSI-installer vorher installiert war oder nicht. Die von Andre in seinem Posting angesprochene Problematik mit der lokalisierten MFC Ressourcen DLL scheint nach meinen Beobachtungen überhaupt nicht zu existieren, bei Nichtvorhandensein der DLL für MFC8 wird einfach eine vorhandene einer früheren Version geladen. Anyway, the kudos goes to Andre, credits, where credits are due.

(Mein Testprojekt kann man sich übrigens hier herunterladen.)

Trackback address for this post

This is a captcha-picture. It is used to prevent mass-access by robots.
Please enter the characters from the image above. (case insensitive)

2 comments, 2 trackbacks

Trackback from: hallenmitteBlog [Visitor]
hallenmitteBlogVisual Studio 2005 Runtime Error R6034 bzw. R6030
Als Student der Medieninformatik, im Glauben seine Sachen mitunter recht ordentlich zu erledigen, wird einem immer wieder mal bewusst, dass das Knowhow klare Grenzen hat, insbesondere dann, wenn sich Felder auftun, denen man bis dahin nicht begegnet is...
06/07/06 @ 13:47
Trackback from: hallenmitteBlog [Visitor]
hallenmitteBlogVisual Studio 2005 Runtime Error R6034 bzw. R6030
Als Student der Medieninformatik, im Glauben seine Sachen mitunter recht ordentlich zu erledigen, wird einem immer wieder mal bewusst, dass das Knowhow klare Grenzen hat, insbesondere dann, wenn sich Felder auftun, denen man bis dahin nicht begegnet is...
06/07/06 @ 13:54
Comment from: Matthias [Visitor]
MatthiasVielen Dank für diesen exzellenten Beitrag! Er hat mein Problem gelöst!

Hat man eigentlich eine Chance ausführbare *.exe Dateien zum Laufen zu bekommen, obwohl der Source-Code nicht vorhanden ist und der Start einer solchen Datei den gleichen Runtime Error auswirft?

Die gleiche exe läuft auf anderen Systemen, auf meinem jedoch nicht. Wie ist das zu erklären?

Beste Grüße,
Matthias

P.S: Ups. Doppelt "getrackbackt".
06/07/06 @ 15:05
Comment from: Stefan Kuhr [Visitor]
Stefan KuhrMatthias,

Die exe laeuft dann wohl deswegen auf Deinem System nicht, weil halt die runtime nicht darauf installiert ist. Um das Problem zu umgehen mit dem obigen Verfahren koenntest Du die Exe mit einem Ressource-Editor (also etwa MS Developer Studio oder Borland Resource Workshop) oeffnen, die Ressource vom numerischen Typ 24 und der ID 1 loeschen und neu abspeichern. Dann von Hand ein Manifest fuer die exe anlegen wie hier beschrieben. Das einzig negative was dabei passieren kann, ist, dass eine eventuell vorhandene digitale Signatur des Binaries seitens des Herstellers dabei perdu geht, weil Du ja damit die exe aenderst.
06/07/06 @ 21:30

Comments are closed for this post.