訓練
建置 Arm64X 二進位檔
您可以建置 Arm64X 二進位檔,也稱為 Arm64X PE 檔案,以支援將單一的二進位檔載入 x64/Arm64EC 和 Arm64 處理序。
若要啟用建置 Arm64X 二進位檔,Arm64EC 組態的屬性頁有新的「建置專案為 ARM64X」屬性,即專案檔中所謂的 BuildAsX
。
當使用者建置專案時,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|ARM64
和 MyDebug|ARM64EC
),您可以手動編輯 vcxproj 或 Directory.Build.props
檔案,將 ARM64ConfigurationNameForX
屬性新增至提供 Arm64 組態名稱的 Arm64EC 組態。
如果所需的 Arm64X 二進位檔是兩個不同的專案組合,一個做為 Arm64,另一個做為 Arm64EC,您可以手動編輯 Arm64EC 專案的 vxcproj,以新增 ARM64ProjectForX
屬性並指定 Arm64 專案的路徑。 這兩個專案必須位於相同的解決方案中。
若要將 CMake 專案二進位檔建置為 Arm64X,您可以使用任何支援建置為 Arm64EC 的 CMake 版本。 此程式牽涉到一開始建置以 Arm64 為目標的專案,以產生 Arm64 連結器輸入。 接著,應該再次建置以Arm64EC為目標的專案,這次結合Arm64和Arm64EC輸入來形成Arm64X二進制檔。 下列步驟會利用CMakePresets.json的使用。
請確定您有以 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" } } ] }
新增兩個新組態,這些組態繼承自您上述的 Arm64 和 Arm64EC 預設值。
ARM64EC
在繼承自 Arm64EC 的組態中,將設定BUILD_AS_ARM64X
為 ,ARM64
另BUILD_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" }
將新的 .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()
在專案中最上層
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()
使用已啟用 Arm64X 的 Arm64 預設 (arm64-debug-x) 建置您的 CMake 專案。
使用已啟用 Arm64X 的 Arm64EC 預設來建置 CMake 專案(arm64ec-debug-x)。 此組建的輸出目錄中所包含的最終 dll(s) 將會是 Arm64X 二進位檔。
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
。
建立稍後會由連結器用來建立純轉寄站的空
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 開發人員命令提示字元。
建立
DEF
x64 和 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
然後使用
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
連結空白
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 呼叫。