Partager via


Générez des binaires Arm64X

Vous pouvez générer des binaires Arm64X, également connus sous le nom de fichiers PE Arm64X, pour supporter le chargement d’un seul binaire à la fois dans les processus x64/Arm64EC et Arm64.

Générer un binaire Arm64X à partir d’un projet Visual Studio

Pour activer la génération de binaires Arm64X, les pages de propriétés de la configuration Arm64EC ont une nouvelle propriété appelée « Construire le projet comme ARM64X », connue sous le nom de BuildAsX dans le fichier du projet.

Page de propriétés d’une configuration Arm64EC montrant l’option Build Project as ARM64X

Lorsque vous générez un projet, Visual Studio compile normalement pour Arm64EC, puis lie les sorties dans un fichier binaire Arm64EC. Lorsque vous définissez BuildAsX sur true, Visual Studio compile pour Arm64EC et Arm64. L’étape de lien Arm64EC lie les deux sorties dans un seul binaire Arm64X. Le répertoire de sortie de ce binaire Arm64X est le répertoire de sortie défini sous la configuration Arm64EC.

Pour BuildAsX fonctionner correctement, vous devez disposer d’une configuration Arm64 existante, en plus de la configuration Arm64EC. Les configurations Arm64 et Arm64EC doivent utiliser le même runtime C et la même bibliothèque standard C++ (par exemple, les deux définies sur /MT). Pour éviter les inefficacités de build, telles que la construction de projets Arm64 complets plutôt que la simple compilation, définissez la propriété BuildAsX sur true pour toutes les références directes et indirectes du projet.

Le système de génération suppose que les configurations Arm64 et Arm64EC ont le même nom. Si les configurations Arm64 et Arm64EC ont des noms différents (comme Debug|ARM64 et MyDebug|ARM64EC), vous pouvez éditer manuellement le fichier vcxproj ou Directory.Build.props pour ajouter une propriété ARM64ConfigurationNameForX à la configuration Arm64EC qui fournit le nom de la configuration Arm64.

Si vous souhaitez que le fichier binaire Arm64X combine deux projets distincts, arm64 et arm64EC, vous pouvez modifier manuellement le vxcproj du projet Arm64EC pour ajouter une ARM64ProjectForX propriété et spécifier le chemin d’accès au projet Arm64. Les deux projets doivent être dans la même solution.

Génération d’une DLL Arm64X avec CMake

Pour générer vos fichiers binaires de projet CMake en tant qu’Arm64X, utilisez n’importe quelle version de CMake qui prend en charge la génération en tant qu’Arm64EC. Tout d’abord, générez le projet ciblant Arm64 pour générer les entrées de l’éditeur de liens Arm64. Ensuite, générez à nouveau le projet ciblant Arm64EC, combinant les entrées Arm64 et Arm64EC pour former des fichiers binaires Arm64X. Les étapes suivantes montrent comment utiliser CMakePresets.json.

  1. Vérifiez que vous disposez de présélections de configuration distinctes ciblant Arm64 et Arm64EC. Par exemple :

     {
       "version": 3,
       "configurePresets": [
         {
           "name": "windows-base",
           "hidden": true,
           "binaryDir": "${sourceDir}/out/build/${presetName}",
           "installDir": "${sourceDir}/out/install/${presetName}",
           "cacheVariables": {
             "CMAKE_C_COMPILER": "cl.exe",
             "CMAKE_CXX_COMPILER": "cl.exe"
           },
     	  "generator": "Visual Studio 17 2022",
         },
         {
           "name": "arm64-debug",
           "displayName": "arm64 Debug",
           "inherits": "windows-base",
           "hidden": true,
     	  "architecture": {
     		 "value": "arm64",
     		 "strategy": "set"
     	  },
           "cacheVariables": {
             "CMAKE_BUILD_TYPE": "Debug"
           }
         },
         {
           "name": "arm64ec-debug",
           "displayName": "arm64ec Debug",
           "inherits": "windows-base",
           "hidden": true,
           "architecture": {
             "value": "arm64ec",
             "strategy": "set"
           },
           "cacheVariables": {
             "CMAKE_BUILD_TYPE": "Debug"
           }
         }
       ]
     }
    
  2. Ajoutez deux nouvelles configurations qui héritent des présélections Arm64 et Arm64EC que vous avez créées à l’étape précédente. Définissez BUILD_AS_ARM64X la valeur ARM64EC dans la configuration qui hérite d’Arm64EC et BUILD_AS_ARM64X de ARM64 l’autre. Ces variables signifient que les compilations de ces deux préconfigurations constituent Arm64X.

         {
           "name": "arm64-debug-x",
           "displayName": "arm64 Debug (arm64x)",
           "inherits": "arm64-debug",
           "cacheVariables": {
             "BUILD_AS_ARM64X": "ARM64"
           }
           },
         {
           "name": "arm64ec-debug-x",
           "displayName": "arm64ec Debug (arm64x)",
           "inherits": "arm64ec-debug",
           "cacheVariables": {
             "BUILD_AS_ARM64X": "ARM64EC"
           }
           }
    
  3. Ajoutez un nouveau fichier .cmake à votre projet CMake appelé arm64x.cmake. Copiez l’extrait de code suivant dans le nouveau fichier .cmake.

     # directory where the link.rsp file generated during arm64 build will be stored
     set(arm64ReproDir "${CMAKE_CURRENT_SOURCE_DIR}/repros")
    
     # This function reads in the content of the rsp file outputted from arm64 build for a target. Then passes the arm64 libs, objs and def file to the linker using /machine:arm64x to combine them with the arm64ec counterparts and create an arm64x binary.
    
     function(set_arm64_dependencies n)
     	set(REPRO_FILE "${arm64ReproDir}/${n}.rsp")
     	file(STRINGS "${REPRO_FILE}" ARM64_OBJS REGEX obj\"$)
     	file(STRINGS "${REPRO_FILE}" ARM64_DEF REGEX def\"$)
     	file(STRINGS "${REPRO_FILE}" ARM64_LIBS REGEX lib\"$)
     	string(REPLACE "\"" ";" ARM64_OBJS "${ARM64_OBJS}")
     	string(REPLACE "\"" ";" ARM64_LIBS "${ARM64_LIBS}")
     	string(REPLACE "\"" ";" ARM64_DEF "${ARM64_DEF}")
     	string(REPLACE "/def:" "/defArm64Native:" ARM64_DEF "${ARM64_DEF}")
    
     	target_sources(${n} PRIVATE ${ARM64_OBJS})
     	target_link_options(${n} PRIVATE /machine:arm64x "${ARM64_DEF}" "${ARM64_LIBS}")
     endfunction()
    
     # During the arm64 build, create link.rsp files that containes the absolute path to the inputs passed to the linker (objs, def files, libs).
    
     if("${BUILD_AS_ARM64X}" STREQUAL "ARM64")
     	add_custom_target(mkdirs ALL COMMAND cmd /c (if not exist \"${arm64ReproDir}/\" mkdir \"${arm64ReproDir}\" ))
     	foreach (n ${ARM64X_TARGETS})
     		add_dependencies(${n} mkdirs)
     		# tell the linker to produce this special rsp file that has absolute paths to its inputs
     		target_link_options(${n} PRIVATE "/LINKREPROFULLPATHRSP:${arm64ReproDir}/${n}.rsp")
     	endforeach()
    
     # During the ARM64EC build, modify the link step appropriately to produce an arm64x binary
     elseif("${BUILD_AS_ARM64X}" STREQUAL "ARM64EC")
     	foreach (n ${ARM64X_TARGETS})
     		set_arm64_dependencies(${n})
     	endforeach()
     endif()
    

/LINKREPROFULLPATHRSP est pris en charge uniquement si vous générez à l’aide de l’éditeur de liens MSVC à partir de Visual Studio 17.11 ou version ultérieure.

Si vous devez utiliser un éditeur de liens plus ancien, copiez plutôt l’extrait de code suivant. Cet itinéraire utilise un indicateur /LINK_REPRO plus ancien. L’utilisation de l’itinéraire /LINK_REPRO entraîne un temps de génération global plus lent en raison de la copie de fichiers et présente des problèmes connus lors de l’utilisation du générateur Ninja.

# directory where the link_repro directories for each arm64x target will be created during arm64 build.
set(arm64ReproDir "${CMAKE_CURRENT_SOURCE_DIR}/repros")

# This function globs the linker input files that was copied into a repro_directory for each target during arm64 build. Then it passes the arm64 libs, objs and def file to the linker using /machine:arm64x to combine them with the arm64ec counterparts and create an arm64x binary.

function(set_arm64_dependencies n)
	set(ARM64_LIBS)
	set(ARM64_OBJS)
	set(ARM64_DEF)
	set(REPRO_PATH "${arm64ReproDir}/${n}")
	if(NOT EXISTS "${REPRO_PATH}")
		set(REPRO_PATH "${arm64ReproDir}/${n}_temp")
	endif()
	file(GLOB ARM64_OBJS "${REPRO_PATH}/*.obj")
	file(GLOB ARM64_DEF "${REPRO_PATH}/*.def")
	file(GLOB ARM64_LIBS "${REPRO_PATH}/*.LIB")

	if(NOT "${ARM64_DEF}" STREQUAL "")
		set(ARM64_DEF "/defArm64Native:${ARM64_DEF}")
	endif()
	target_sources(${n} PRIVATE ${ARM64_OBJS})
	target_link_options(${n} PRIVATE /machine:arm64x "${ARM64_DEF}" "${ARM64_LIBS}")
endfunction()

# During the arm64 build, pass the /link_repro flag to linker so it knows to copy into a directory, all the file inputs needed by the linker for arm64 build (objs, def files, libs).
# extra logic added to deal with rebuilds and avoiding overwriting directories.
if("${BUILD_AS_ARM64X}" STREQUAL "ARM64")
	foreach (n ${ARM64X_TARGETS})
		add_custom_target(mkdirs_${n} ALL COMMAND cmd /c (if exist \"${arm64ReproDir}/${n}_temp/\" rmdir /s /q \"${arm64ReproDir}/${n}_temp\") && mkdir \"${arm64ReproDir}/${n}_temp\" )
		add_dependencies(${n} mkdirs_${n})
		target_link_options(${n} PRIVATE "/LINKREPRO:${arm64ReproDir}/${n}_temp")
		add_custom_target(${n}_checkRepro ALL COMMAND cmd /c if exist \"${n}_temp/*.obj\" if exist \"${n}\" rmdir /s /q \"${n}\" 2>nul && if not exist \"${n}\" ren \"${n}_temp\" \"${n}\" WORKING_DIRECTORY ${arm64ReproDir})
		add_dependencies(${n}_checkRepro ${n})
	endforeach()

# During the ARM64EC build, modify the link step appropriately to produce an arm64x binary
elseif("${BUILD_AS_ARM64X}" STREQUAL "ARM64EC")
	foreach (n ${ARM64X_TARGETS})
		set_arm64_dependencies(${n})
	endforeach()
endif()
  1. En bas du fichier de niveau CMakeLists.txt supérieur dans votre projet, ajoutez l’extrait de code suivant. Veillez à remplacer le contenu des crochets angle par des valeurs réelles. Cette étape utilise le arm64x.cmake fichier que vous venez de créer.

     if(DEFINED BUILD_AS_ARM64X)
     	set(ARM64X_TARGETS <Targets you want to Build as ARM64X>)
     	include("<directory location of the arm64x.cmake file>/arm64x.cmake")
     endif()
    
  2. Compilez votre projet CMake en utilisant le préréglage Arm64 activé par Arm64X (arm64-debug-x).

  3. Générez votre projet CMake à l’aide du préréglage Arm64X compatible Arm64EC (arm64ec-debug-x). Les DLL finales dans le répertoire de sortie de cette build sont des fichiers binaires Arm64X.

Créer un DLL de transfert pur Arm64X

Un DLL de transfert pur Arm64X est un petit DLL Arm64X qui transfère les API à des DLL séparées en fonction de leur type :

  • Les API Arm64 sont transférées à une DLL Arm64.

  • les API x64 sont transférées à un DLL x64 ou Arm64EC.

Un transfert pur Arm64X accorde les avantages de l’utilisation d’un binaire Arm64X même en cas de difficultés avec la création d’un binaire Arm64X fusionné contenant tout le code Arm64EC et Arm64. Pour plus d’informations, consultez les fichiers PE Arm64X.

Il est possible de construire un transmetteur Arm64X pur à partir de l'Invite de commandes pour développeur Arm64 en suivant les étapes ci-dessous. Le redirecteur pur Arm64X résultant route les appels x64 vers foo_x64.DLL et Arm64 vers foo_arm64.DLL.

  1. Créez des fichiers vides OBJ que l’éditeur de liens utilise pour créer le redirecteur pur. Ces fichiers sont vides, car le redirecteur pur ne contient aucun code. Pour créer ces fichiers, créez un fichier vide. Dans l’exemple suivant, le fichier est nommé empty.cpp. Permet cl de créer des fichiers vides OBJ , avec un pour Arm64 (empty_arm64.obj) et un pour Arm64EC (empty_x64.obj) :

    cl /c /Foempty_arm64.obj empty.cpp
    cl /c /arm64EC /Foempty_x64.obj empty.cpp
    

    Si vous voyez le message d’erreur « cl : Avertissement de ligne de commande D9002 : ignorer l’option inconnue « -arm64EC », vous utilisez le compilateur incorrect. Pour résoudre ce problème, basculez vers l’Invite de Commandes Développeur Arm64.

  2. Créez des fichiers DEF pour x64 et Arm64. Ces fichiers répertorient toutes les exportations d’API de la DLL et pointent le chargeur vers le nom de la DLL qui peut répondre à ces appels d’API.

    foo_x64.def :

    EXPORTS
        MyAPI1  =  foo_x64.MyAPI1
        MyAPI2  =  foo_x64.MyAPI2
    

    foo_arm64.def :

    EXPORTS
        MyAPI1  =  foo_arm64.MyAPI1
        MyAPI2  =  foo_arm64.MyAPI2
    
  3. Utilisez link pour créer LIB des fichiers d'importation pour les deux plateformes x64 et Arm64 :

    link /lib /machine:x64 /def:foo_x64.def /out:foo_x64.lib
    link /lib /machine:arm64 /def:foo_arm64.def /out:foo_arm64.lib
    
  4. Liez les fichiers vides OBJ et importés LIB à l’aide du paramètre /MACHINE:ARM64X pour produire la DLL redirecteur pur Arm6X :

    link /dll /noentry /machine:arm64x /defArm64Native:foo_arm64.def /def:foo_x64.def empty_arm64.obj empty_x64.obj /out:foo.dll foo_arm64.lib foo_x64.lib
    

Le foo.dll résultant peut être chargé dans un processus Arm64 ou x64/Arm64EC. Lorsqu’un processus Arm64 se charge foo.dll, le système d’exploitation charge foo_arm64.dll immédiatement à sa place et tous les appels d’API sont gérés par foo_arm64.dll.