Arm64X バイナリをビルドする

Arm64X バイナリ (Arm64X PE ファイルとも呼ばれます) をビルドすると、x64/Arm64EC プロセスと Arm64 プロセスの両方に 1 つのバイナリを読み込ませることができます。

Visual Studio プロジェクトから Arm64X バイナリをビルドする

Arm64X バイナリのビルドを有効にするために、Arm64EC 構成のプロパティ ページには、新しい [Build Project as ARM64X] プロパティがあります。これは、プロジェクトファイルでは BuildAsX として知られています。

[Build Project as ARM64X]\(ARM64X としてのプロジェクトのビルド\) オプションが表示されている Arm64EC 構成のプロパティ ページ

ユーザーがプロジェクトをビルドすると、通常、Visual Studio は Arm64EC 用にコンパイルし、出力を Arm64EC バイナリにリンクします。 BuildAsXtrue に設定すると、Visual Studio は代わりに、Arm64EC および Arm64 の両方でコンパイルします。 次に、Arm64EC リンク ステップを使用して、両方を 1 つの Arm64X バイナリにリンクします。 この Arm64X バイナリの出力ディレクトリは、Arm64EC 構成で出力ディレクトリが設定されているものになります。

BuildAsX正常に動作させるには、Arm64EC 構成に加えて、既存の Arm64 構成が必要です。 Arm64 と Arm64EC の構成には、C ランタイムと C++ 標準ライブラリが同じである必要があります (たとえば、両方とも /MT を設定します)。 コンパイルだけでなく、完全なArm64 プロジェクトをビルドするなど、ビルドの非効率性を回避するために、プロジェクトのすべての直接参照と間接参照の BuildAsX が True と設定されている必要があります。

ビルド システムでは、Arm64 構成と Arm64EC 構成の名前が同じであると想定されています。 Arm64 構成と Arm64EC 構成の名前が異なる場合 (たとえば、Debug|ARM64MyDebug|ARM64EC)、vcxproj または Directory.Build.props ファイルを手動で編集すると、Arm64 構成の名前を提供する Arm64EC 構成に ARM64ConfigurationNameForX プロパティを追加できます。

目的の Arm64X バイナリが 2 つの別個のプロジェクト (1 つは Arm64、1 つは Arm64EC) の組み合わせである場合は、Arm64EC プロジェクトの vxcproj を手動で編集して ARM64ProjectForX プロパティを追加すると、Arm64 プロジェクトへのパスを指定できます。 2 つのプロジェクトは同じソリューション内にある必要があります。

CMake を使用した Arm64X DLL のビルド

CMake プロジェクト バイナリを Arm64X としてビルドするには、Arm64EC としてのビルドをサポートする任意のバージョンの CMake を使用できます。 このプロセスでは、Arm64 をターゲットとするプロジェクトを最初にビルドして、Arm64 リンカー入力を生成する必要があります。 その後、Arm64EC をターゲットとするプロジェクトをもう一度ビルドする必要があります。今回は、Arm64 と Arm64EC の入力を組み合わせて Arm64X バイナリを形成します。 次の手順では、 CMakePresets.jsonの使用を活用します。

  1. Arm64 と Arm64EC を対象とする個別の構成プリセットがあることを確認します。 次に例を示します。

     {
       "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. 上記の Arm64 プリセットと Arm64EC プリセットを継承する 2 つの新しい構成を追加します。 BUILD_AS_ARM64Xを Arm64EC から継承する構成でARM64ECに設定し、もう一方でARM64BUILD_AS_ARM64Xします。 これらの変数は、これら 2 つのプリセットからのビルドが 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. arm64x.cmakeという名前の新しい .cmake ファイルを CMake プロジェクトに追加します。 次のスニペットを新しい .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 は、Visual Studio 17.11 以降の MSVC リンカーを使用してビルドする場合にのみサポートされます。

古いリンカーを使用する必要がある場合は、代わりに次のスニペットをコピーします。 このルートでは、古いフラグ /LINK_REPROが使用されます。 /LINK_REPRO ルートを使用すると、ファイルのコピーが原因で全体的なビルド時間が遅くなり、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. プロジェクトの最上位 CMakeLists.txt ファイルの下部に、次のスニペットを追加します。 山かっこの内容を実際の値に置き換えます。 これにより、先ほど作成した arm64x.cmake ファイルが使用されます。

     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. Arm64X 対応 Arm64 プリセット (arm64-debug-x) を使用して CMake プロジェクトをビルドします。

  3. Arm64X 対応 Arm64EC プリセット (arm64ec-debug-x) を使用して CMake プロジェクトをビルドします。 このビルドの出力ディレクトリに含まれる最終的な dll は、Arm64X バイナリになります。

Arm64X 純粋フォワーダー DLL の構築

Arm64X 純粋フォワーダー DLL は、種類に応じて API を個別の DLL に転送する小さな Arm64X DLL です。

  • Arm64 API は、Arm64 DLL に転送されます。

  • x64 API は x64 または Arm64EC DLL に転送されます。

Arm64X 純粋フォワーダーは、すべての Arm64EC と Arm64 コードを含むマージされた Arm64X バイナリの構築に関する課題がある場合でも、Arm64X バイナリを有効活用できます。 Arm64X 純粋フォワーダー DLL の詳細については、「Arm64X PE ファイル」の概要ページを参照してください。

次の手順に従って、Arm64 開発者コマンド プロンプトから Arm64X 純粋フォワーダーをビルドします。 結果として得られる Arm64X 純粋フォワーダーは x64 コールを foo_x64.DLL に呼び出し、Arm64 を foo_arm64.DLL に呼び出します。

  1. リンカーが後で純粋フォワーダーを作成するために使用する空の OBJ ファイルを作成します。 純粋フォワーダーにはコードがないため、これらは空です。 これを行うには、空のファイルを作成します。 次の例では、empty.cpp という名前をファイルに付けます。 空の OBJ ファイルは、cl と Arm64 (empty_arm64.obj) 用と Arm64EC (empty_x64.obj) 用の 1 つを使用して作成されます。

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

    「cl : Command line warning D9002 : ignoring unknown option '-arm64EC'」というエラー メッセージが表示された場合、正しくないコンパイラが使用されています。 これを解決するには、arm64 開発者コマンド プロンプトに切り替えてください。

  2. x64 と Arm64 に対して DEF ファイルを作成します。 これらのファイルは、DLL のすべての API エクスポートを列挙し、これらの API 呼び出しを満たすことができる DLL の名前をローダーに示します。

    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. その後、link を使用して、x64 と Arm64 の両方に対して LIB インポート ファイルを作成します。

    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. 空の OBJ をリンクして、フラグ /MACHINE:ARM64X を使用して LIB ファイルをインポートして、Arm6X 純粋フォワーダー DLL を生成します。

    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
    

結果の foo.dll は、Arm64 または x64/Arm64EC プロセスのいずれかで読み込まれます。 Arm64 プロセスが foo.dll を読み込むと、オペレーティング システムは所定の場所で foo_arm64.dll を即時に読み込み、foo_arm64.dll が任意の API コールに応答します。