Windows on ARM

Dev Guide: Compiling for ARM64/ARM64EC in Visual Studio (Start to Finish)

Introduction
This guide walks you through everything you need to compile, test, and ship native ARM64 and hybrid ARM64EC applications using Visual Studio on Windows 11. It is designed for developers, IT pros, and power users targeting modern Copilot+ PCs and other Windows on ARM devices. You will learn what ARM64 and ARM64EC are, how they relate to the Prism emulation layer, how to set up your toolchain, how to migrate existing projects, how to benchmark performance, and how to troubleshoot common issues.

Why it matters: Native ARM64 binaries deliver the best performance and battery life on Snapdragon-based Copilot+ PCs. ARM64EC lets you mix native ARM64 code with existing x64 components (like plugins) in the same process, making it possible to ship sooner without rewriting everything at once. Done right, your app can feel first-class on Windows on ARM while still supporting x64-only dependencies.

Overview: ARM64 vs ARM64EC and Prism emulation

ARM64 (native)

  • What it is: Aarch64 binaries compiled for Windows on ARM. Runs directly on ARM cores.
  • When to use: The default and best choice for new apps and for dependencies you control.
  • Benefits: Fastest performance, lowest CPU overhead, best battery life and thermals, full access to ARM64-specific optimizations (e.g., NEON/SIMD).
  • Constraints: All in-process native modules must be ARM64. x64-only plugins cannot be loaded by a pure ARM64 process.

ARM64EC (hybrid ABI)

  • What it is: A Microsoft ABI that lets you compile modules that run natively on ARM but can interoperate with x64 code in the same process.
  • When to use: Your app must load x64 components (plugins, middleware, legacy SDKs) you cannot immediately port.
  • Benefits: You can make performance-critical parts native while keeping x64-only parts working. Great bridge for large apps.
  • Constraints: Windows 11 required. Tooling requires specific ARM64EC libraries and linkages. Some ABI boundaries and CRT/linker requirements apply. Slight overhead may occur at x64↔ARM64EC call boundaries.

Prism (x64 emulation in Windows 11)

  • What it is: The next-gen emulation layer that translates x64 binaries to run on ARM64. Included in Windows 11 24H2 and Copilot+ PCs.
  • When to use: For x64 apps or dependencies you haven’t ported yet.
  • Benefits: Significantly faster than previous emulation, markedly better compatibility.
  • Constraints: Still not native. CPU-bound workloads can be noticeably slower than ARM64. Kernel drivers, anti-cheat modules, and low-level system components must be native ARM64.

Choosing the right path

  • New code or dependencies you control: choose ARM64.
  • Mixed environment with must-have x64 plugins: choose ARM64EC for the host and port modules to ARM64EC/ARM64 over time.
  • Proof-of-concept or legacy apps: start under Prism emulation, then incrementally move to ARM64EC/ARM64 for performance.

Compatibility and key facts

Supported development tools and status (high-level snapshot)

Category Tool/App ARM64 Native Works Under Prism (x64) Notes
IDE Visual Studio 2022 Yes Yes Native ARM64 VS recommended (17.4+).
Build system MSBuild Yes Yes Cross-target from x64 host; or build on ARM64.
CMake CMake (VS generator) Yes Yes Use -A ARM64 or -A ARM64EC.
Ninja Ninja Yes Yes ARM64 binaries available.
Compiler MSVC (cl.exe) Yes N/A ARM64 and ARM64EC supported.
Package manager vcpkg Yes Yes Triplets: arm64-windows, arm64ec-windows.
Package manager NuGet Yes Yes Depends on package contents; ensure ARM64 assets.
Source control Git for Windows Yes Yes Native ARM64 installer available.
Runtimes .NET 8/9 Yes Yes Target win-arm64 for best perf; AnyCPU runs natively on ARM64.
Languages Python 3.11+ (Windows ARM64) Yes Yes Prefer ARM64 builds for performance.
Runtime Node.js (Windows ARM64 builds) Yes Yes Prefer ARM64 builds for perf and native addons.
Containers Docker Desktop (Linux containers) Yes N/A Supported via WSL2 on ARM64; Windows containers not supported on ARM.
Subsystem WSL (ARM64) Yes N/A Use ARM64 Linux distros; amd64 images use emulation and are slower.
See also  Creators’ Guide: Video/Audio Tools on Copilot+ PCs—What Runs Today

Known constraints and issues

  • Kernel-mode drivers must be ARM64. x64 drivers will not load on ARM.
  • Some anti-cheat drivers and DRM still require vendor updates for ARM64.
  • Some commercial SDKs ship x64-only binaries; if they run in-process, you need ARM64EC or an out-of-proc model.
  • Visual Studio extensions must support ARM64 to run inside native VS.
  • Windows containers on ARM are not supported; use Linux containers via WSL2.

Requirements and prerequisites

Operating system

  • Windows 11 (ARM64 device). ARM64EC requires Windows 11.
  • For best Prism performance, use Windows 11 version 24H2 or newer (Copilot+ PCs ship with this).
  • Keep firmware and drivers up to date via Windows Update and your OEM support app.

Hardware

  • ARM64 PC (e.g., Copilot+ PC with Snapdragon X Elite/X Plus). You can also cross-compile on an x64 PC.

Visual Studio and SDKs

  • Visual Studio 2022 (17.7 or later recommended; 17.10+ ideal). Install the ARM64-native VS if working on an ARM device.
  • Workloads:
    • Desktop development with C++
    • .NET Desktop development (if building C#/.NET apps)
  • Individual components:
    • MSVC v143 – VS 2022 C++ ARM64 build tools
    • MSVC v143 – VS 2022 C++ ARM64EC build tools
    • C++ CMake tools for Windows
    • Windows 11 SDK (latest)
  • Optional: Windows Terminal, Git, CMake, Ninja, vcpkg.

Cross-compiling (on x64 host)

  • You can build ARM64/ARM64EC targets from an x64 machine using MSVC cross tools (vcvarsall x64_arm64 / x64_arm64ec).
  • Remote testing/debugging: Remote Tools for Visual Studio 2022 (ARM64) on the device.

Dependencies

  • Ensure native libraries you consume are available in ARM64 or ARM64EC. If not, plan ARM64EC hosting or out-of-process IPC.
  • For NuGet/vcpkg packages, choose appropriate ARM64/ARM64EC triplets and RIDs.

Step-by-step: Build ARM64 and ARM64EC in Visual Studio

1) Install workloads and components

  • Launch Visual Studio Installer → Modify your VS 2022 instance.
  • Workloads:
    • Desktop development with C++
    • .NET Desktop development (if needed)
  • Individual components:
    • MSVC v143 – VS 2022 C++ ARM64 build tools (latest)
    • MSVC v143 – VS 2022 C++ ARM64EC build tools (latest)
    • C++ CMake tools for Windows
    • Windows 11 SDK (latest)

Verify toolchains on the command line (Developer PowerShell for VS):

  • For ARM64 native on an ARM PC:
    • Start “ARM64 Native Tools Command Prompt for VS 2022”
  • For cross-compile from x64 host:
    • “x64 Native Tools Command Prompt for VS 2022”
    • Then:
      call “C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat” x64_arm64
      call “C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvarsall.bat” x64_arm64ec

2) Create or retarget a C++ project

Option A: Create new project with ARM64/ARM64EC platforms

  • File → New → Project → e.g., Windows Desktop Application (C++).
  • Once created, open Build → Configuration Manager.
  • Under “Active solution platform”, choose ARM64 or ARM64EC. If missing:
    • Click <New…>, copy settings from x64, set name to ARM64 or ARM64EC, and confirm.

Option B: Retarget an existing solution

  • Right-click solution → Retarget solution for latest Windows SDK (select current).
  • Configuration Manager → add ARM64/ARM64EC platforms for all projects.
  • For static/dynamic libraries, ensure they also target ARM64 or ARM64EC.

Key project properties (right-click project → Properties):

  • General → Platform Toolset: Visual Studio 2022 (v143).
  • General → Target Machine:
    • ARM64: default with ARM64 platform.
    • ARM64EC: use ARM64EC platform or add cl.exe option /arm64EC.
  • C/C++ → Code Generation: choose runtime as needed (MD/MDd preferred for shared CRT).
  • Linker → Advanced → Target Machine: matches platform (MACHINE:ARM64 or ARM64EC).

3) Compile from the command line with MSBuild

  • Build ARM64:
    msbuild MySolution.sln -p:Configuration=Release -p:Platform=ARM64
  • Build ARM64EC:
    msbuild MySolution.sln -p:Configuration=Release -p:Platform=ARM64EC

Single-file example with cl.exe:

  • ARM64:
    cl /nologo /O2 /EHsc hello.cpp /link /MACHINE:ARM64
  • ARM64EC:
    cl /nologo /O2 /EHsc /arm64EC hello.cpp /link /MACHINE:ARM64EC

4) CMake projects targeting ARM64/ARM64EC

  • Visual Studio generator (recommended for MSVC):
    • ARM64:
      cmake -G “Visual Studio 17 2022” -A ARM64 -S . -B build-arm64
      cmake –build build-arm64 –config Release
    • ARM64EC:
      cmake -G “Visual Studio 17 2022” -A ARM64EC -S . -B build-arm64ec
      cmake –build build-arm64ec –config Release
  • Ninja + MSVC cross-environment (from an x64 host):
    • Open “x64 Native Tools” then:
      call “%VSINSTALLDIR%\VC\Auxiliary\Build\vcvarsall.bat” x64_arm64
      cmake -G Ninja -S . -B build-arm64
      cmake –build build-arm64 -j

5) Bring your dependencies with vcpkg

  • Install packages with the correct triplet:
    • ARM64 native:
      vcpkg install zlib:arm64-windows openssl:arm64-windows
    • ARM64EC:
      vcpkg install zlib:arm64ec-windows
  • CMake integration:
    cmake -S . -B build-arm64 ^
    -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake ^
    -DVCPKG_TARGET_TRIPLET=arm64-windows
  • Prefer static vs dynamic linking per your policy (arm64-windows-static, arm64ec-windows-static also available).

6) NuGet packages and architecture-specific assets

  • Many NuGet packages are architecture-agnostic; native packages must provide ARM64 assets.
  • For C# projects, ensure runtime-specific files:
    • Place native DLLs under runtimes/win-arm64/native/ in the NuGet package.
  • In .csproj, you can add conditional content if you’re distributing native assets yourself:

7) Building .NET apps for ARM64

  • AnyCPU apps will run natively on ARM64 JIT. For best startup and perf, publish for ARM64:
    dotnet publish -c Release -r win-arm64 –self-contained true
  • Consider NativeAOT for high-performance workloads (where supported):
    dotnet publish -c Release -r win-arm64 -p:PublishAot=true
See also  How to Check If Your App Has an ARM64/ARM64EC Build (and Find One)

8) Detecting emulation and ARM64EC at runtime

  • Detect whether your process is running under emulation:
    • Use IsWow64Process2 to differentiate x64 emulation vs native ARM64.
    • For ARM64EC detection, prefer an OS-provided API if available, or use IsWow64Process2 and module inspection.

C++ snippet:

include <windows.h>

include

int main() {
USHORT processMachine = 0, nativeMachine = 0;
if (IsWow64Process2(GetCurrentProcess(), &processMachine, &nativeMachine)) {
if (processMachine == IMAGE_FILE_MACHINE_UNKNOWN &&
nativeMachine == IMAGE_FILE_MACHINE_ARM64) {
std::cout << “Native ARM64 process\n”;
} else if (processMachine == IMAGE_FILE_MACHINE_AMD64 &&
nativeMachine == IMAGE_FILE_MACHINE_ARM64) {
std::cout << “x64-on-ARM (emulated) or ARM64EC host\n”;
}
}
return 0;
}

9) Package and distribute for multiple architectures

  • MSIX: create multi-architecture packages (x86, x64, ARM64). Windows selects the best at install time.
  • Traditional installers: ship separate installers or a bootstrapper that detects architecture and fetches the correct payload.
  • Ensure your updater can deliver ARM64 updates independently of x64.

10) Test and debug on device

  • Install Remote Tools for Visual Studio 2022 (ARM64) on the device.
  • In Visual Studio: Debug → Attach to Process → connect to the remote ARM64 machine.
  • Validate:
    • CPU utilization and perf counters
    • Plugin loading (for ARM64EC hosts)
    • Battery/thermal behavior on real workloads
    • WER crash dumps symbolized with ARM64 PDBs

Performance and benchmarks

Key principles and typical expectations

  • CPU-bound code:
    • ARM64 native: best performance (baseline 1.0x).
    • ARM64EC: near-native for ARM64EC-compiled modules; slight overhead at ARM64EC↔x64 transitions.
    • x64 under Prism: typically slower than native ARM64 in compute-heavy loops; often acceptable for many desktop workloads.
  • I/O-bound code: Differences are smaller; emulation overhead may be masked by I/O latency.
  • UI responsiveness: Native ARM64 generally improves responsiveness and power draw; animation smoothness benefits from native GPU drivers and lower CPU wakeups.
  • JIT/AOT:
    • .NET: Prefer win-arm64; NativeAOT provides best cold-start and steady-state CPU for supported scenarios.
    • Node/Python: Use ARM64 builds for better startup and native addon performance.

Example measurement plan (repeatable on your hardware)

  • Microbenchmarks:
    • C++: n-body, image convolution, JSON parse/serialize.
    • .NET: Benchmarks using BenchmarkDotNet targeting win-arm64 vs x64-on-Prism.
  • Real workloads:
    • Build times for your repo (CMake+MSBuild).
    • DB-intensive use (SQLite/LevelDB).
    • Web server throughput (Kestrel/Node) behind localhost wrk.
  • Capture:
    • Use Windows Performance Recorder (WPR) and Windows Performance Analyzer (WPA).
    • Compare CPU utilization, context switches, DPC/ISR time.
    • Log power draw on battery for 15–30 minutes of steady-state use.

Illustrative relative results (directional, vary by app and device)

  • C++ compute kernels: ARM64 native 1.0x, ARM64EC 0.95–1.0x, x64 Prism 0.6–0.85x.
  • Mixed host with x64 plugins: ARM64EC host 0–10% overhead depending on frequency of cross-ABI calls.
  • Build workloads: ARM64 native commonly competitive or better than x64 under Prism; disk and parallelism dominate.

Tips to improve performance

  • Use Profile-Guided Optimization (PGO) on MSVC for ARM64.
  • Enable Link-Time Code Generation (/GL and /LTCG).
  • Optimize hot paths with NEON intrinsics where appropriate.
  • Reduce cross-ABI calls in ARM64EC designs; batch work across boundaries.
  • Prefer ARM64-native dependencies and container images.

Troubleshooting

Common build and runtime issues and fixes

  • LNK1112: “module machine type ‘x64’ conflicts with target machine ‘ARM64’”
    • Cause: Linking an x64 library into an ARM64/ARM64EC target.
    • Fix: Acquire or build the library for ARM64/ARM64EC; verify vcpkg triplet; clean and rebuild; check Additional Library Directories point to arm64 or arm64ec folders.
  • Missing ARM64EC libs during link
    • Cause: Wrong environment or toolset.
    • Fix: Ensure you used the ARM64EC platform or /arm64EC and selected MSVC ARM64EC libraries (vc\lib\arm64ec). Re-run vcvarsall x64_arm64ec.
  • Plugins fail to load on ARM64
    • Cause: x64 plugin loaded by ARM64 process.
    • Fix: Use an ARM64 build of the plugin, switch host to ARM64EC, or isolate the plugin out-of-process and communicate over RPC/COM/gRPC.
  • NuGet package assets not deployed
    • Cause: Package lacks win-arm64 assets or project didn’t select them.
    • Fix: Add runtime-specific assets under runtimes/win-arm64; set RuntimeIdentifier to win-arm64.
  • Poor performance in emulated mode
    • Cause: Compute-heavy x64 code under Prism.
    • Fix: Port hot paths to ARM64/ARM64EC; prefer ARM64 versions of runtimes (Node/Python/.NET).
  • Docker build is slow or images won’t run
    • Cause: Building amd64 images on ARM host using emulation; Windows containers unsupported on ARM.
    • Fix: Use linux/arm64 base images; use remote or cloud builders for amd64; avoid Windows containers on ARM hosts.
  • Driver-dependent features missing
    • Cause: x64 kernel drivers cannot load.
    • Fix: Install OEM-provided ARM64 drivers; disable features depending on legacy drivers; engage vendor.

Diagnostic checks

  • Verify process architecture in Task Manager → Details → select columns “Architecture”.
  • Use dumpbin /headers on binaries to confirm Machine: ARM64 (0xAA64) or ARM64EC (0xA64E).
  • Environment correctness:
    • echo %VSCMD_ARG_TGT_ARCH% (should report arm64 or arm64ec).
    • where cl, link to confirm VS paths.

Best practices for stable, optimized ARM64 delivery

Build and packaging

  • Offer native ARM64 packages alongside x64. Prefer multi-arch MSIX bundles.
  • Maintain a build matrix (x86/x64/ARM64/ARM64EC) in CI. Validate that each artifact includes the correct native dependencies.
  • Use vcpkg triplets and lockfiles for reproducible dependency builds.
See also  Windows on ARM Compatibility List 2025: Native Emulated or Replace?

Architecture decisions

  • Default to pure ARM64 for new code. Choose ARM64EC only when you must load in-process x64 components.
  • Minimize calls across ARM64EC↔x64 boundaries; consolidate and batch APIs.
  • Consider out-of-proc brokers to isolate x64 components when ARM64EC isn’t feasible.

Performance and battery

  • Keep firmware/GPU drivers up to date via OEM support tools.
  • In Windows Settings → System → Power & battery:
    • Prefer Balanced for normal use; switch to Best performance for benchmarking.
  • Use ETW/WPA to understand CPU residency and hot paths; prioritize hot functions for ARM64 optimization.

Tooling hygiene

  • Avoid mixing library architectures. Create separate output directories per platform.
  • Verify NuGet/vcpkg packages have ARM64 variants before committing to them.
  • Ensure Visual Studio Extensions used in ARM64 VS are ARM64-compatible.

Fallback strategies

  • If a dependency is x64-only and critical, use ARM64EC or out-of-proc hosting.
  • Document feature degradations for ARM64 where unavoidable and provide telemetry-based guidance for future porting.

Use case example: Port a plugin-based desktop app with ARM64EC

Goal: Ship a performant ARM-first app while keeping critical x64 plugins working.

  1. Choose host architecture
  • Make the main executable ARM64EC so it can load x64 plugins immediately.
  1. Prepare your solution
  • In Visual Studio: Configuration Manager → add ARM64EC platform for the EXE and all in-proc modules you own.
  • For each of your native DLLs you control, set Platform to ARM64EC (or ARM64 if it will never directly call x64).
  1. Update dependencies
  • Rebuild third-party libraries you control for ARM64EC/ARM64 via vcpkg:
    vcpkg install zlib:arm64ec-windows
  • For x64-only plugins (3rd-party), keep them as-is. They will be loaded into your ARM64EC process.
  1. Build and run
  • Build the solution as ARM64EC:
    msbuild MySolution.sln -p:Configuration=Release -p:Platform=ARM64EC
  • Run on an ARM device running Windows 11. Verify plugins load.
  1. Optimize
  • Profile with WPA; identify hot paths in your code.
  • Migrate performance-critical DLLs from ARM64EC to pure ARM64 to reduce overhead (still OK if the host is ARM64EC).
  • Reduce frequency of calls between your native code and x64 plugin boundaries.
  1. Long-term migration
  • Work with plugin vendors to obtain ARM64 builds.
  • Once all in-proc deps are ARM64, switch the host to pure ARM64 for best end-to-end performance.

Use case example: Docker and cross-compiling on Windows on ARM

Scenario: You develop a C++ service and ship Linux containers. You also ship a Windows desktop tool.

  • Windows desktop tool:
    • Build ARM64 native as above. Package MSIX with x64 and ARM64.
  • Linux containers for ARM64:
    • Install Docker Desktop on Windows on ARM (uses WSL2/ARM64).
    • Prefer linux/arm64 base images; rebuild native dependencies for arm64.
    • Build:
      docker buildx create –use –name builder
      docker buildx build –platform linux/arm64 -t org/app:arm64 –load .
  • Multi-arch images:
    • Build arm64 locally and amd64 with a remote/cloud builder (avoid slow local emulation):
      docker buildx build –platform linux/arm64,linux/amd64 -t org/app:latest –push .
  • CI approach:
    • On x64 runners, cross-compile Windows ARM64 using vcvarsall x64_arm64 and MSBuild.
    • On Linux runners, build arm64 container images natively, or use remote arm64 builders.

Conclusion

Windows on ARM is ready for serious development. With ARM64 you get class-leading efficiency and performance on Copilot+ PCs. With ARM64EC, you can bridge the gap for x64-only components while delivering a native-first experience. Using Visual Studio 2022, MSBuild/CMake, vcpkg, and NuGet, you can compile, package, and ship across architectures with confidence. Follow the steps and best practices in this guide to get production-quality results, measurable speed-ups, and fewer surprises.

FAQ

What is ARM64EC and when should I use it?

ARM64EC is a hybrid ABI that allows native execution on ARM64 while interoperating with x64 modules in the same process. Use it when you need to host x64 plugins or dependencies you cannot yet port. For new code and full control over dependencies, prefer pure ARM64.

Can I build ARM64/ARM64EC on an x64 development PC?

Yes. Visual Studio’s cross tools support ARM64 and ARM64EC. Use the “x64 Native Tools Command Prompt” and run vcvarsall x64_arm64 or x64_arm64ec, then build with MSBuild or CMake.

Will x64 apps work on Copilot+ PCs without changes?

Many will, thanks to the Prism emulation layer in Windows 11. However, performance is generally better with native ARM64 builds, and kernel-mode drivers must be ARM64. For best results, port to ARM64 or use ARM64EC to bridge dependencies.

How do I ensure NuGet and vcpkg packages work on ARM64?

For vcpkg, use the correct triplet (arm64-windows or arm64ec-windows). For NuGet, make sure packages provide ARM64 runtime assets. If a package is x64-only and in-process, consider ARM64EC or an out-of-proc architecture.

Are Windows containers supported on Windows on ARM?

Windows containers are not supported on ARM. Use Docker Desktop with WSL2 for Linux containers. Prefer linux/arm64 images and remote builders for amd64 images to avoid slow emulation.

About the author

Jonathan Dudamel

Jonathan Dudamel

I'm Jonathan Dudamel, an experienced IT specialist and network engineer passionate about all things Windows. I have deep expertise in Microsoft project management, virtualization (VMware ESXi and Hyper-V), and Microsoft’s hybrid platform. I'm also skilled with Microsoft O365, Azure ADDS, and Windows Server environments from 2003 through 2022.

My strengths include Microsoft network infrastructure, VMware platforms, CMMS, ERP systems, and server administration (2016/2022).