Certaines de mes applications (certaines natives, certaines .NET) utilisent des fichiers manifestes pour pouvoir être déployées de manière totalement isolée , sans nécessiter un enregistrement COM global. Par exemple, la dépendance sur le serveur com dbgrid32.ocx est déclarée comme suit dans le fichier myapp.exe.manifest qui se trouve dans le même dossier que myapp.exe:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Assembly manifestVersion="1.0" xmlns="urn:schemas-Microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="myapp.exe" version="1.2.3.4" />
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="dbgrid32.ocx" version="5.1.81.4" />
</dependentAssembly>
</dependency>
</Assembly>
Le fichier dbgrid32.ocx est déployé dans le même dossier, avec son propre fichier dbgrid32.ocx.manifest:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Assembly manifestVersion="1.0" xmlns="urn:schemas-Microsoft-com:asm.v1">
<assemblyIdentity type="win32" name="dbgrid32.ocx" version="5.1.81.4" />
<file name="dbgrid32.ocx">
<typelib
tlbid="{00028C01-0000-0000-0000-000000000046}"
version="1.0"
helpdir=""/>
<comClass progid="MSDBGrid.DBGrid"
clsid="{00028C00-0000-0000-0000-000000000046}"
description="DBGrid Control" />
</file>
</Assembly>
Tout cela fonctionne bien, mais la maintenance manuelle de ces fichiers manifeste est un peu pénible. Est-il possible de générer ces fichiers automatiquement? Idéalement, j'aimerais simplement déclarer la dépendance de l'application sur une liste de serveurs COM (natifs et .NET), puis laisser le reste être généré automatiquement. C'est possible?
Il semble que la solution parfaite n'existe pas encore. Pour résumer quelques recherches:
Make My Manifest ( link )
Cet outil analyse un projet VB6 à la recherche de dépendances COM, mais il prend également en charge la déclaration manuelle des dépendances COM à liaison tardive (c'est-à-dire celles utilisées via CreateObject).
Il est intéressant de noter que cet outil place toutes les informations sur les dépendances dans le manifeste de l’application. L'application exe et ses dépendances sont décrites comme un assemblage unique composé de plusieurs fichiers. Je n'avais pas réalisé auparavant que c'était possible.
Cela ressemble à un très bon outil, mais à partir de la version 0.6.6, il présente les limitations suivantes:
Je n'ai pas vérifié s'il prend en charge les bibliothèques .NET com.
regsvr42 ( lien codeprojet )
Cet outil de ligne de commande génère des fichiers de manifeste pour les bibliothèques COM natives. Il appelle DllRegisterServer, puis espionne l'auto-enregistrement car il ajoute des informations au registre. Il peut également générer un manifeste client pour les applications.
Cet utilitaire ne prend pas en charge les bibliothèques COM .NET, car celles-ci n'exposent pas de routine DllRegisterServer.
L'utilitaire est écrit en C++. Le code source est disponible.
mt.exe
Une partie du Windows SDK (téléchargeable à partir de MSDN ), que vous possédez déjà si Visual Studio est installé. Il est documenté ici . Vous pouvez générer des fichiers de manifeste pour les bibliothèques COM natives avec ceci:
mt.exe -tlb:mycomlib.ocx -dll:mycomlib.ocx -out:mycomlib.ocx.manifest
Vous pouvez générer des fichiers manifestes pour les bibliothèques .NET COM avec, par exemple:
mt.exe -managedassemblyname:netlib.dll -nodependency -out:netlib.dll.manifest
Cependant, cet outil pose quelques problèmes:
<runtime>
et <mvid>
Qui doivent être supprimés avant Les manifestes fonctionnent réellement.Peut-être que les futures versions du SDK amélioreront cet outil, j’ai testé celui du SDK Windows 6.0a (Vista).
Avec la tâche MSBuild GenerateApplicationManifest , j’ai généré un manifeste sur la ligne de commande identique au manifeste généré par Visual Studio. Je soupçonne que Visual Studio utilise le GenerateApplicationManifest lors de la construction. Ci-dessous, mon script de construction qui peut être exécuté à partir de la ligne de commande en utilisant msbuild "msbuild build.xml"
Merci à Dave Templin et à son post qui m'a indiqué la tâche de GenerateApplicationManifest, ainsi que la documentation de la suite de MSDN de la tâche .
build.xml
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.Microsoft.com/developer/msbuild/2003">
<Target Name="Build">
<ItemGroup>
<File Include='MyNativeApp.exe'/>
<ComComponent Include='Com1.ocx;Com2.ocx'/>
</ItemGroup>
<GenerateApplicationManifest
AssemblyName="MyNativeApp.exe"
AssemblyVersion="1.0.0.0"
IsolatedComReferences="@(ComComponent)"
Platform="x86"
ManifestType="Native">
<Output
ItemName="ApplicationManifest"
TaskParameter="OutputManifest"/>
</GenerateApplicationManifest>
</Target>
</Project>
Make My Manifest (MMM) est un outil agréable pour ce faire. Il est également possible d'écrire un script pour traiter tous vos fichiers DLL/OCX en utilisant mt.exe afin de générer un manifeste pour chacun puis de les fusionner. MMM est généralement mieux/plus facile, car il gère également de nombreux cas spéciaux/étranges.
Vous pouvez utiliser Unattended Make My Manifest spin off pour générer des manifestes directement dans des versions automatisées. Il utilise un fichier de script pour ajouter des composants COM dépendants. Voici un extrait de l'exemple ini avec les commandes disponibles:
# Unattended MMM script
#
# Command names are case-insensitive. Reference of supported commands:
#
# Command: Identity
#
# Appends assemblyIdentity and description tags.
#
# Parameters <exe_file> [name] [description]
# exe_file file name can be quoted if containing spaces. The containing folder
# of the executable sets base path for relative file names
# name (optional) Assembly name. Defaults to MyAssembly
# description (optional) description of Assembly
#
# Command: Dependency
#
# Appends dependency tag for referencing dependent assemblies like Common Controls 6.0,
# VC run-time or MFC
#
# Parameters {<lib_name>|<Assembly_file>} [version] [/update]
# lib_name one of { comctl, vc90crt, vc90mfc }
# Assembly_file file name of .NET DLL exporting COM classes
# version (optional) required Assembly version. Multiple version of vc90crt can
# be required by a single manifest
# /update (optional) updates Assembly_file Assembly manifest. Spawns mt.exe
#
# Command: File
#
# Appends file tag and collects information about coclasses and interfaces exposed by
# the referenced COM component typelib.
#
# Parameters <file_name> [interfaces]
# file_name file containing typelib. Can be relative to base path
# interfaces (optional) pipe (|) separated interfaces with or w/o leading
# underscore
#
# Command: Interface
#
# Appends comInterfaceExternalProxyStub tag for inter-thread marshaling of interfaces
#
# Parameters <file_name> <interfaces>
# file_name file containing typelib. Can be relative to base path
# interfaces pipe (|) separated interfaces with or w/o leading underscore
#
# Command: TrustInfo
#
# Appends trustInfo tag for UAC user-rights elevation on Vista and above
#
# Parameters [level] [uiaccess]
# level (optional) one of { 1, 2, 3 } corresponding to { asInvoker,
# highestAvailable, requireAdministrator }. Default is 1
# uiaccess (optional) true/false or 0/1. Allows application to gain access to
# the protected system UI. Default is 0
#
# Command: DpiAware
#
# Appends dpiAware tag for custom DPI aware applications
#
# Parameters [on_off]
# on_off (optional) true/false or 0/1. Default is 0
#
# Command: SupportedOS
#
# Appends supportedOS tag
#
# Parameters <os_type>
# os_type one of { Vista, win7 }. Multiple OSes can be supported by a single
# manifest
#
Il fonctionnera sous Windows 32 ou 64 bits.
Pour renseigner les ProgID que le mt.exe n'inclut pas, vous pouvez appeler ProgIDFromCLSID
pour les rechercher dans le registre. Cela nécessite un enregistrement COM traditionnel avant de compléter le fichier manifeste, mais par la suite, le fichier manifeste sera autonome.
Ce code C # ajoute les ProgID à toutes les classes COM dans un manifeste:
var manifest = XDocument.Load(fileName);
var namespaceManager = new XmlNamespaceManager(new NameTable());
namespaceManager.AddNamespace("s", "urn:schemas-Microsoft-com:asm.v1");
foreach (var classElement in manifest.XPathSelectElements("s:Assembly/s:file/s:comClass", namespaceManager)) {
var clsid = Guid.Parse(classElement.Attribute("clsid").Value);
int result = ProgIDFromCLSID(ref clsid, out string progId); if (result != S_OK) throw new COMException($"ProgID lookup failed for {clsid}.", result);
classElement.SetAttributeValue("progid", progId);
}
manifest.Save(fileName);
Le code s'appuie sur ces définitions d'interopérabilité:
[DllImport("ole32.dll")] static extern int ProgIDFromCLSID([In] ref Guid clsid, [MarshalAs(UnmanagedType.LPWStr)] out string lplpszProgID);
const int S_OK = 0;