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


Сборка двоичных файлов Arm64X

Двоичные файлы Arm64X, также известные как ФАЙЛЫ PE Arm64X, поддерживают загрузку одного двоичного файла в процессы x64/Arm64EC и Arm64EC.

Создание двоичного файла 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, которые вы создали выше. Задайте значение 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-файл в проект 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 — это небольшая библиотека DLL Arm64X, которая перенаправит API в отдельные библиотеки DLL в зависимости от их типа:

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

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

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

Вы можете создать чистый сервер пересылки Arm64X из командной строки разработчика Arm64, выполнив указанные ниже действия. Результирующий сервер пересылки Arm64X будет направлять вызовы x64 в foo_x64.DLL вызовы x64 и Arm64.foo_arm64.DLL

  1. Создайте пустые OBJ файлы, которые будут использоваться компоновщиком для создания чистого сервера пересылки. Они пусты, так как чистый сервер пересылки не имеет в нем кода. Для этого создайте пустой файл. В приведенном ниже примере мы назвали файл empty.cpp. Затем пустые OBJ файлы создаются с помощью clодного для 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 для создания библиотеки DLL чистого пересылки 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
    

Полученный foo.dll результат можно загрузить в процесс Arm64 или x64/Arm64EC. При загрузке foo.dllпроцесса Arm64 операционная система немедленно загружается foo_arm64.dll на его место, и все вызовы API будут обрабатываться foo_arm64.dll.