Windows on Arm (WoA) devices, such as the Surface Pro X, can run x86 Windows apps in emulation. With insider builds, WoA devices can also emulate x64 Windows apps.
Emulation translates the x86 CPU instructions to Arm instructions. Of course, this is not as fast as running applications natively — that is, directly compiled for the Arm architecture and instruction set. So, if you want to take advantage of the platform’s power and maximize battery life, you should create native apps.
You can create a native app for WoA using several approaches. For example, you can create a Universal Windows Platform (UWP) app targeting Arm devices. Since the 5.0.8 release of the .NET Framework, Windows Forms, Windows Presentation Foundation (WPF), and UWP have native Arm support. If you use any of those frameworks for an existing application, it's simple to compile it natively for WoA devices.
In this article, we will take a Windows Forms (WinForms) application developed on an x86-64 machine and port it to a native WoA WinForms app. To follow along, you should be familiar with C#, .NET, and WinForms.
Creating a New AArch64 Project
As a demonstration, let's start by creating a new Windows Forms project and then compiling it for AArch64 (64-bit Arm). First, ensure that you have Visual Studio 2019 installed with the .NET desktop development component. It looks like this in the Visual Studio installer:
Next, we open Visual Studio and choose Create a new project. In the template list, we select Windows Forms App, not Windows Forms App (.NET Framework). We do not want Windows Form App because that is for .NET Framework 4.x, whereas we want .NET 5.
Then, we click Next and choose a Project name and Location for our application.
The wizard then asks which framework we want to target: .NET Core 3.1 or .NET 5. While we can compile to AArch64 when targeting .NET Core 3.1, the performance is better when targeting .NET 5 because it uses A hardware intrinsics more. So, we select .NET 5.0 and click Create. We can then start developing a Windows Forms application as usual.
As a simple demonstration, let's create a form that just has one button. When a user clicks it, a message box pops up displaying the process architecture.
We start by adding a button to the form in the designer:
Then, we double-click on the button to add a Click event handler. We use the following code as an event handler:
private void button1_Click(object sender, EventArgs e)
If you run the application on your development machine and press the button, it probably displays x64 (unless you are not on an x64 device, of course).
Compiling a Native WoA Application
Now, let's compile our application natively for a WoA device instead. First, we double-click Properties in Solution Explorer:
Doing this opens the project's properties.
We then go to the Build tab.
Then, all we have to do is change Platform target to ARM64 and recompile. We cannot run the application on our development machine now because the architecture is incompatible, so let's try running it on an Arm device instead. We copy the bin/Debug/net5.0-windows directory to our Arm device and try running WinFormsArm.exe in that directory.
If you do not have the .NET runtime installed yet, the device prompts you to do that first and redirects you to the download page. On that page, you select Download Arm64 under Run console apps, run the installer, and do the same for Run desktop apps (you need both).
When the .NET runtime finishes installing, we try running the application and pressing the button again. It now shows Arm64 in the message box.
For comparison, you can also try setting the Platform target to x86, downloading the x86 runtimes on your Arm device, then running the x86 executable. You will see that the message box shows "x86," so it is running under emulation.
As you can see, compiling a Windows Forms application for AArch64 is straightforward: you only need to ensure you target .NET 5 and change the Platform target to ARM64. For existing applications on .NET Framework 4.x, you should create a new project in Visual Studio (because the project types for .NET 5 and .NET Framework 4.x differ) and import the files from your old project.
Keep in mind the Scale and layout Windows setting when creating or porting an application for Windows on Arm. A device like Surface Pro has a high resolution on a relatively small screen, so 200% is the recommended setting. On the other hand, your development machine probably has set this value between 100% and 150%.
The Scale and layout setting affects, for example, the text and form size. But controls such as a PictureBox don't scale. You will want to ensure that your application looks good regardless of this setting’s value. Otherwise, your form could look quite distorted on an Arm device. This concern is, of course, not unique to Arm because all Windows devices have that setting, but it is good to remember.
Benchmarking AArch64 Against x86 Emulation
The advantage of compiling natively for AArch64 instead of emulating an x86 binary is that it is faster. Let's do a quick benchmark to confirm this is true.
Our benchmark consists of generating a 400 by 400 Bitmap, filling it with random pixels, and displaying the result. We are expecting this to be faster when compiling for AArch64 than for x86.
We will expand our form with a new button to start the benchmark, a label to display how long it took, and a
PictureBox to display the image.
First, we name the button
benchmarkBtn, the label
benchmarkLabel, and the
PictureBox benchmarkPbox. Then, we double-click on
benchmarkBtn to add a click event handler that performs the benchmark:
private void benchmarkBtn_Click(object sender, EventArgs e)
Stopwatch sw = new Stopwatch();
Random rng = new Random();
Bitmap bmp = new Bitmap(400, 400);
for (int i = 0; i < 10; i++)
for (int x = 0; x < 400; x++)
for (int y = 0; y < 400; y++)
bmp.SetPixel(x, y, Color.FromArgb(rng.Next()));
benchmarkPbox.Image = bmp;
benchmarkLabel.Text = "Benchmark time: " + sw.Elapsed.ToString();
We need to add using
System.Diagnostics; at the top of the file because our code uses Stopwatch.
The benchmark uses
SetPixel to fill one pixel at a time, which is an inefficient way to generate a random image. However, it does not matter for a benchmark of this kind because the results are only significant compared to each other.
The whole process of generating a random image repeats ten times to average the results for each iteration. Only the last image displays, but that also does not matter. When the picture finishes generating, our app displays the elapsed time on the label.
Try it out by compiling the application for AArch64 and x86, then running them both on your Arm device. You will also want to set the build configuration to Release instead of Debug to optimize the code. The executable files then end up in bin/Release instead of bin/Debug.
On my Arm device, the native AArch64 application completed the benchmark between 0.6 and 0.7 seconds. The emulated x86 application took 1.2 to 1.5 seconds. So, the native application performed this benchmark twice as fast. Of course, it times a specific situation, and not all operations have the same relative speed, but it does highlight that a natively-compiled application is more efficient, as we expected.
To take full advantage of the WoA platform, you should create your applications natively to the Arm architecture. With a Windows Forms application, it’s easy to target .NET 5 and tell Visual Studio to compile for AArch64.
To learn more about this topic, you might like to explore Remote Debugging a C# or Visual Basic project in Visual Studio. Now that you know how to write native Windows Forms applications for WoA, start building your own Arm-native WinForms apps!