core
core copied to clipboard
Tour of .NET Behavior on Windows 11 Arm64
Tour of .NET Behavior on Windows 11 Arm64
We're going to take a tour of the basic behavior of .NET 6 and .NET Framework 4.8.1 on Windows 11 Arm64. This tour of the experience is going to give us a sense of which .NET variants can be installed on the machine, how to use them to target various architectures, and what various key APIs report. This tour will provide a good sense of the experience that you an expect and that is on offer.
The primary questions it answers are:
- When I build an app, which architecture does it target/support by default?
- How do I build my app for a different/particular architecture?
- Is there a way to get my app to run a different architecture than what it was built for as part of launching it?
A subset of this document will be moved to official docs. In fact, it was created as a set of working notes to do just that. The author of those notes is sharing them with the hope that they will be a benefit for someone trying to figure out how best to target Windows Arm64.
The tour is on Windows 11 Arm64, however, you could just as easily use these instructions to build for Windows Arm64 on Windows x64 and then run the built apps on Windows Arm64. Also, much of what you'll see equally applies to macOS x64 and Arm64.
The key -- potentially surprising -- experience is that .NET Framework
AnyCPU
apps run as emulatedx64
on Windows Arm64. That choice was made for the benefit of compatibility. The document describes multiple ways to get your .NET Framework apps to run natively, either as a build-time or launch-time decision.
I'm using .NET 6.0.8 (.NET SDK 6.0.400) and .NET Framework 4.8.1. I'll be using Arm64, x64, and x86 variants of both. .NET 6 is not included in Windows 11 (or any other Windows version), while all three .NET Framework architecture variants are included. I'm using a Windows 11 Insider build (10.0.22621). The machine and OS are Arm64.
There is also one honorable mention of .NET 7, where its behavior differs from .NET 6.
Much of the tour relies on the Developer Command Prompt
that comes with Visual Studio 2022. Visual Studio 2022 will soon be supported on Windows 11 Arm64.
Test samples
We'll be using the following samples to test our environment:
- https://github.com/dotnet/dotnet-docker/blob/main/samples/dotnetapp/Program.cs
- https://github.com/microsoft/dotnet-framework-docker/blob/main/samples/dotnetapp/Program.cs
Those are samples we maintain for containers, but they are also great for this use case.
PATH
All the .NET variants mentioned are installed on the Windows 11 Arm64 test machine that will be used for the tour.
The PATH
is a key aspect of any dev experience and is critical to understand to using platform components (particularly tools).
Let's take a look at the PATH
:
$env:PATH
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files\dotnet\;C:\Program Files\Git\cmd;C:\Program Files\Microsoft SQL Server\150\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;C:\Users\rich\AppData\Local\Microsoft\WindowsApps;C:\Users\rich\.dotnet\tools;C:\Users\rich\AppData\Local\Programs\Microsoft VS Code\bin
The following path segments relate to .NET:
-
C:\Program Files\dotnet\
-
C:\Users\rich\.dotnet\tools
There are two interesting aspects here:
- Only the native architecture .NET (Core) location is registered. That's Arm64.
- There is just one location for .NET tools. That feature is built in such a way that tools from multiple architectures can be co-located without issue.
There is no PATH
entry for .NET Framework. That's nothing new. There has never been a PATH
entry for .NET Framework. It doesn't explicitly need one. If you want to use .NET Framework tools, you need to rely on the Developer Command Prompt for Visual Studio
.
Building and running by default
First, .NET 6:
PS C:\Users\rich\git\dotnet-docker\samples\dotnetapp> dotnet run
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
The app is running on Arm64, as expected.
Next, .NET Framework 4.8.1:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>msbuild /t:restore;build
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>bin\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X64
ProcessorCount: 8
In contrast, the .NET Framework app is running on x64.
.NET Framework apps run as x64 by default on Windows Arm64. That was a key design decision made to aid compatibility. .NET Framework apps have been primarily running as x64 for over a decade on pervasively deployed x64 desktops, laptops, and servers. It's quite likely that many .NET Framework apps have x64 dependencies (like native libraries). Running them as emulated seemed like the safest bet, and to make Arm64 opt-in.
Targeting Arm64
Targeting Arm64 is easy with .NET 6. Your app will target the same architecture as the SDK you are using. You'll produce an Arm64 app by default as long as you are using the Arm64 SDK. That was just demonstrated above.
However, if you want to force targeting Arm64 (which will be more important later on the tour), you can use -a arm64
as demonstrated in the following:
PS C:\Users\rich\git\dotnet-docker\samples\dotnetapp> dotnet run -a arm64
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
There is a similar option with MSBuild, for .NET Framework, using the Platform
property:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>msbuild /t:restore;build /p:Platform=arm64
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>bin\arm64\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
You can also use the .NET SDK for the same purpose:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>dotnet run -a arm64
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
I'm using dotnet run
for this demonstration, but dotnet build
works the same way.
Targeting x64
Targeting x64 is just a variation on what you've just seen (mostly).
For .NET 6, we can continue using the Arm64 SDK, but target x64 with the same -a
switch you just saw. That's the easiest and highest performance approach (the SDK is a lot of code and will run slowly when emulation).
PS C:\Users\rich\git\dotnet-docker\samples\dotnetapp> dotnet run -a x64
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
We can also use the x64 SDK, which will produce x64 apps by default. That's not the recommended approach (due to the emulation performance cost), but works fine.
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>"c:\Program Files\dotnet\x64\dotnet" run
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
There isn't a C:\Program Files (x64)
on Windows Arm64. As a result, the x64 variant of .NET is installed in C:\Program Files\dotnet
in the child x64
directory. That's what you see demonstrated above. the x64
directory will only be there if you install the x64 .NET SDK. It doesn't come with the Arm64 SDK.
To complete the demonstration, we can use the x64 SDK to produce Arm64 apps. That's also not recommended, but works.
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>"c:\Program Files\dotnet\x64\dotnet" run -a arm64
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
For .NET Framework, x64 is the default target as demonstrated above. There isn't anything more to show.
Targeting x86
For .NET 6, this experience is very similar to targeting x64. We'll use the same approach.
First, using Arm64 SDK, again using the -a
switch:
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>dotnet run -a x86
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X86
ProcessorCount: 8
TotalAvailableMemoryBytes: 2.00 GiB
Huh. Why did we drop from (near) 32GB to 2GB? Are 32-bit apps running in some small virtual machine used for emulation? How do I make it bigger? That's not it. 32-bit processes are limited to 4GB of memory and it's split 50/50 between kernel and user mode. That's why we're seeing 2GB.
We see slightly difference behavior with .NET 7 (specifically for OSArchitecture
).
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>dotnet run -a x86
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 7.0.0-preview.7.22375.6
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: X86
ProcessorCount: 8
TotalAvailableMemoryBytes: 2.00 GiB
Let's try with .NET Framework.
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>msbuild /t:restore;build /p:Platform=x86
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>bin\x86\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: X86
ProcessorCount: 8
We can try the same thing with the .NET SDK:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>dotnet run -a x86
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: X86
ProcessorCount: 8
Hmmm. That's interesting. OSArchitecture
is reported differently for .NET 6 on one side and .NET 7 and .NET Framework 4.8.1 on the other when running x86 apps. They must use different Windows APIs to report OSArchitecture
. They don't all report the same thing.
Coercing already-built apps to Arm64
With .NET 6, you can (in some cases) run apps targeted for one architecture to another. We won't go into all the details on that, but will demonstrate the mechanism.
We'll first build and run the app natively, as has been demonstrated earlier.
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>dotnet run
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
Now, we'll use the x64 dotnet
to run the app.
C:\Users\rich\git\dotnet-docker\samples\dotnetapp>"C:\Program Files\dotnet\x64\dotnet" bin\Debug\net6.0\dotnetapp.dll
42
42 ,d ,d
42 42 42
,adPPYb,42 ,adPPYba, MM42MMM 8b,dPPYba, ,adPPYba, MM42MMM
a8" `Y42 a8" "8a 42 42P' `"8a a8P_____42 42
8b 42 8b d8 42 42 42 8PP""""""" 42
"8a, ,d42 "8a, ,a8" 42, 42 42 "8b, ,aa 42,
`"8bbdP"Y8 `"YbbdP"' "Y428 42 42 `"Ybbd8"' "Y428
.NET 6.0.8
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X64
ProcessorCount: 8
TotalAvailableMemoryBytes: 31.41 GiB
Every .NET (Core) app has a native launcher. It's a component of every app and ties each app to an operating system and architecture. However, you don't need to launch an app with the native launcher that the SDK provides. You can launch it with the dotnet
host instead. That's what is being demonstrated above.
.NET Framework works differently. It has special executables that are not tied to an architecture in quite as direct a way. Lets try some techniques to coerce an x64 app to Arm64.
We'll rebuild the app again to ensure we're starting from a clean slate.
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>msbuild /t:restore;build
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>bin\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: X64
ProcessArchitecture: X64
ProcessorCount: 8
That looks good for our baseline.
Let's first try start
:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>start /MACHINE arm64 /B /WAIT bin\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
Our app now runs as Arm64.
runas
is another option that you can use to get a .NET Framework app to run as Arm64.
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>runas /machine:arm64 /trustlevel:0x20000 bin\Debug\net48\dotnetapp.exe
This launches another console windows, which follows:
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
In this case, I had to add a Console.ReadLine()
to the app. Otherwise, it would just open and close immediately. start
offers /wait
and /b
to work around that. For UI apps, that won't be a problem.
You can also specify that you want your app to always run in the registry as Arm64.
Create the following registry key (rename for your app):
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\dotnetapp.exe
It would have this DWORD
item and hex value:
PreferredMachine=aa64
or as decimal:
PreferredMachine=43620
You can see my registy entry.
Note: Is there a way to specify the absolute path for this key, not just the filename?
Let's try that:
C:\Users\rich\git\dotnet-framework-docker\samples\dotnetapp>bin\Debug\net48\dotnetapp.exe
ad88
,d d8"
88 88
8b,dPPYba, ,adPPYba, MM88MMM MM88MMM 8b, ,d8
88P' `"8a a8P_____88 88 88 `Y8, ,8P'
88 88 8PP""""""" 88 88 )888(
88 88 "8b, ,aa 88, 88 ,d8" "8b,
88 88 `"Ybbd8"' "Y888 88 8P' `Y8
.NET Framework 4.8.9065.0
Microsoft Windows 10.0.22621
OSArchitecture: Arm64
ProcessArchitecture: Arm64
ProcessorCount: 8
That works as expected.
For folks that start processes from code, you can specify machine type by using the UpdateProcThreadAttribute function. See PROC_THREAD_ATTRIBUTE_MACHINE_TYPE
.
Visual Studio
The targeting experience is the same in Visual Studio as it is for MSBuild.
Note: Is there a way to specify the absolute path for this key, not just the filename?
Yes, with the undocumented FilterFullPath
registry key.
Hmmm. That's interesting.
OSArchitecture
is reported differently for .NET 6 and .NET Framework 4.8.1 when running x86 apps. .NET Framework and .NET 6 use different Windows APIs to reportOSArchitecture
. They don't all report the same thing.
Isn't that a regression in .NET 6 comparing to .NET Framework 4.8.1?
The first three lines in the "netfx" logo need to be indented by one more space. Good article!
Reported: https://github.com/dotnet/runtime/issues/73974
Regarding the behavior for .NET Framework 4.8.1: is there no mechanism to enable Arm64 support at build time, other than building an Arm64-specific executable? Many .NET Framework apps (I'm thinking of client apps in particular) are distributed/deployed as loose, portable executables with no installer. Given the options described above, there's currently no convenient way to enable Arm64 support for these apps without either:
- complicating building & distribution by creating separate executables for Arm64
- distributing some kind of launch script that will launch the real app using the preferred architecture
- writing to the registry (which seems like a bad idea for a portable app)
I would have imagined that, for example, targeting .NET Framework 4.8.1 would automatically cause an AnyCPU app to start as Arm64 (i.e. changing the target framework would act as confirmation that the app was made to work with Arm64). Or, perhaps, an app.config option similar to <supportedRuntime> could indicate that the app should run using the Arm64 runtime instead of the x64 one. Or, one could even imagine a new "AnyCPU, Arm64 preferred" configuration that assemblies could be built with.
All of these alternatives would address the inability for .NET Framework apps to opt-in to Arm64 support without changing their deployment model. Many client apps, for better or for worse, are still built on .NET Framework, and this ability would allow them to trivially opt-in to Arm64 support. Given that "pure managed" apps with no unmanaged dependencies are very common, I would think that the return on a feature like this could be high, and the lack of it seems like a missed opportunity.
Great question. We had the 32BitPrefered feature from the past. It is kinda similar. I think this would be NativeArchPrefered. We haven't built that. While simple, it is not straightforward. We are looking some other options to achieve similar results. At present, you've correctly determined the set of options available.
Note that there isn't a magic feature like this coming for .NET 6/7/8. This limits our desire to invest in an expensive magic feature for .NET Framework.
Now that .NET Framework 4.8.1 is available to Windows 10 on ARM as well, it would be helpful to document the behavior there too. I guess AnyCPU will run as x86 by default there.
Also IIRC start /machine
is not available in in-market version of Windows 11 (21H2, Build 22000).
Good point. Yes, that should added to the doc, too. I am away on a trip so it will be a bit before that happens.
The problem with NativeArchPrefered is, it will create even more issues if .NET Framework add yet another new architecture (think RISC-V?) to support 5-10 years down the road.
Sure, hopefully .NET Framework would be dead by then, but you never know, it was presumed to be dead years ago when MS decided no .NET Framework on ARM64.
We are not going to build NativeArchPrefered for this reason. The options we are looking at are future proof and naturally extend to potential new architectures.
cc @davidwrighton
What is the point of targeting .NET Framework 4.8.1 right now, if not for supporting ARM64? I mean, no legacy project will bother to target net481 for some WinForms/WPF accessibility enhancements.
AnyCPU being x64 for net48 (and older) makes perfectly sense. But it doesn't for net481.
Just my 2 cents,
My ideal solution would be a section in the config file that just lists the architectures in the order it would like them to be tried. eg. <idealRuntimes>arm64;x64;x86</idealRuntimes>
, if this section is present, the .NET Framework will pick the first one it understands (being the installation, not the targeted framework) and that works on the OS. This config option would be ignored on older frameworks that don't know of this feature.
Ie. if I write an app targeting 4.6 or some other similar version and include this config option it will be ignored on all frameworks up to 4.8.1/2/whenever it's added and then followed on a framework that understands it. This solution would allow full developer control on what architectures they want to be able to run in.
Use case: I use the .NET Framework primarily as a bootstrapper for my .NET Core apps these days (and for some old apps and for older versions of some apps) - this allows a single exe to check .NET Core installations etc., and have no plans to stop using it since .NET Core will not be installed on Windows by default and isn't supported for like 10 years and isn't fully forwards compatible. I currently target .NET Framework 4.6.1 (for some reason?), but will eventually update to a newer version, but not past 4.8 since it doesn't support Windows 7 (until I eventually give up on supporting windows 7 but that's not directly related to what we're discussing here). I'd like the ability to be able to easily detect the os architecture in my bootstrapper which seems to be fixed in 4.8.1 (or earlier, see below), and be able to force older versions of my apps to be able to run in arm64 if I know it will work with ease.
Also, I have a question. If a user launches an app compiled with a version ≤4.8 on an arm64 machine, what will OSArchitecture return? It should probably return x64 in this scenario unless my above feature is implemented and includes that architecture in the list (since this is what it does with 4.8 and lower - or at least whatever version I tested this with I think).
Also a second question. If windows does support a new architecture in the future, is it safe to assume that they will all support x64 emulation? Or should I only assume x86 emulation is available? Is there a Windows API that tells me which processor architectures can be emulated from something like an image file machine constant
Thanks for your time reading this lol.
Ie. if I write an app targeting 4.6 or some other similar version and include this config option it will be ignored on all frameworks up to 4.8.1/2/whenever it's added and then followed on a framework that understands it.
I don't think this is necessary, most AnyCPU .NET FX app can run as ARM64 unmodified, as long as it does not use things like P/Invoke to third party unmanaged dependencies. I even ran a .NET 4.5 app on ARM64 with start /machine arm64
and it work just fine.
If we does not limit it to .NET 4.8.x target where this is added, then end-user can try to make their app ARM64 native even without source code.
Is there a Windows API that tells me which processor architectures can be emulated from something like an image file machine constant
GetMachineTypeAttributes: https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-getmachinetypeattributes
I don't think this is necessary, most AnyCPU .NET FX app can run as ARM64 unmodified, as long as it does not use things like P/Invoke to third party unmanaged dependencies. I even ran a .NET 4.5 app on ARM64 with
start /machine arm64
and it work just fine.
I do not expect my users to have to open the command line to open an app (which is why this doesn't solve it for a portable app), but I will use this when I have control over launching it.
If we does not limit it to .NET 4.8.x target where this is added, then end-user can try to make their app ARM64 native even without source code.
As you said above, a user can use start /machine arm64
already to do this, so I don't really see how this is an issue. I don't think these files show as being openable in notepad by default and if someone who doesn't do programming tries to open it then they would probably get scared by it looking like code and not change it anyway.
It might even be good that it's easy to change it permanently, e.g. if they have an old app that they want to force to run natively that doesn't have any native dependencies (they would probably find out very quickly whether it has native dependencies or not because it would crash, and could revert it).
GetMachineTypeAttributes: https://docs.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-getmachinetypeattributes
Thanks!
I don't think this is necessary, most AnyCPU .NET FX app can run as ARM64 unmodified
Also, can is different to do, as stated in the original post, these only run natively when compiled specifically for arm64. Otherwise they run in x64 emulation for compatibility (without things like running on the command line with start /machine arm64
).
Ie. if I write an app targeting 4.6 or some other similar version and include this config option
OK, I misunderstood you, I thought you mean one have to target 4.8.x in order to use this config option. So we actually are on the same page.
I just installed Windows 10 ARM for testing, and no, ARM64 .Net Framework is not available on Windows 10.
Not exactly sure why though.
I just installed Windows 10 ARM for testing, and no, ARM64 .Net Framework is not available on Windows 10.
Not exactly sure why though. https://devblogs.microsoft.com/dotnet/announcing-dotnet-framework-481/
Tangentially related question: Is there a plan to release a version of Microsoft.DotNet.Framework.NativeImageCompiler for Arm64? It works well for us on x86/x64 in places where we can't use Ngen.exe (the Microsoft Store), so it would be nice to provide the same experience on Arm64.
Tangentially related question: Is there a plan to release a version of Microsoft.DotNet.Framework.NativeImageCompiler for Arm64? It works well for us on x86/x64 in places where we can't use Ngen.exe (the Microsoft Store), so it would be nice to provide the same experience on Arm64.
Looks like it's deprecated, per the individual runtime packages https://www.nuget.org/packages/runtime.win10-x64.Microsoft.DotNet.Framework.NativeImageCompiler
If it's really deprecated, it would be nice if the official documentation recommending its use was amended: https://learn.microsoft.com/en-us/windows/msix/desktop/desktop-to-uwp-r2r
Q. Can I release binaries using this technology?
A. This version includes a Go Live license that you can use today.
So, is the NgenR2R tool deprecated, or were those packages marked accidentally in some automated proces? Will it receive Arm64 support? @jkotas?
Please give us an option to opt-in into ARM64 with the usual XXX.exe.config
files for .NET Framework apps.
@hrumhurum Came here looking for the same, or at least a configuration manifest. I was also able to run a 4.5.2 target in ARM64 as long as it was being built with VS2022 and Platform set to ARM64, but I want my AnyCPU builds to run as ARM64 on ARM64 Windows :)
The new supportedArchitectures
attribute in application manifest should be the way to go.
Kind of wonder why it is in application manifest though, that would be a little bit tricky to backport to downlevel OS...
https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests#supportedarchitectures