共用方式為


建置 Arm64X 二進位檔

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

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

若要啟用建置 Arm64X 二進位檔,在 Arm64EC 組態的屬性頁中新增了一個「將專案建置為 ARM64X」的屬性,這在專案檔案中稱為 BuildAsX

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

當您建置專案時,Visual Studio 通常會針對 Arm64EC 進行編譯,然後將輸出連結至 Arm64EC 二進位檔。 當您設定 BuildAsXtrue時,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 的設定中,將BUILD_AS_ARM64X設為ARM64EC,在另一個設定中,將BUILD_AS_ARM64X設為ARM64。 這些變數表示這兩個預設集的組建是 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 預設集 (arm64ec-debug-x) 來建置 CMake 專案。 此組建輸出目錄中的最終 DLL 是 Arm64X 二進位檔。

建置 Arm64X 純正轉發器 DLL

Arm64X 純轉送 DLL 是小型 Arm64X DLL,依據其類型將 API 轉送至個別 DLL。

  • Arm64 API 會轉送至 Arm64 DLL。

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

即使建置包含所有 Arm64EC 和 Arm64 程式碼之合併 Arm64X 二進位檔具有挑戰,一個 Arm64X 純轉寄器仍能享受使用 Arm64X 二進位檔的優勢。 如需詳細資訊,請參閱 Arm64X PE 檔案

您可以遵循下列步驟,從 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. 建立針對 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 來建立 LIB x64 和 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
    
  4. 使用旗標 /MACHINE:ARM64X 將空白 OBJ 和匯入 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 其位置,而任何 API 呼叫都會由 foo_arm64.dll處理。