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.
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.
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" } } ] }
Aggiungere due nuove configurazioni che ereditano dai set di impostazioni arm64 e Arm64EC precedenti. Impostare
BUILD_AS_ARM64X
suARM64EC
nella configurazione che eredita da Arm64EC eBUILD_AS_ARM64X
suARM64
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" }
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()
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 ilarm64x.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()
Compilare il progetto CMake usando il set di impostazioni Arm64X abilitato per Arm64 (arm64-debug-x).
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
.
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 fileOBJ
vuoti vengono quindi creati utilizzandocl
, 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.
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
Puoi quindi utilizzare
link
per creare i file di importazione diLIB
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
Collega i file vuoti
OBJ
e importa i fileLIB
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
.