建置 Arm64X 二進位檔

您可以建置 Arm64X 二進位檔,也稱為 Arm64X PE 檔案,以支援將單一的二進位檔載入 x64/Arm64EC 和 Arm64 處理序。

從 Visual Studio 專案建置 Arm64X 二進位檔

若要啟用建置 Arm64X 二進位檔,Arm64EC 組態的屬性頁有新的「建置專案為 ARM64X」屬性,即專案檔中所謂的 BuildAsX

顯示 [建置專案為 ARM64X] 選項的 Arm64EC 組態屬性頁

當使用者建置專案時,Visual Studio 通常會針對 Arm64EC 進行編譯,然後將輸出連結至 Arm64EC 二進位檔。 當 BuildAsX 設定為 true 時,Visual Studio 會改為針對 Arm64EC Arm64 進行編譯。 接著會使用 Arm64EC 連結步驟,將兩者連結至單一 Arm64X 二進位檔。 此 Arm64X 二進位檔的輸出目錄將會是 Arm64EC 組態下設定的任何輸出目錄。

若要 BuildAsX 正確運作,除了 Arm64EC 組態外,使用者還必須具有現有的 Arm64 組態。 Arm64 和 Arm64EC 組態必須具有相同的 C 執行階段和 C++ 標準程式庫 (例如,兩者皆設定 /MT)。 為避免建置效率不佳,例如建置完整的 Arm64 專案,而不只是編譯,專案的所有直接或間接參考都應該將 BuildAsX 設定為 true。

建置系統假設 Arm64 和 Arm64EC 組態具有相同的名稱。 如果 Arm64 和 Arm64EC 組態有不同的名稱 (例如 Debug|ARM64MyDebug|ARM64EC),您可以手動編輯 vcxprojDirectory.Build.props 檔案,將 ARM64ConfigurationNameForX 屬性新增至提供 Arm64 組態名稱的 Arm64EC 組態。

如果所需的 Arm64X 二進位檔是兩個不同的專案組合,一個做為 Arm64,另一個做為 Arm64EC,您可以手動編輯 Arm64EC 專案的 vxcproj,以新增 ARM64ProjectForX 屬性並指定 Arm64 專案的路徑。 這兩個專案必須位於相同的解決方案中。

使用 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 預設值。 ARM64EC在繼承自 Arm64EC 的組態中,將設定BUILD_AS_ARM64X為 ,ARM64BUILD_AS_ARM64X一個設定為 。 這些變數將用來表示來自這兩個預設的組建是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. 將新的 .cmake 檔案新增至名為 arm64x.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()
    

只有在您使用 Visual Studio 17.11 或更新版本的 MSVC 連結器建置時,才支援 /LINKREPROFULLPATHRSP

如果您需要使用較舊的連結器,請改為複製下列代碼段。 此路由會使用舊版旗標 /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 預設來建置 CMake 專案(arm64ec-debug-x)。 此組建的輸出目錄中所包含的最終 dll(s) 將會是 Arm64X 二進位檔。

建置 Arm64X 純轉寄站 DLL

Arm64X 純轉寄站 DLL 是小型 Arm64X DLL,會根據以下類型將 API 轉送至個別 DLL:

  • Arm64 API 會轉送至 Arm64 DLL。

  • x64 API 會轉送至 x64 或 Arm64EC DLL。

即使建置合併的 Arm64X 二進位檔包含所有 Arm64EC 和 Arm64 程式碼有其難度,Arm64X 純轉寄站仍能利用到 Arm64X 二進位檔的優點。 在 Arm64X PE 檔案概觀頁面中,深入了解 Arm64X 純轉寄站 DLL。

您可以遵循下列步驟,從 Arm64 開發人員命令提示字元建置 Arm64X 純轉寄站。 產生的 Arm64X 純轉寄站會將 x64 呼叫路由傳送至 foo_x64.DLL,並將 Arm64 呼叫路由至 foo_arm64.DLL

  1. 建立稍後會由連結器用來建立純轉寄站的空 OBJ 檔案。 這些是空的,因為純轉寄站中沒有程式碼。 若要這樣做,請建立空檔案。 在下列範例中,我們將檔案命名為 empty.cpp。 接著會使用 cl 建立空的 OBJ 檔案,其中一個用於 Arm64 (empty_arm64.obj) 和一個用於 Arm64EC (empty_x64.obj):

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

    如果出現錯誤訊息「cl : 命令列警告 D9002:忽略未知選項 '-arm64EC'」,則會使用不正確的編譯程式。 若要解決此問題,請切換至 Arm64 開發人員命令提示字元

  2. 建立 DEFx64 和 Arm64 的檔案。 這些檔案會列舉 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 呼叫。