다음을 통해 공유


Arm64X 바이너리 빌드

Arm64X PE 파일이라고도 하는 Arm64X 이진 파일을 빌드하여 x64/Arm64EC 및 Arm64 프로세스 모두에 단일 이진 파일을 로드할 수 있습니다.

Visual Studio 프로젝트에서의 Arm64X 이진 빌드

Arm64X 이진 파일을 빌드할 수 있도록 Arm64EC 구성의 속성 페이지에는 프로젝트 파일에서와 같이 BuildAsX로 알려진 새로운 'ARM64X 프로젝트 빌드' 속성이 있습니다.

빌드 프로젝트를 ARM64X 옵션으로 표시하는 Arm64EC 구성의 속성 페이지

사용자가 프로젝트를 빌드할 때 Visual Studio는 일반적으로 Arm64EC용으로 컴파일한 다음 출력을 Arm64EC 이진 파일에 연결합니다. BuildAsX이(가) true(으)로 설정되면 Visual Studio가 Arm64EC Arm64 둘 다에 대해 컴파일됩니다. 그런 다음 Arm64EC 링크 단계를 사용하여 둘 다 단일 Arm64X 이진 파일에 연결합니다. 이 Arm64X 이진 파일에 대한 출력 디렉터리는 Arm64EC 구성에서 출력 디렉터리가 설정된 모든 디렉터리입니다.

BuildAsX이(가) 올바르게 작동하려면 사용자에게 Arm64EC 구성 외에도 기존 Arm64 구성이 있어야 합니다. Arm64 및 Arm64EC 구성에는 동일한 C 런타임 및 C++ 표준 라이브러리(예: 둘 다 /MT로 설정되어야 함)가 있어야 합니다. 컴파일이 아닌 전체 Arm64 프로젝트 빌드와 같은 빌드 비효율성을 방지하려면 프로젝트의 모든 직접 및 간접 참조에서 BuildAsX을(를) true로 설정해야 합니다.

빌드 시스템에서 Arm64 및 Arm64EC 구성의 이름이 동일하다고 가정합니다. Arm64 및 Arm64EC 구성의 이름이 다른 경우(예: Debug|ARM64MyDebug|ARM64EC) vcxproj 또는 Directory.Build.props파일을 수동으로 편집하여 Arm64 구성의 이름을 제공하는 Arm64EC 구성ARM64ConfigurationNameForX에 속성을 추가할 수 있습니다.

원하는 Arm64X 이진 파일이 Arm64와 Arm64EC와 같은 두 개의 개별 프로젝트의 조합인 경우 Arm64EC 프로젝트의 vxcproj를 수동으로 편집하여 ARM64ProjectForX 속성을 추가하고 Arm64 프로젝트의 경로를 지정할 수 있습니다. 두 프로젝트는 동일한 솔루션에 있어야 합니다.

CMake를 사용하여 Arm64X DLL 빌드

CMake 프로젝트 이진 파일을 Arm64X로 빌드하려면 Arm64EC로 빌드를 지원하는 CMake 버전을 사용할 수 있습니다. 이 프로세스에는 처음에 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 Arm64EC에서 상속되는 구성에서 BUILD_AS_ARM64X 다른 구성으로 ARM64 설정합니다BUILD_AS_ARM64X. 이러한 변수는 이러한 두 사전 설정의 빌드가 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. 라는 arm64x.cmakeCMake 프로젝트에 새 .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 는 Visual Studio 17.11 이상의 MSVC 링커를 사용하여 빌드하는 경우에만 지원됩니다.

이전 링커를 사용해야 하는 경우 아래 코드 조각을 대신 복사합니다. 이 경로는 이전 플래그 /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. Arm64X 사용 Arm64 사전 설정(arm64-debug-x)을 사용하여 CMake 프로젝트를 빌드합니다.

  3. Arm64X 사용 Arm64EC 사전 설정(arm64ec-debug-x)을 사용하여 CMake 프로젝트를 빌드합니다. 이 빌드의 출력 디렉터리에 포함된 최종 dll은 Arm64X 이진 파일입니다.

Arm64X 순수 전달자 DLL 빌드

Arm64X 순수 전달자 DLL은 유형에 따라 API를 개별 DLL로 전달하는 작은 Arm64X DLL입니다.

  • Arm64 API는 Arm64 DLL로 전달됩니다.

  • x64 API는 x64 또는 Arm64EC DLL로 전달됩니다.

Arm64X 순수 전달자를 사용하면 모든 Arm64EC 및 Arm64 코드를 포함하는 병합된 Arm64X 이진 파일을 빌드하는 데 문제가 있는 경우에도 Arm64X 이진 파일을 사용할 수 있습니다. Arm64X PE 파일 개요 페이지에서 Arm64X 순수 전달자 DLL에 대해 자세히 알아봅니다.

아래 단계에 따라 Arm64 개발자 명령 프롬프트에서 Arm64X 순수 전달자를 빌드할 수 있습니다. 결과로, 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. x64 및 Arm64 모두에 대한 DEF 파일을 만듭니다. 이러한 파일은 DLL의 모든 API 내보내기를 나열하고, 로더를 해당 API 호출을 수행할 수 있는 DLL의 이름을 가리킵니다.

    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을(를) 사용하여 x64 및 Arm64 모두에 대한 LIB 가져오기 파일을 만드는 데 사용할 수 있습니다.

    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. /MACHINE:ARM64X 플래그를 사용하여 빈 OBJLIB파일을 연결하고 파일을 가져와 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.dll을(를) 로드하면 운영 체제가 즉시 foo_arm64.dll을(를) 해당 위치에 로드하고 모든 API 호출이 foo_arm64.dll에서 처리됩니다.