Membangun biner Arm64X

Anda dapat membangun biner Arm64X, juga dikenal sebagai file Arm64X PE, untuk mendukung pemuatan biner tunggal ke dalam proses x64/Arm64EC dan Arm64.

Membangun biner Arm64X dari proyek Visual Studio

Untuk mengaktifkan pembuatan biner Arm64X, halaman properti konfigurasi Arm64EC memiliki properti "Build Project as ARM64X" baru, yang dikenal sebagai BuildAsX dalam file proyek.

Halaman properti untuk konfigurasi Arm64EC memperlihatkan opsi Build Project sebagai ARM64X

Saat pengguna membangun proyek, Visual Studio biasanya akan mengkompilasi untuk Arm64EC dan kemudian menautkan output ke dalam biner Arm64EC. Ketika BuildAsX diatur ke true, Visual Studio akan dikompilasi untuk Arm64EC dan Arm64. Langkah tautan Arm64EC kemudian digunakan untuk menautkan keduanya ke dalam satu biner Arm64X. Direktori output untuk biner Arm64X ini akan menjadi apa pun direktori output yang diatur ke di bawah konfigurasi Arm64EC.

Agar BuildAsX berfungsi dengan benar, pengguna harus memiliki konfigurasi Arm64 yang ada, selain konfigurasi Arm64EC. Konfigurasi Arm64 dan Arm64EC harus memiliki runtime C dan pustaka standar C++ yang sama (misalnya, keduanya diatur /MT). Untuk menghindari inefisiensi build, seperti membangun proyek Arm64 penuh daripada hanya kompilasi, semua referensi langsung dan tidak langsung proyek harus BuildAsX diatur ke true.

Sistem build mengasumsikan bahwa konfigurasi Arm64 dan Arm64EC memiliki nama yang sama. Jika konfigurasi Arm64 dan Arm64EC memiliki nama yang berbeda (seperti Debug|ARM64 dan MyDebug|ARM64EC), Anda dapat mengedit vcxproj atau Directory.Build.props file secara manual untuk menambahkan ARM64ConfigurationNameForX properti ke konfigurasi Arm64EC yang menyediakan nama konfigurasi Arm64.

Jika biner Arm64X yang diinginkan adalah kombinasi dari dua proyek terpisah, satu sebagai Arm64 dan satu sebagai Arm64EC, Anda dapat mengedit vxcproj proyek Arm64EC secara manual untuk menambahkan ARM64ProjectForX properti dan menentukan jalur ke proyek Arm64. Kedua proyek harus dalam solusi yang sama.

Membangun DLL Arm64X dengan CMake

Untuk membangun biner proyek CMake Anda sebagai Arm64X, Anda dapat menggunakan versi CMake apa pun yang mendukung pembangunan sebagai Arm64EC. Proses ini awalnya melibatkan pembangunan proyek yang menargetkan Arm64 untuk menghasilkan input linker Arm64. Selanjutnya, proyek harus dibangun lagi yang menargetkan Arm64EC, kali ini menggabungkan input Arm64 dan Arm64EC untuk membentuk biner Arm64X. Langkah-langkah di bawah ini memanfaatkan penggunaan CMakePresets.json.

  1. Pastikan Anda memiliki prasetel konfigurasi terpisah yang menargetkan Arm64 dan Arm64EC. Contohnya:

     {
       "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. Tambahkan dua konfigurasi baru yang mewarisi dari preset Arm64 dan Arm64EC yang Anda miliki di atas. Atur BUILD_AS_ARM64X ke ARM64EC dalam konfigurasi yang mewarisi dari Arm64EC dan BUILD_AS_ARM64X ke ARM64 di konfigurasi lainnya. Variabel ini akan digunakan untuk menandakan bahwa build dari kedua preset ini adalah bagian dari 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. Tambahkan file .cmake baru ke proyek CMake Anda yang disebut arm64x.cmake. Salin cuplikan di bawah ini ke dalam file .cmake baru.

     # 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 hanya didukung jika Anda membangun menggunakan linker MSVC dari Visual Studio 17.11 atau yang lebih baru.

Jika Anda perlu menggunakan linker yang lebih lama, salin cuplikan di bawah ini sebagai gantinya. Rute ini menggunakan bendera /LINK_REPRO yang lebih lama. Menggunakan rute /LINK_REPRO akan menghasilkan waktu build keseluruhan yang lebih lambat karena penyalinan file dan memiliki masalah yang diketahui saat menggunakan generator 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. Di bagian bawah file tingkat CMakeLists.txt atas dalam proyek Anda, tambahkan cuplikan di bawah ini. Pastikan untuk mengganti konten tanda kurung sudut dengan nilai aktual. Ini akan menggunakan file yang arm64x.cmake baru saja Anda buat di atas.

     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. Bangun proyek CMake Anda menggunakan preset Arm64X dengan dukungan Arm64 (arm64-debug-x).

  3. Bangun proyek CMake Anda menggunakan preset Arm64X yang diaktifkan Arm64EC (arm64ec-debug-x). Dll akhir yang terkandung dalam direktori output untuk build ini akan menjadi biner Arm64X.

Membangun DLL penerus murni Arm64X

DLL penerus murni Arm64X adalah DLL Arm64X kecil yang meneruskan API ke DLL terpisah tergantung pada jenisnya:

  • API Arm64 diteruskan ke DLL Arm64.

  • API x64 diteruskan ke DLL x64 atau Arm64EC.

Penerus murni Arm64X memungkinkan keuntungan menggunakan biner Arm64X bahkan jika ada tantangan dengan membangun biner Arm64X gabungan yang berisi semua kode Arm64EC dan Arm64. Pelajari selengkapnya tentang DLL penerusan murni Arm64X di halaman gambaran umum file Arm64X PE.

Anda dapat membangun penerus murni Arm64X dari prompt perintah pengembang Arm64 dengan mengikuti langkah-langkah di bawah ini. Penerus murni Arm64X yang dihasilkan akan merutekan panggilan x64 ke foo_x64.DLL dan panggilan Arm64 ke foo_arm64.DLL.

  1. Buat file kosong OBJ yang nantinya akan digunakan oleh linker untuk membuat penerus murni. Ini kosong karena penerus murni tidak memiliki kode di dalamnya. Untuk melakukan ini, buat file kosong. Untuk contoh di bawah ini, kami menamai file empty.cpp. File kosong OBJ kemudian dibuat menggunakan cl, dengan satu untuk Arm64 (empty_arm64.obj) dan satu untuk Arm64EC (empty_x64.obj):

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

    Jika pesan kesalahan "cl : Peringatan baris perintah D9002 : mengabaikan opsi yang tidak diketahui '-arm64EC'" muncul, pengkompilasi yang salah sedang digunakan. Untuk mengatasinya, silakan beralih ke Prompt Perintah Pengembang Arm64.

  2. Buat DEF file untuk x64 dan Arm64. File-file ini menghitung semua ekspor API DLL dan mengarahkan loader ke nama DLL yang dapat memenuhi panggilan API tersebut.

    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. Anda kemudian dapat menggunakan link untuk membuat LIB file impor untuk x64 dan 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. Tautkan file kosong OBJ dan impor LIB menggunakan bendera /MACHINE:ARM64X untuk menghasilkan DLL penerus murni Arm6X:

    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
    

Hasilnya foo.dll dapat dimuat ke dalam proses Arm64 atau x64/Arm64EC. Ketika proses Arm64 dimuat foo.dll, sistem operasi akan segera dimuat foo_arm64.dll di tempatnya dan panggilan API apa pun akan ditangani oleh foo_arm64.dll.