Поделиться через


Сборка бинарников Arm64X

Вы можете создавать двоичные файлы Arm64X, также известные как файлы PE Arm64X, чтобы поддерживать загрузку одного двоичного файла в процессы x64/Arm64EC и Arm64.

Создание двоичного файла Arm64X из проекта Visual Studio

Чтобы включить сборку двоичных файлов Arm64X, страницы свойств конфигурации Arm64EC имеют новое свойство Build Project as ARM64X, известное как BuildAsX в файле проекта.

Страница свойств для конфигурации Arm64EC с параметром Build Project as ARM64X

При сборке проекта Visual Studio обычно компилируется для Arm64EC, а затем связывает выходные данные с двоичным файлом Arm64EC. При установке BuildAsX в true Visual Studio компилирует код как для Arm64EC, так и для Arm64. Этап связывания Arm64EC соединяет оба выходных файла в один двоичный файл Arm64X. Выходной каталог для этого двоичного файла Arm64X — это выходной каталог, заданный в конфигурации Arm64EC.

Для BuildAsX правильной работы необходимо иметь существующую конфигурацию Arm64 в дополнение к конфигурации Arm64EC. Конфигурации Arm64 и Arm64EC должны использовать одну и ту же среду выполнения C и стандартную библиотеку C++ (например, значение /MT). Чтобы избежать неэффективности сборки, например создания полных проектов Arm64, а не просто компиляции, задайте BuildAsX значение true для всех прямых и косвенных ссылок на проект.

В системе сборки предполагается, что конфигурации Arm64 и Arm64EC имеют то же имя. Если конфигурации Arm64 и Arm64EC имеют разные имена (например Debug|ARM64 , и MyDebug|ARM64EC), можно вручную изменить vcxproj или Directory.Build.props файл, чтобы добавить ARM64ConfigurationNameForX свойство в конфигурацию Arm64EC, которая предоставляет имя конфигурации Arm64.

Если вы хотите, чтобы двоичный файл Arm64X сочетал два отдельных проекта, один как Arm64 и один как Arm64EC, можно вручную изменить vxcproj проекта Arm64EC, чтобы добавить ARM64ProjectForX свойство и указать путь к проекту Arm64. Два проекта должны находиться в одном решении.

Создание библиотеки DLL Arm64X с помощью CMake

Чтобы собрать двоичные файлы проекта CMake в виде Arm64X, используйте любую версию CMake, которая поддерживает сборку в виде Arm64EC. Сначала создайте проект, предназначенный для 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, созданных на предыдущем шаге. Задайте значение BUILD_AS_ARM64XARM64EC в конфигурации, наследуемой от Arm64EC и BUILD_AS_ARM64XARM64 в другой. Эти переменные свидетельствуют о том, что сборки из этих двух предустановок являются частью 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-файл в проект CMake с именем arm64x.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 поддерживается только при сборке с помощью компоновщика MSVC из Visual Studio 17.11 или более поздней версии.

Если вам нужно использовать старый линкер, скопируйте следующий фрагмент кода. Этот маршрут использует старый флаг /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. Создайте проект CMake, используя предустановку Arm64X с включенной поддержкой Arm64 (arm64-debug-x).

  3. Создайте проект CMake с помощью предустановки Arm64X с поддержкой Arm64EC (arm64ec-debug-x). Окончательные библиотеки DLL в выходном каталоге для этой сборки — двоичные файлы Arm64X.

Создание чисто пересылающей библиотеки DLL для Arm64X

Чистая DLL переадресатора Arm64X — это небольшая Arm64X DLL, которая перенаправляет API в отдельные DLL в зависимости от их типа.

  • API Arm64 перенаправляются в библиотеку DLL Arm64.

  • API x64 перенаправляются в библиотеку DLL x64 или Arm64EC.

Чистое средство пересылки Arm64X позволяет использовать двоичный файл Arm64X даже при возникновении проблем с созданием объединенного двоичного файла Arm64X, содержащего весь код Arm64EC и Arm64. Дополнительные сведения см. в статье Arm64X PE files.

Вы можете создать чистый сервер пересылки Arm64X из командной строки разработчика Arm64, выполнив указанные ниже действия. Полученный сервер пересылки 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. Создайте DEF файлы для x64 и Arm64. Эти файлы перечисляют все экспорты API библиотеки DLL и указывают загрузчику имя библиотеки DLL, которая может выполнять эти вызовы API.

    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. Свяжите пустые OBJ файлы и импортируйте LIB файлы с помощью флага /MACHINE:ARM64X для создания пересылающей 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_arm64.dll вместо него, и все вызовы API обрабатываются foo_arm64.dll.