Cibler à la fois 32 bits et 64 bits avec Visual Studio dans la même solution / projet

111

J'ai un petit dilemme sur la façon de configurer mes builds de studio visuel pour le multi-ciblage.

Contexte: c # .NET v2.0 avec p / invocation dans des DLL tierces 32 bits, SQL compact v3.5 SP1, avec un projet d'installation. À l'heure actuelle, la cible de la plate-forme est définie sur x86 afin qu'elle puisse être exécutée sous Windows x64.

La société tierce vient de publier des versions 64 bits de leurs DLL et je souhaite créer un programme 64 bits dédié.

Cela soulève des questions auxquelles je n'ai pas encore de réponses. Je veux avoir exactement la même base de code. Je dois construire avec des références à l'ensemble de DLL 32 bits ou de DLL 64 bits. (À la fois tiers et SQL Server Compact)

Cela peut-il être résolu avec 2 nouveaux ensembles de configurations (Debug64 et Release64)?

Dois-je créer 2 projets d'installation séparés (projets std. Visual Studio, pas de Wix ou tout autre utilitaire), ou cela peut-il être résolu dans le même .msi?

Toutes les idées et / ou recommandations seraient les bienvenues.

Magnus Johansson
la source
@Magnus Johansson: vous pouvez utiliser deux configurations pour atteindre la moitié de votre objectif. Le MSI est un peu plus difficile.
user7116

Réponses:

83

Oui, vous pouvez cibler à la fois x86 et x64 avec la même base de code dans le même projet. En général, les choses fonctionneront simplement si vous créez les bonnes configurations de solution dans VS.NET (bien que P / Invoke pour des DLL entièrement non gérées nécessitera probablement du code conditionnel): les éléments qui nécessitent une attention particulière sont:

  • Références à des assemblys gérés externes avec le même nom mais leur propre bitness spécifique (cela s'applique également aux assemblys d'interopérabilité COM)
  • Le package MSI (qui, comme cela a déjà été noté, devra cibler x86 ou x64)
  • Toutes les actions personnalisées basées sur les classes du programme d'installation .NET dans votre package MSI

Le problème de référence d'assembly ne peut pas être résolu entièrement dans VS.NET, car il ne vous permettra d'ajouter une référence avec un nom donné à un projet qu'une seule fois. Pour contourner ce problème, modifiez manuellement votre fichier de projet (dans VS, cliquez avec le bouton droit sur votre fichier de projet dans l'Explorateur de solutions, sélectionnez Décharger le projet, puis cliquez à nouveau avec le bouton droit et sélectionnez Modifier). Après avoir ajouté une référence à, par exemple, la version x86 d'un assembly, votre fichier projet contiendra quelque chose comme:

<Reference Include="Filename, ..., processorArchitecture=x86">
  <HintPath>C:\path\to\x86\DLL</HintPath>
</Reference>

Enveloppez cette balise Reference dans une balise ItemGroup indiquant la configuration de la solution à laquelle elle s'applique, par exemple:

<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
   <Reference ...>....</Reference>
</ItemGroup>

Ensuite, copiez et collez la balise ItemGroup entière, et modifiez-la pour qu'elle contienne les détails de votre DLL 64 bits, par exemple:

<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
  <Reference Include="Filename, ..., processorArchitecture=AMD64">
     <HintPath>C:\path\to\x64\DLL</HintPath>
   </Reference>
</ItemGroup>

Après avoir rechargé votre projet dans VS.NET, la boîte de dialogue Référence d'assemblage sera un peu confuse par ces modifications et vous risquez de rencontrer des avertissements concernant les assemblys avec le mauvais processeur cible, mais toutes vos versions fonctionneront très bien.

Résoudre le problème MSI est à côté, et malheureusement , ce sera besoin d' un outil non-VS.NET: Je préfère de Caphyon Advanced Installer à cet effet, car il retire l'astuce de base impliqués (créer un MSI commun, ainsi que 32 bits et les MSI spécifiques 64 bits, et utilisez un lanceur d'installation .EXE pour extraire la bonne version et effectuer les corrections nécessaires au moment de l'exécution) très, très bien.

Vous pouvez probablement obtenir les mêmes résultats en utilisant d'autres outils ou l'ensemble d'outils Windows Installer XML (WiX) , mais Advanced Installer rend les choses si faciles (et est tout à fait abordable à cela) que je n'ai jamais vraiment envisagé d'alternatives.

Une chose pour laquelle vous pouvez toujours avoir besoin de WiX, même lorsque vous utilisez Advanced Installer, est pour vos actions personnalisées .NET Installer Class. Bien qu'il soit trivial de spécifier certaines actions qui ne devraient s'exécuter que sur certaines plates-formes (en utilisant respectivement les conditions d'exécution VersionNT64 et NOT VersionNT64), les actions personnalisées AI intégrées seront exécutées à l'aide du Framework 32 bits, même sur des machines 64 bits .

Cela peut être corrigé dans une version ultérieure, mais pour le moment (ou lorsque vous utilisez un outil différent pour créer vos MSI qui présente le même problème), vous pouvez utiliser la prise en charge des actions personnalisées gérées par WiX 3.0 pour créer des DLL d'action avec le bon bitness qui sera exécuté en utilisant le Framework correspondant.


Edit: à partir de la version 8.1.2, Advanced Installer prend correctement en charge les actions personnalisées 64 bits. Depuis ma réponse initiale, son prix a augmenté un peu, malheureusement, même s'il est toujours extrêmement bon rapport qualité-prix par rapport à InstallShield et ses semblables ...


Edit: Si vos DLL sont enregistrées dans le GAC, vous pouvez également utiliser les balises de référence standard de cette façon (SQLite par exemple):

<ItemGroup Condition="'$(Platform)' == 'x86'">
    <Reference Include="System.Data.SQLite, Version=1.0.80.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86" />
</ItemGroup>
<ItemGroup Condition="'$(Platform)' == 'x64'">
    <Reference Include="System.Data.SQLite, Version=1.0.80.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=AMD64" />
</ItemGroup>

La condition est également réduite à tous les types de build, version ou débogage, et spécifie simplement l'architecture du processeur.

mdb
la source
Dans Visual Studio 2008, j'ai trouvé que les <ItemGroup> ne pouvaient pas être imbriqués. Cette solution a bien fonctionné une fois que j'ai fait les nouveaux <ItemGroup> sous le groupe le reste des <Reference>. J'ai également dû changer x86 en AnyCPU, ce qui est probablement lié à l'historique de mon projet particulier.
Oliver Bock
Ce programme d'installation avancé a l'air plutôt génial.
Pat
C'est peut-être une question idiote, mais comment accéder au fichier pour le modifier manuellement?
2012
1
Pour éditer le fichier dans VS, faites un clic droit sur le projet dans l'Explorateur de solutions et trouvez "Décharger le projet". Une fois le projet déchargé, cliquez à nouveau avec le bouton droit de la souris et cliquez sur "Editer <nom de fichier du projet>". Après avoir édité le fichier de projet, enregistrez-le et cliquez à nouveau avec le bouton droit sur le fichier de projet et chargez-le. Si aucune faute de frappe ou erreur, il se chargera à nouveau. Sinon, VS vous dira à peu près quel est le problème avec le fichier. J'espère que cela pourra aider!
John Baughman
Très bonne réponse! Je vous remercie!
John Baughman
27

Supposons que vous ayez créé les DLL pour les deux plates-formes et qu'elles se trouvent à l'emplacement suivant:

C:\whatever\x86\whatever.dll
C:\whatever\x64\whatever.dll

Il vous suffit de modifier votre fichier .csproj à partir de ceci:

<HintPath>C:\whatever\x86\whatever.dll</HintPath>

Pour ça:

<HintPath>C:\whatever\$(Platform)\whatever.dll</HintPath>

Vous devriez alors être en mesure de créer votre projet ciblant les deux plates-formes, et MSBuild cherchera dans le bon répertoire pour la plate-forme choisie.

Tim Booker
la source
Ce serait génial si cela fonctionnait, mais ce n'est pas le cas. Au moins pas pour moi.
John Sheehan
10
N'est-ce pas censé être: <HintPath> C: \ n'importe \ $ (Platform) \ any.dll </HintPath>
Andreas
A bien fonctionné sur Visual Studio 2008 pour moi, mais je n'ai pas automatiquement copié la DLL dans le répertoire cible de construction, comme le fait une <Reference> normale. La solution de mdb fonctionnait mieux pour moi.
Oliver Bock
1

Je ne suis pas sûr de la réponse totale à votre question - mais j'ai pensé que je voudrais signaler un commentaire dans la section Informations supplémentaires du page de téléchargement de SQL Compact 3.5 SP1 lorsque vous regardez x64 - j'espère que cela vous aidera.

En raison des modifications apportées à SQL Server Compact SP1 et de la prise en charge supplémentaire de la version 64 bits, les environnements en mode mixte et installés de manière centralisée de la version 32 bits de SQL Server Compact 3.5 et de la version 64 bits de SQL Server Compact 3.5 SP1 peuvent créer ce qui semble être intermittent problèmes. Pour minimiser les risques de conflits et pour permettre le déploiement indépendant de la plate-forme des applications client gérées, l'installation centralisée de la version 64 bits de SQL Server Compact 3.5 SP1 à l'aide du fichier Windows Installer (MSI) nécessite également l'installation de la version 32 bits de SQL Server Fichier MSI compact 3.5 SP1. Pour les applications qui ne nécessitent que 64 bits natif, un déploiement privé de la version 64 bits de SQL Server Compact 3.5 SP1 peut être utilisé.

J'ai lu ceci comme "inclure les fichiers SQLCE 32 bits ainsi que les fichiers 64 bits" si la distribution pour les clients 64 bits.

Rend la vie intéressante je suppose ... je dois dire que j'adore la ligne "ce qui semble être des problèmes intermittents" ... sonne un peu comme "vous imaginez des choses, mais juste au cas où, faites ceci ..."

gleng
la source
1

Une version .Net avec des dépendances x86 / x64

Alors que toutes les autres réponses vous donnent une solution pour créer des builds différents en fonction de la plate-forme, je vous donne la possibilité d'avoir uniquement la configuration "AnyCPU" et de créer une build qui fonctionne avec vos dll x86 et x64.

Vous devez écrire un code de plomberie pour cela.

Résolution des dll x86 / x64 correctes au moment de l'exécution

Pas:

  1. Utilisez AnyCPU dans csproj
  2. Décidez si vous ne référencez que les DLL x86 ou x64 dans vos csprojs. Adaptez les paramètres UnitTests aux paramètres d'architecture que vous avez choisis. C'est important pour le débogage / l'exécution des tests dans VisualStudio.
  3. Sur Reference-Properties, définissez Copier la version locale et spécifique sur false
  4. Débarrassez-vous des avertissements d'architecture en ajoutant cette ligne au premier PropertyGroup dans tous vos fichiers csproj où vous référencez x86 / x64: <ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
  5. Ajoutez ce script postbuild à votre projet de démarrage, utilisez et modifiez les chemins de ce script sp qu'il copie toutes vos dll x86 / x64 dans les sous-dossiers correspondants de votre build bin \ x86 \ bin \ x64 \

    xcopy /E /H /R /Y /I /D $(SolutionDir)\YourPathToX86Dlls $(TargetDir)\x86 xcopy /E /H /R /Y /I /D $(SolutionDir)\YourPathToX64Dlls $(TargetDir)\x64

    -> Lorsque vous démarrez l'application maintenant, vous obtenez une exception indiquant que l'assembly n'a pas pu être trouvé.

  6. Enregistrez l'événement AssemblyResolve dès le début du point d'entrée de votre application

    AppDomain.CurrentDomain.AssemblyResolve += TryResolveArchitectureDependency;

    avec cette méthode:

    /// <summary>
    /// Event Handler for AppDomain.CurrentDomain.AssemblyResolve
    /// </summary>
    /// <param name="sender">The app domain</param>
    /// <param name="resolveEventArgs">The resolve event args</param>
    /// <returns>The architecture dependent assembly</returns>
    public static Assembly TryResolveArchitectureDependency(object sender, ResolveEventArgs resolveEventArgs)
    {
        var dllName = resolveEventArgs.Name.Substring(0, resolveEventArgs.Name.IndexOf(","));
    
        var anyCpuAssemblyPath = $".\\{dllName}.dll";
    
        var architectureName = System.Environment.Is64BitProcess ? "x64" : "x86";
    
        var assemblyPath = $".\\{architectureName}\\{dllName}.dll";
    
        if (File.Exists(assemblyPath))
        {
            return Assembly.LoadFrom(assemblyPath);
        }
    
        return null;
    }
  7. Si vous avez des tests unitaires, créez une TestClass avec une méthode qui a un AssemblyInitializeAttribute et enregistrez également le TryResolveArchitectureDependency-Handler ci-dessus. (Cela ne sera parfois pas exécuté si vous exécutez des tests uniques dans Visual Studio, les références ne seront pas résolues à partir du bac UnitTest. Par conséquent, la décision de l'étape 2 est importante.)

Avantages:

  • Une installation / construction pour les deux plates-formes

Inconvénients: - Aucune erreur au moment de la compilation lorsque les DLL x86 / x64 ne correspondent pas. - Vous devez toujours exécuter le test dans les deux modes!

Créez éventuellement un deuxième exécutable exclusif pour l'architecture x64 avec Corflags.exe dans le script postbuild

Autres variantes à essayer: - Vous n'avez pas besoin du gestionnaire d'événements AssemblyResolve si vous vous assurez que les bonnes dll sont copiées dans votre dossier binaire au démarrage (Évaluer l'architecture du processus -> déplacer les dll correspondantes de x64 / x86 vers le dossier bin et inversement. ) - Dans le programme d'installation, évaluez l'architecture et supprimez les binaires pour la mauvaise architecture et déplacez les bons vers le dossier bin.

Felix Keil
la source
0

Concernant votre dernière question. Vous ne pouvez probablement pas résoudre ce problème dans un seul MSI. Si vous utilisez des dossiers de registre / système ou tout autre élément connexe, le MSI lui-même doit en être conscient et vous devez préparer un MSI 64 bits pour une installation correcte sur une machine 32 bits.

Il est possible que vous puissiez installer votre produit en tant qu'application informatique 32 et toujours pouvoir le faire fonctionner en 64 bits, mais je pense que cela peut être un peu difficile à réaliser.

Cela étant dit, je pense que vous devriez pouvoir garder une base de code unique pour tout. Dans mon lieu de travail actuel, nous avons réussi à le faire. (mais il a fallu un peu de jonglerie pour que tout joue ensemble)

J'espère que cela t'aides. Voici un lien vers des informations relatives aux problèmes 32/64 bits: http://blog.typemock.com/2008/07/registry-on-windows-64-bit-double-your.html

Lior Friedman
la source
0

Si vous utilisez des actions personnalisées écrites en .NET dans le cadre de votre programme d'installation MSI, vous rencontrez un autre problème.

Le 'shim' qui exécute ces actions personnalisées est toujours 32 bits, votre action personnalisée s'exécutera également en 32 bits, quelle que soit la cible que vous spécifiez.

Plus d'informations et quelques mouvements de ninja pour se déplacer (modifiez essentiellement le MSI pour utiliser la version 64 bits de ce shim)

Création d'un MSI dans Visual Studio 2005/2008 pour travailler sur un SharePoint 64

Actions personnalisées gérées 64 bits avec Visual Studio

Ryan
la source
0

Vous pouvez générer deux solutions différemment et les fusionner par la suite! Je l'ai fait pour VS 2010. et ça marche. J'ai eu 2 solutions différentes générées par CMake et je les ai fusionnées

voidMainReturn
la source
0

Vous pouvez utiliser une condition à un ItemGroup pour les références dll dans le fichier projet.
Cela obligera Visual Studio à revérifier la condition et les références chaque fois que vous modifiez la configuration active.
Ajoutez simplement une condition pour chaque configuration.

Exemple:

 <ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
    <Reference Include="DLLName">
      <HintPath>..\DLLName.dll</HintPath>
    </Reference>
    <ProjectReference Include="..\MyOtherProject.vcxproj">
      <Project>{AAAAAA-000000-BBBB-CCCC-TTTTTTTTTT}</Project>
      <Name>MyOtherProject</Name>
    </ProjectReference>
  </ItemGroup>
Yochai Timmer
la source