Condividi tramite


Costruisci i binari Arm64X

Puoi creare binari Arm64X, noti anche come Arm64X PE files, per supportare il caricamento di un singolo binario sia in processi x64/Arm64EC che Arm64.

Costruire un binario Arm64X da un progetto di Visual Studio

Per consentire la creazione di binari Arm64X, le pagine delle proprietà della configurazione Arm64EC hanno una nuova proprietà "Build Project as ARM64X", nota come BuildAsX nel file di progetto.

Pagina delle proprietà per una configurazione arm64EC che mostra l'opzione Compila progetto come ARM64X

Quando un utente compila un progetto, Visual Studio normalmente compila per Arm64EC e poi collega i risultati in un binario Arm64EC. Quando BuildAsX è impostato su true, Visual Studio compilerà per Arm64EC e Arm64. Il passo di collegamento Arm64EC viene quindi utilizzato per collegare entrambi in un unico binario Arm64X. La directory di output per questo binario Arm64X sarà quella impostata nella configurazione di Arm64EC.

Affinché BuildAsX funzioni correttamente, l'utente deve avere una configurazione Arm64 esistente, oltre alla configurazione Arm64EC. Le configurazioni Arm64 e Arm64EC devono avere lo stesso runtime C e la stessa libreria standard C++ (ad esempio, entrambe impostate su /MT). Per evitare inefficienze nella compilazione, come ad esempio la creazione di progetti Arm64 completi piuttosto che la sola compilazione, tutti i riferimenti diretti e indiretti del progetto devono avere BuildAsX impostato su true.

Il sistema di compilazione presuppone che le configurazioni Arm64 e Arm64EC abbiano lo stesso nome. Se le configurazioni Arm64 e Arm64EC hanno nomi diversi (come Debug|ARM64 e MyDebug|ARM64EC), puoi modificare manualmente il file vcxproj o Directory.Build.props per aggiungere una proprietà ARM64ConfigurationNameForX alla configurazione Arm64EC che fornisce il nome della configurazione Arm64.

Se il binario Arm64X desiderato è una combinazione di due progetti separati, uno Arm64 e uno Arm64EC, puoi modificare manualmente il vxcproj del progetto Arm64EC per aggiungere una proprietà ARM64ProjectForX e specificare il percorso del progetto Arm64. I due progetti devono essere nella stessa soluzione.

Compilazione di una DLL Arm64X con CMake

Per compilare i file binari del progetto CMake come Arm64X, è possibile usare qualsiasi versione di CMake che supporta la compilazione come Arm64EC. Il processo prevede inizialmente la compilazione del progetto destinato ad Arm64 per generare gli input del linker Arm64. Successivamente, il progetto deve essere compilato di nuovo per Arm64EC, questa volta combinando gli input Arm64 e Arm64EC per formare file binari Arm64X. I passaggi seguenti sfruttano l'uso di CMakePresets.json.

  1. Assicurarsi di avere set di impostazioni di configurazione separati destinati ad Arm64 e Arm64EC. Ad esempio:

     {
       "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. Aggiungere due nuove configurazioni che ereditano dai set di impostazioni arm64 e Arm64EC precedenti. Impostare BUILD_AS_ARM64X su ARM64EC nella configurazione che eredita da Arm64EC e BUILD_AS_ARM64X su ARM64 nell'altro. Queste variabili verranno usate per indicare che le build di questi due set di impostazioni fanno parte di 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. Aggiungere un nuovo file con estensione cmake al progetto CMake denominato arm64x.cmake. Copiare il frammento di codice seguente nel nuovo file con estensione 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 è supportato solo se si compila usando il linker MSVC da Visual Studio 17.11 o versione successiva.

Se è necessario usare un linker precedente, copiare invece il frammento di codice seguente. Questa route usa un flag /LINK_REPRO precedente. L'uso della route /LINK_REPRO comporterà un tempo di compilazione complessivo più lento a causa della copia dei file e presenta problemi noti quando si usa il generatore 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. Nella parte inferiore del file di primo livello CMakeLists.txt del progetto aggiungere il frammento di codice seguente. Assicurarsi di sostituire il contenuto delle parentesi angolari con i valori effettivi. Verrà usato il arm64x.cmake file appena creato in precedenza.

     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. Compilare il progetto CMake usando il set di impostazioni Arm64X abilitato per Arm64 (arm64-debug-x).

  3. Compilare il progetto CMake usando il set di impostazioni Arm64X abilitato per Arm64EC (arm64ec-debug-x). Le DLL finali contenute nella directory di output per questa compilazione saranno file binari Arm64X.

Costruire una DLL per forwarder puro Arm64X

Una DLL forwarder pura Arm64X è una piccola DLL Arm64X che inoltra le API a DLL separate a seconda del loro tipo:

  • Le API Arm64 vengono inoltrate a una DLL Arm64.

  • le API x64 vengono inoltrate a una DLL x64 o Arm64EC.

Un forwarder Arm64X puro permette di ottenere i vantaggi dell'utilizzo di un binario Arm64X anche se ci sono problemi nella costruzione di un binario Arm64X unito contenente tutto il codice Arm64EC e Arm64. Per saperne di più sulle DLL Arm64X pure forwarder, consulta la pagina Arm64X PE files .

Puoi costruire un forwarder Arm64X puro dal prompt dei comandi dello sviluppatore Arm64 seguendo i passi seguenti. Il forwarder puro Arm64X risultante indirizzerà le chiamate x64 a foo_x64.DLL e le chiamate Arm64 a foo_arm64.DLL.

  1. Crea dei file OBJ vuoti che verranno poi utilizzati dal linker per creare il forwarder puro. Questi sono vuoti perché il forwarder puro non contiene alcun codice. Per farlo, crea un file vuoto. Per l'esempio seguente, abbiamo chiamato il file empty.cpp. I file OBJ vuoti vengono quindi creati utilizzando cl, con uno per Arm64 (empty_arm64.obj) e uno per Arm64EC (empty_x64.obj):

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

    Se il messaggio di errore "cl : Viene visualizzato l'avviso della riga di comando D9002: ignorando l'opzione sconosciuta '-arm64EC'", viene utilizzato un compilatore non corretto. Per risolvere il problema, passare al prompt dei comandi per gli sviluppatori arm64.

  2. Crea i file DEF sia per x64 che per Arm64. Questi file enumerano tutte le esportazioni API della DLL e indicano al caricatore il nome della DLL in grado di eseguire tali chiamate 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. Puoi quindi utilizzare link per creare i file di importazione di LIB sia per x64 che per 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. Collega i file vuoti OBJ e importa i file LIB utilizzando il flag /MACHINE:ARM64X per produrre la DLL del forwarder 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
    

Il foo.dll risultante può essere caricato in un processo Arm64 o x64/Arm64EC. Quando un processo Arm64 carica foo.dll, il sistema operativo caricherà immediatamente foo_arm64.dll al suo posto e qualsiasi chiamata API sarà gestita da foo_arm64.dll.