Freigeben über


Erstellen von Arm64X-Binärdateien

Erstellen Sie Arm64X-Binärdateien, auch als Arm64X PE-Dateien bezeichnet, um das Laden einer einzelnen Binärdatei in x64/Arm64EC- und Arm64-Prozesse zu unterstützen.

Erstellen einer Arm64X-Binärdatei aus einem Visual Studio-Projekt

Um das Erstellen von Arm64X-Binärdateien zu ermöglichen, verfügt die Seite „Property“ der Arm64EC-Konfiguration über die neue Eigenschaft „Build Project as ARM64X“, die in der Projektdatei als BuildAsX bezeichnet wird.

Eigenschaftenseite für eine Arm64EC-Konfiguration mit der Option

Wenn Sie ein Projekt erstellen, kompiliert Visual Studio normalerweise für Arm64EC und verknüpft dann die Ausgaben zu einer Arm64EC-Binärdatei. Wenn Sie BuildAsX auf true festlegen, kompiliert Visual Studio sowohl für Arm64EC als auch für Arm64. Der Arm64EC-Verknüpfungsschritt verknüpft beide Ausgaben mit einer einzelnen Arm64X-Binärdatei. Das Ausgabeverzeichnis für diese Arm64X-Binärdatei ist das Ausgabeverzeichnis, das unter der Arm64EC-Konfiguration festgelegt ist.

Damit BuildAsX ordnungsgemäß funktioniert, müssen Sie neben der Arm64EC-Konfiguration auch über eine vorhandene Arm64-Konfiguration verfügen. Die Arm64- und Arm64EC-Konfigurationen müssen dieselbe C-Laufzeit- und C++-Standardbibliothek verwenden (z. B. beide auf /MT festgelegt). Um Ineffizienzen zu vermeiden, z. B. das Erstellen vollständiger Arm64-Projekte und nicht nur die Kompilierung, legen Sie BuildAsX für alle direkten und indirekten Verweise des Projekts auf "true" fest.

Das Buildsystem geht davon aus, dass die Konfigurationen Arm64 und Arm64EC denselben Namen haben. Wenn die Arm64- und Arm64EC-Konfigurationen unterschiedliche Namen haben (wie Debug|ARM64 und MyDebug|ARM64EC), können Sie vcxproj oder die Datei Directory.Build.props manuell bearbeiten, um der Arm64EC-Konfiguration eine ARM64ConfigurationNameForX-Eigenschaft hinzuzufügen, die den Namen der Arm64-Konfiguration bereitstellt.

Wenn Sie möchten, dass die Arm64X-Binärdatei zwei separate Projekte kombiniert, eins als Arm64 und eins als Arm64EC, können Sie das vxcproj des Arm64EC-Projekts manuell bearbeiten, um eine ARM64ProjectForX Eigenschaft hinzuzufügen und den Pfad zum Arm64-Projekt anzugeben. Die beiden Projekte müssen sich in derselben Lösung befinden.

Erstellen einer Arm64X-DLL mit CMake

Um Ihre CMake-Projektbinärdateien als Arm64X zu erstellen, verwenden Sie eine beliebige Version von CMake , die das Erstellen als Arm64EC unterstützt. Zunächst erstellen Sie das Projekt für Arm64, um Eingaben für den Arm64-Linker zu generieren. Erstellen Sie dann das Projekt erneut für Arm64EC, wobei die Arm64- und Arm64EC-Eingaben kombiniert werden, um Arm64X-Binärdateien zu bilden. Die folgenden Schritte zeigen, wie CMakePresets.json verwendet wird.

  1. Stellen Sie sicher, dass Sie über separate Konfigurationsvoreinstellungen für Arm64 und Arm64EC verfügen. Zum Beispiel:

     {
       "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. Fügen Sie zwei neue Konfigurationen hinzu, die von den im vorherigen Schritt erstellten Voreinstellungen "Arm64" und "Arm64EC" erben. Setzen Sie BUILD_AS_ARM64X auf ARM64EC in der Konfiguration, die von Arm64EC erbt, und BUILD_AS_ARM64X auf ARM64 in der anderen. Diese Variablen bedeuten, dass die Builds aus diesen beiden Voreinstellungen Teil von Arm64X sind.

         {
           "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. Fügen Sie Ihrem CMake-Projekt eine neue .cmake-Datei namens arm64x.cmake hinzu. Kopieren Sie den folgenden Codeausschnitt in die neue CMAKE-Datei.

     # 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 wird nur unterstützt, wenn Sie den MSVC-Linker aus Visual Studio Version 17.11 oder höher verwenden.

Wenn Sie einen älteren Linker verwenden müssen, kopieren Sie stattdessen den folgenden Codeausschnitt. Diese Route verwendet ein älteres Flag /LINK_REPRO. Die Verwendung der Route "/LINK_REPRO" führt zu einer langsameren Gesamtbuildzeit aufgrund des Kopierens von Dateien und hat bekannte Probleme bei der Verwendung des Ninja-Generators.

# 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. Fügen Sie unten in der Datei auf oberster Ebene CMakeLists.txt in Ihrem Projekt den folgenden Codeausschnitt hinzu. Achten Sie darauf, den Inhalt der spitzen Klammern durch tatsächliche Werte zu ersetzen. Dieser Schritt verwendet die arm64x.cmake soeben erstellte Datei.

     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. Erstellen Sie Ihr CMake-Projekt mit der Arm64X-aktivierten Arm64-Voreinstellung "arm64-debug-x".

  3. Erstellen Sie Ihr CMake-Projekt mithilfe der Arm64X-fähigen Arm64EC-Voreinstellung (arm64ec-debug-x). Die endgültigen DLLs im Ausgabeverzeichnis für diesen Build sind Arm64X-Binärdateien.

Erstellen einer reinen Arm64X-Weiterleitungs-DLL

Eine reine Arm64X-Weiterleitungs-DLL ist eine kleine Arm64X-DLL, die APIs je nach Typ an separate DLLs weiterleitet:

  • Arm64-APIs werden an eine Arm64-DLL weitergeleitet.

  • x64-APIs werden an eine x64- oder Arm64EC-DLL weitergeleitet.

Eine reine Arm64X-Weiterleitung ermöglicht die Vorteile der Verwendung einer Arm64X-Binärdatei, auch wenn es Herausforderungen beim Erstellen einer zusammengeführten Arm64X-Binärdatei mit dem gesamten Arm64EC- und Arm64-Code gibt. Weitere Informationen finden Sie unter Arm64X PE-Dateien.

Sie können eine reine Arm64X-Weiterleitung über die Arm64-Entwickler-Eingabeaufforderung erstellen, indem Sie die folgenden Schritte ausführen. Die resultierende Arm64X-Reinweiterleitung leitet x64-Anrufe an foo_x64.DLL und Arm64-Anrufe an foo_arm64.DLL.

  1. Erstellen Sie leere OBJ-Dateien, die der Linker verwendet, um die reine Weiterleitungsdatei zu erstellen. Diese Dateien sind leer, da die reine Weiterleitung keinen Code enthält. Um diese Dateien zu erstellen, erstellen Sie eine leere Datei. Im folgenden Beispiel wird die Datei empty.cpp benannt. Dient cl zum Erstellen leerer OBJ Dateien mit einer für Arm64 (empty_arm64.obj) und einer für Arm64EC (empty_x64.obj):

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

    Wenn die Fehlermeldung "cl : Befehlszeilenwarnung D9002 : Unbekannte Option '-arm64EC' ignoriert" angezeigt wird, verwenden Sie den falschen Compiler. Um dieses Problem zu beheben, wechseln Sie zur Arm64 Developer-Eingabeaufforderung.

  2. Erstellen Sie DEF-Dateien für x64 und Arm64. Diese Dateien listen alle API-Exporte der DLL auf und leiten den Loader zum Namen der DLL, die diese API-Aufrufe ausführen kann.

    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. Verwenden Sie link, um LIB-Importdateien für x64 und Arm64 zu erstellen.

    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. Verknüpfen Sie die leeren OBJ Dateien und importieren Sie LIB dateien mithilfe des Flags /MACHINE:ARM64X , um die reine Arm6X-Weiterleitungs-DLL zu erzeugen:

    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
    

Die daraus resultierende foo.dll kann entweder in einen Arm64- oder einen x64/Arm64EC-Prozess geladen werden. Sobald ein Arm64-Prozess foo.dll lädt, lädt das Betriebssystem unmittelbar foo_arm64.dll an dessen Stelle, und alle API-Aufrufe werden von foo_arm64.dll bearbeitet.