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ération d’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

Lorsqu’un utilisateur génère un projet, Visual Studio compile normalement pour Arm64EC puis lie les sorties en un binaire Arm64EC. Lorsque BuildAsX est paramétré sur true, Visual Studio compilera à la place pour Arm64EC et Arm64. L’étape de liaison Arm64EC est ensuite utilisée pour lier les deux ensemble en un seul binaire Arm64X. Le répertoire de sortie pour ce binaire Arm64X sera celui qui est défini sous la configuration Arm64EC

Pour que BuildAsX fonctionne correctement, l’utilisateur doit avoir une configuration Arm64 existante, en plus de la configuration Arm64EC. Les configurations Arm64 et Arm64EC doivent avoir le même runtime C et la même bibliothèque standard C++ (par exemple, les deux définissent /MT). Pour éviter les inefficacités de construction, telles que la construction de projets Arm64 complets plutôt que juste la compilation, toutes les références directes et indirectes du projet doivent avoir BuildAsX réglé sur vrai (True).

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 le binaire Arm64X souhaité est une combinaison de deux projets séparés, l’un en tant qu’Arm64 et l’autre en tant qu’Arm64EC, vous pouvez éditer manuellement le vxcproj du projet Arm64EC pour ajouter une propriété ARM64ProjectForX et spécifier le chemin vers le 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, vous pouvez utiliser n’importe quelle version de CMake qui prend en charge la génération en tant qu’Arm64EC. Le processus implique de créer initialement le projet ciblant Arm64 pour générer les entrées de l’éditeur de liens Arm64. Par la suite, le projet doit être redécodé ciblant Arm64EC, cette fois en combinant les entrées Arm64 et Arm64EC pour former des fichiers binaires Arm64X. Les étapes ci-dessous tirent parti de l’utilisation de 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 ci-dessus. 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 seront utilisées pour indiquer que les builds de ces deux présélections font partie d’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 ci-dessous 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 créez 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 ci-dessous. 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 ci-dessous. Veillez à remplacer le contenu des crochets angle par des valeurs réelles. Cela utilisera le arm64x.cmake fichier que vous venez de créer ci-dessus.

     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. Générez votre projet CMake à l’aide de la présélection Arm64X compatible Arm64 (arm64-debug-x).

  3. Générez votre projet CMake à l’aide de la présélection Arm64X compatible Arm64EC (arm64ec-debug-x). La ou les dll finales contenues dans le répertoire de sortie de cette build seront 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. Vous pouvez en apprendre davantage concernant les DLL de transfert pur Arm64X dans la page de vue d’ensemble des fichiers PE Arm64X.

Vous pouvez créer un transfert pur Arm64X à partir de la commande développeur Arm64 en suivant la procédure ci-dessous. Le transfert pur Arm64X résultant acheminera les appels x64 vers foo_x64.DLL et les appels Arm64 vers foo_arm64.DLL.

  1. Créez des fichiers OBJ vides qui seront utilisés plus tard par l’éditeur de liens pour créer le transfert pur. Ceux-ci sont vides car le transfert pur ne contient pas de code Pour cela, créez un fichier vide. Pour l’exemple ci-dessous, nous avons nommé le fichier empty.cpp. Des fichiers OBJ vides sont ensuite créés en utilisant cl, 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 le message d’erreur « cl : Command line warning D9002 : ignoring unknown option "-arm64EC" » apparaît, le compilateur incorrect est utilisé. Pour résoudre ce problème, passez à l’invite de commandes du développeur Arm64.

  2. Créez des fichiers DEF pour x64 et Arm64. Ces fichiers énumèrent toutes les exportations d’API du DLL et indiquent au chargeur le nom du DLL qui peut répondre à ces appels 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. Vous pouvez ensuite utiliser link pour créer des fichiers d’importation LIB pour 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 OBJ vides et importez LIB en utilisant l’indicateur /MACHINE:ARM64X pour produire le DLL de transfert 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. Quand un processus Arm64 charge foo.dll, le système d’exploitation chargera immédiatement foo_arm64.dll à sa place et tout appel API sera géré par foo_arm64.dll.