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.

Criando 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 um usuário cria um projeto, o Visual Studio normalmente compila para Arm64EC e, em seguida, vincula as saídas em um binário Arm64EC. Quando BuildAsX estiver definido como true, o Visual Studio compilará para Arm64EC e Arm64. A etapa de link Arm64EC é então usada para vincular ambos em um único binário Arm64X. O diretório de saída para este binário Arm64X será qualquer que seja o diretório de saída definido sob a configuração Arm64EC.

Para BuildAsX funcionar corretamente, o usuário deve ter uma configuração Arm64 existente, além da configuração Arm64EC. As configurações Arm64 e Arm64EC devem ter o mesmo tempo de execução C e biblioteca padrão C++ (por exemplo, ambos definidos /MT). Para evitar ineficiências de construção, como a construção de projetos Arm64 completos em vez de apenas compilação, todas as referências diretas e indiretas do projeto devem ter BuildAsX sido definidas como verdadeiras.

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 vcxproj ou Directory.Build.props arquivo para adicionar uma ARM64ConfigurationNameForX propriedade à configuração Arm64EC que forneça o nome da configuração Arm64.

Se o binário Arm64X desejado for uma combinação de 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 os binários do projeto do CMake como Arm64X, você pode usar qualquer versão do CMake que dê suporte à compilação como Arm64EC. O processo envolve a criação inicial do projeto direcionado ao Arm64 para gerar as entradas do vinculador Arm64. Posteriormente, o projeto deve ser construído novamente visando Arm64EC, desta vez combinando as entradas Arm64 e Arm64EC para formar binários Arm64X. As etapas abaixo aproveitam o uso de 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 as predefinições Arm64 e Arm64EC que você tem acima. Defina BUILD_AS_ARM64X como ARM64EC na configuração que herda de Arm64EC e BUILD_AS_ARM64X como ARM64 na outra. Essas variáveis serão usadas para indicar que as compilações 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 abaixo para o 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ê estiver criando usando o vinculador MSVC do Visual Studio 17.11 ou posterior.

Se você precisar usar um vinculador mais antigo, copie o snippet abaixo. Essa rota usa um sinalizador /LINK_REPRO mais antigo. Usar a rota /LINK_REPRO resultará em um tempo de compilação 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 do seu projeto, adicione o snippet abaixo. Certifique-se de substituir o conteúdo dos colchetes angulares por valores reais. Isso consumirá o arm64x.cmake arquivo que você acabou de criar acima.

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

  3. Crie seu projeto CMake usando a predefinição Arm64EC habilitada para Arm64X (arm64ec-debug-x). As dlls finais contidas no diretório de saída para este build serão binários Arm64X.

Criando uma DLL de encaminhador puro Arm64X

Uma DLL de encaminhador puro Arm64X é uma pequena DLL Arm64X que encaminha APIs para separar DLLs dependendo de seu tipo:

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

  • APIs x64 são encaminhadas 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. Saiba mais sobre as DLLs de encaminhador puro Arm64X na página de visão geral dos arquivos Arm64X PE.

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

  1. Crie arquivos vazios OBJ que serão usados posteriormente pelo vinculador para criar o encaminhador puro. Eles estão vazios, pois o encaminhador puro não tem código. Para fazer isso, crie um arquivo vazio. Para o exemplo abaixo, nomeamos o arquivo vazio.cpp. Arquivos vazios OBJ são então criados usando cl, com um para Arm64 () e um para Arm64EC (empty_arm64.objempty_x64.obj):

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

    Se a mensagem de erro "cl : aviso de linha de comando D9002 : ignorando a opção desconhecida '-arm64EC'" for exibida, o compilador incorreto está sendo usado. Para resolver isso, mude para o Prompt de Comando do Desenvolvedor Arm64.

  2. Crie DEF arquivos para x64 e Arm64. Esses arquivos enumeram todas as exportações de API da DLL e aponta 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. Em seguida, você pode usar 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 para produzir a DLL do encaminhador /MACHINE:ARM64X 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 é foo.dllcarregado, o sistema operacional será carregado foo_arm64.dll imediatamente em seu lugar e quaisquer chamadas de API serão tratadas pelo foo_arm64.dll.