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

Normalerweise würde Visual Studio beim Erstellen eines Projekts eine Kompilierung für Arm64EC durchführen und dann die Ausgaben mit einer Arm64EC-Binärdatei verknüpfen. Wenn BuildAsX auf true festgelegt ist, kompiliert Visual Studio stattdessen sowohl für Arm64EC als auch für . Im Arm64EC-Verknüpfungsschritt werden dann beide mit einer einzelnen Arm64X-Binärdatei verknüpft. Das Ausgabeverzeichnis für diese Arm64X-Binärdatei entspricht dem Ausgabeverzeichnis, das unter der Arm64EC-Konfiguration festgelegt ist.

Damit BuildAsX ordnungsgemäß funktioniert, muss der Benutzer zusätzlich zur Arm64EC-Konfiguration über eine vorhandene Arm64-Konfiguration verfügen. Die Arm64- und Arm64EC-Konfigurationen müssen dieselbe C-Runtime- und C++-Standardbibliothek aufweisen (z. B. beides auf /MT) festgelegt. Um Ineffizienzen zu vermeiden, z. B. das Erstellen vollständiger Arm64-Projekte und nicht nur die Kompilierung, sollte bei allen direkten und indirekten Verweise des Projekts die Option BuildAsX auf „true“ festgelegt sein.

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 die gewünschte Arm64X-Binärdatei eine Kombination aus zwei separaten Projekten ist, eines davon Arm64 und und das andere Arm64EC, können Sie 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, können Sie jede Version von CMake verwenden, die das Erstellen als Arm64EC unterstützt. Der Prozess umfasst zunächst die Erstellung des Projekts für Arm64, um die Arm64-Linkereingaben zu generieren. Anschließend sollte das Projekt erneut auf Arm64EC ausgerichtet werden, diesmal mit der Kombination der Arm64- und Arm64EC-Eingaben, um Arm64X-Binärdateien zu bilden. Die folgenden Schritte nutzen die Verwendung von CMakePresets.json.

  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 oben genannten Voreinstellungen "Arm64" und "Arm64EC" erben. Legen Sie diesen ARM64EC Wert BUILD_AS_ARM64X in der Konfiguration fest, die von Arm64EC und BUILD_AS_ARM64X ARM64 in der anderen Datei erbt. Diese Variablen werden verwendet, um zu kennzeichnen, 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 hinzu, die aufgerufen wird arm64x.cmake. 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 17.11 oder höher erstellen.

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 winkeligen Klammern durch tatsächliche Werte zu ersetzen. Dadurch wird die Datei verwendet, die arm64x.cmake Sie soeben erstellt haben.

     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-fähigen Arm64-Voreinstellung (arm64-debug-x).

  3. Erstellen Sie Ihr CMake-Projekt mit der Arm64X-fähigen Arm64EC-Voreinstellung (arm64ec-debug-x). Die endgültigen DLL-Dateien, die im Ausgabeverzeichnis für diesen Build enthalten sind, 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. Erfahren Sie mehr über reine Arm64X-Weiterleitungs-DLLs auf der Übersichtsseite für Arm64X PE-Dateien.

Sie können eine reine Arm64X-Weiterleitung über die Arm64-Entwickler-Eingabeaufforderung erstellen, indem Sie die nachfolgenden Schritte befolgen. Die daraus resultierende reine Arm64X-Weiterleitung leitet x64-Aufrufe an foo_x64.DLL und Arm64-Aufrufe an foo_arm64.DLL weiter.

  1. Erstellen Sie leere OBJ-Dateien, die später vom Linker zum Erstellen der reinen Weiterleitung verwendet werden. Diese sind leer, da die reine Weiterleitungen keinen Code enthält. Erstellen Sie dazu eine leere Datei. Im folgenden Beispiel wurde die Datei empty.cpp benannt. Leere OBJ-Dateien werden dann mit cl erstellt, einmal für Arm64 (empty_arm64.obj) und einmal 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 : Command line warning D9002 : ignoring unknown option '-arm64EC“ angezeigt wird, wird der falsche Compiler verwendet. Um dies zu beheben, wechseln Sie zur Arm64 Developer-Eingabeaufforderung.

  2. Erstellen Sie DEF-Dateien für x64 und Arm64. Diese Dateien enumerieren alle API-Exporte der DLL und verweisen das Ladeprogramm auf den Namen der DLL, die diese API-Aufrufe erfüllen 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. Anschließend können Sie link verwenden, 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. Wenn ein Arm64-Prozess foo.dll lädt, lädt das Betriebssystem stattdessen sofort foo_arm64.dll, und alle API-Aufrufe werden von foo_arm64.dll verarbeitet.