Compartilhar via


Criar binários do Arm64X

Você pode criar binários Arm64X, também conhecidos como arquivos Arm64X PE, para suportar o carregamento de um único binário em processos x64/Arm64EC e Arm64.

Criar um binário Arm64X a partir de um projeto do Visual Studio

Para habilitar a criação de binários Arm64X, as páginas de propriedades da configuração Arm64EC têm uma nova propriedade "Build Project as ARM64X", conhecida como BuildAsX no arquivo de projeto.

Página de propriedades para uma configuração Arm64EC mostrando a opção Construir Projeto como ARM64X

Quando você cria um projeto, o Visual Studio normalmente compila para Arm64EC e, em seguida, vincula as saídas a um binário Arm64EC. Quando você define BuildAsX como true, o Visual Studio compila para Arm64EC e Arm64. A etapa de link Arm64EC vincula ambas as saídas a um único binário Arm64X. O diretório de saída desse binário Arm64X é o diretório de saída definido sob a configuração arm64EC.

Para BuildAsX trabalhar corretamente, você deve ter uma configuração existente do Arm64, além da configuração arm64EC. As configurações arm64 e arm64EC devem usar o mesmo runtime C e biblioteca padrão C++ (por exemplo, ambas definidas como /MT). Para evitar ineficiências de construção, como a construção de projetos completos em Arm64 ao invés de apenas compilação, defina BuildAsX como "true" para todas as referências diretas e indiretas do projeto.

O sistema de compilação assume que as configurações Arm64 e Arm64EC têm o mesmo nome. Se as configurações Arm64 e Arm64EC tiverem nomes diferentes (como Debug|ARM64 e MyDebug|ARM64EC), você poderá editar manualmente o vcxprojnome da configuração Arm64.

Se você quiser que o binário Arm64X combine dois projetos separados, um como Arm64 e outro como Arm64EC, você poderá editar manualmente o vxcproj do projeto Arm64EC para adicionar uma ARM64ProjectForX propriedade e especificar o caminho para o projeto Arm64. Os dois projetos devem estar na mesma solução.

Criando uma DLL Arm64X com o CMake

Para criar binários de projeto do CMake como Arm64X, use qualquer versão do CMake que dê suporte à criação como Arm64EC. Primeiro, crie o projeto direcionado ao Arm64 para gerar as entradas do vinculador Arm64. Em seguida, crie o projeto novamente visando o Arm64EC, combinando as entradas Arm64 e Arm64EC para formar binários Arm64X. As etapas a seguir mostram como usar CMakePresets.json.

  1. Certifique-se de ter predefinições de configuração separadas direcionadas a Arm64 e Arm64EC. Por exemplo:

     {
       "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. Adicione duas novas configurações que herdam das predefinições Arm64 e Arm64EC criadas na etapa anterior. Defina BUILD_AS_ARM64X como ARM64EC na configuração que herda de Arm64EC e BUILD_AS_ARM64X como ARM64 na outra. Essas variáveis significam que os builds dessas duas predefinições fazem parte do 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. Adicione um novo arquivo .cmake ao seu projeto CMake chamado arm64x.cmake. Copie o snippet a seguir no novo arquivo .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 só terá suporte se você criar usando o vinculador MSVC do Visual Studio 17.11 ou posterior.

Se você precisar usar um vinculador mais antigo, copie o snippet a seguir. Essa rota usa um sinalizador /LINK_REPRO mais antigo. O uso da rota /LINK_REPRO resulta em um tempo de build geral mais lento devido à cópia de arquivos e tem problemas conhecidos ao usar o gerador 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. Na parte inferior do arquivo de nível CMakeLists.txt superior em seu projeto, adicione o snippet a seguir. Certifique-se de substituir o conteúdo dos colchetes angulares por valores reais. Esta etapa consome o arm64x.cmake arquivo que você acabou de criar.

     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. Crie seu projeto do CMake usando a predefinição Arm64X habilitada para Arm644 (arm64-debug-x).

  3. Crie seu projeto do CMake usando a predefinição Arm64X compatível com Arm64EC (arm64ec-debug-x). As DLLs finais no diretório de saída para esse build são binários Arm64X.

Criando uma DLL de encaminhador puro Arm64X

Uma DLL de encaminhamento puro Arm64X é uma pequena DLL Arm64X que encaminha APIs para outras DLLs, dependendo do tipo delas:

  • As APIs do Arm64 são encaminhadas para uma DLL do Arm64.

  • APIs x64 são redirecionadas para uma DLL x64 ou Arm64EC.

Um encaminhador puro Arm64X permite as vantagens de usar um binário Arm64X, mesmo que haja desafios com a construção de um binário Arm64X mesclado contendo todo o código Arm64EC e Arm64. Para obter mais informações, consulte arquivos ARM64X PE.

Você pode criar um encaminhador puro Arm64X no prompt de comando do desenvolvedor do Arm64 seguindo as etapas abaixo. O encaminhador puro Arm64X resultante roteia chamadas x64 para foo_x64.DLL e chamadas Arm64 para foo_arm64.DLL.

  1. Crie arquivos vazios OBJ que o vinculador usa para criar o encaminhador puro. Esses arquivos estão vazios porque o encaminhador puro não contém código. Para criar esses arquivos, crie um arquivo vazio. No exemplo a seguir, o arquivo é nomeado empty.cpp. Use cl para criar arquivos vazios OBJ , com um para Arm64 (empty_arm64.obj) e outro para Arm64EC (empty_x64.obj):

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

    Se você vir a mensagem de erro "cl: aviso de linha de comando D9002: ignorando a opção desconhecida '-arm64EC'", você está usando o compilador errado. Para corrigir esse problema, alterne para o Prompt de Comando do Desenvolvedor do Arm64.

  2. Crie arquivos DEF para x64 e Arm64. Esses arquivos listam todas as exportações de API da DLL e apontam o carregador para o nome da DLL que pode atender a essas chamadas de 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. Use link para criar LIB arquivos de importação para x64 e 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. Vincule os arquivos vazios OBJ e de importação LIB usando o sinalizador /MACHINE:ARM64X para produzir a DLL do encaminhador puro 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
    

O resultado foo.dll pode ser carregado em um processo Arm64 ou x64/Arm64EC. Quando um processo Arm64 carrega foo.dll, o sistema operacional imediatamente carrega foo_arm64.dll em seu lugar, e todas as chamadas de API são tratadas por foo_arm64.dll.