Compartir a través de


Compilación de archivos binarios Arm64X

Puede compilar archivos binarios Arm64X, también conocidos como archivos ARM64X PE, para admitir la carga de un solo archivo binario en procesos x64/Arm64EC y Arm64.

Compilación de un archivo binario arm64X desde un proyecto de Visual Studio

Para habilitar la compilación de archivos binarios arm64X, las páginas de propiedades de la configuración de Arm64EC tienen una nueva propiedad llamada "Build Project as ARM64X", conocida como BuildAsX en el archivo de proyecto.

Página de propiedades de una configuración de Arm64EC que muestra la opción Compilar proyecto como ARM64X

Cuando un usuario compila un proyecto, Visual Studio normalmente lo compilaría para Arm64EC y, a continuación, vincularía las salidas a un archivo binario Arm64EC. Cuando BuildAsX se establece en true, Visual Studio lo compilará en su lugar para Arm64EC y Arm64. A continuación, el paso de vinculación Arm64EC se usa para vincular ambos en un único archivo binario arm64X. El directorio de salida para este archivo binario Arm64X será cualquiera que sea el directorio de salida establecido en la opción Configuración de Arm64EC.

Para que BuildAsX funcione correctamente, el usuario debe tener una configuración de Arm64 existente, además de la configuración de Arm64EC. Las configuraciones de Arm64 y Arm64EC deben tener el mismo entorno de ejecución de C y la misma biblioteca estándar de C++ (por ejemplo, ambas establecidas en /MT). Para evitar las ineficiencias de compilación, como la compilación de proyectos completos de Arm64 en lugar de solo compilación, todas las referencias directas e indirectas del proyecto deben tener el valor de BuildAsX establecido en true.

El sistema de compilación supone que las configuraciones de Arm64 y Arm64EC tienen el mismo nombre. Si las configuraciones de Arm64 y Arm64EC tienen nombres diferentes (como Debug|ARM64 y MyDebug|ARM64EC), puede editar manualmente el archivo vcxproj o Directory.Build.props para añadir una propiedad ARM64ConfigurationNameForX a la configuración de Arm64EC que proporcione el nombre de la configuración de Arm64.

Si el archivo binario Arm4X deseado es una combinación de dos proyectos independientes, uno como Arm64 y otro como Arm64EC, puede editar manualmente el vxcproj del proyecto Arm64EC para añadir una propiedad ARM64ProjectForX y especificar la ruta de acceso al proyecto de Arm64. Los dos proyectos deben estar en la misma solución.

Creación de un archivo DLL de Arm64X con CMake

Para compilar los archivos binarios del proyecto de CMake como Arm64X, puede usar cualquier versión de CMake que admita la compilación como Arm64EC. El proceso implica crear inicialmente el proyecto destinado a Arm64 para generar las entradas del enlazador Arm64. Posteriormente, el proyecto se debe compilar de nuevo como destino Arm64EC, esta vez combinando las entradas Arm64 y Arm64EC para formar archivos binarios Arm64X. Los pasos siguientes aprovechan el uso de CMakePresets.json.

  1. Asegúrese de que tiene valores preestablecidos de configuración independientes destinados a Arm64 y Arm64EC. Por ejemplo:

     {
       "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. Agregue dos configuraciones nuevas que hereden de los valores preestablecidos arm64 y Arm64EC anteriores. Establezca BUILD_AS_ARM64X en ARM64EC en la configuración que hereda de Arm64EC y BUILD_AS_ARM64X en ARM64 en la otra. Estas variables se usarán para indicar que las compilaciones de estos dos valores preestablecidos forman parte de 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. Agregue un nuevo archivo .cmake al proyecto de CMake denominado arm64x.cmake. Copie el fragmento de código siguiente en el nuevo archivo .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 solo se admite si va a compilar mediante el enlazador MSVC desde Visual Studio 17.11 o posterior.

Si necesita usar un enlazador anterior, copie el fragmento de código siguiente en su lugar. Esta ruta usa una marca /LINK_REPRO anterior. El uso de la ruta /LINK_REPRO dará lugar a un tiempo de compilación general más lento debido a la copia de archivos y tiene problemas conocidos al usar el generador 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 la parte inferior del archivo de nivel CMakeLists.txt superior del proyecto, agregue el fragmento de código siguiente. Asegúrese de sustituir el contenido de los corchetes angulares por valores reales. Esto consumirá el arm64x.cmake archivo que acaba de crear anteriormente.

     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. Compile el proyecto de CMake mediante el valor preestablecido arm64 habilitado para Arm64X (arm64-debug-x).

  3. Compile el proyecto de CMake mediante el valor preestablecido arm64EC habilitado para Arm64X (arm64ec-debug-x). Los archivos DLL finales incluidos en el directorio de salida de esta compilación serán archivos binarios arm64X.

Compilación de un archivo DLL reenviador puro Arm64X

Un archivo DLL reenviador puro Arm64X es un pequeño archivo DLL Arm64X que reenvía las API a archivos DLL independientes en función de su tipo:

  • Las API de Arm64 se reenvieron a un archivo DLL de Arm64.

  • las API x64 se reenvían a un archivo DLL x64 o Arm64EC.

Un reenviador puro Arm64Xpermite las ventajas de utilizar un archivo binario Arm64X incluso si hay problemas al compilar un archivo binario Arm64X combinado que contenga todo el código Arm64EC y Arm64. Puede obtener más información sobre los archivos DLL reenviador puro Arm64X en la página de información general de los archivos Arm64X PE.

Puede compilar un reenviador puro Arm64X desde el símbolo del sistema para desarrolladores de Arm64 siguiendo los pasos que se indican a continuación. El reenviador puro Arm64X resultante enrutará las llamadas x64 a foo_x64.DLL y las llamadas de Arm64 a foo_arm64.DLL.

  1. Cree archivos OBJ vacíos que usará más adelante el enlazador para crear el reenviador puro. Estos están vacíos, ya que el reenviador puro no tiene código en él. Para ello, cree un archivo vacío. En el ejemplo siguiente, hemos llamado al archivo empty.cpp. A continuación, se crean archivos OBJ vacíos mediante cl, con uno para Arm64 (empty_arm64.obj) y otro para Arm64EC (empty_x64.obj):

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

    Si aparece el mensaje de error “cl : Command line warning D9002 : ignoring unknown option '-arm64EC'”, significa que se está utilizando el compilador incorrecto. Para resolverlo, cambie al símbolo del sistema para desarrolladores de Arm64.

  2. Cree archivos DEF para x64 y Arm64. Estos archivos enumeran todas las exportaciones de API del archivo DLL y apuntan el cargador al nombre del archivo DLL que puede cumplir esas llamadas 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. A continuación, puede usar link para crear archivos de importación LIB para x64 y 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. Vincule el OBJ vacío e importe los archivos LIB con el indicador /MACHINE:ARM64X para generar el archivo DLL reenviador puro 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
    

El foo.dll resultante se puede cargar en un proceso Arm64 o x64/Arm64EC. Cuando un proceso Arm64 carga foo.dll, el sistema operativo cargará foo_arm64.dll inmediatamente en su lugar y cualquier llamada a la API se controlará mediante foo_arm64.dll.