.NET 7 app developed from Arm64 won’t run on x64

1k Views Asked by At

I’m developing a .NET 7 app on Windows 11, Arm64. Compiling for “any CPU”. When trying to run the app in X64 computers, I get an error saying “This app can’t run on your PC”. The computer has .NET 7 SDK and runtime installed.

If I compile the app using .NET Framework 4.8 on the Arm64 computer, it works properly on the x64 computer as well. What is the issue with .NET 7 here?

2

There are 2 best solutions below

2
On BEST ANSWER

You can try using dotnet to run your compiled dll or publish the app (via the UI or using dotnet publish) providing architecture specific runtime identifier (RID) for example - win-x64. Note the --self-contained parameter which by default is true when RID is specified and the project is an executable one:

--sc|--self-contained [true|false]

Publishes the .NET runtime with your application so the runtime doesn't need to be installed on the target machine. Default is true if a runtime identifier is specified and the project is an executable project (not a library project).

Which will lead to publishing all components of the app, including the .NET libraries and target runtime (see the corresponding docs). Set it to false to publish framework-dependent executable, for example:

dotnet publish -r win-x64 --self-contained false

Also you can consider using publish profile files.

3
On

If you have AnyCPU NET Core app that you want to build once and run on all platforms (x86/x64/ARM64), then you'll need 3 EXEs, one for each platform. There are 2 main problems with the way it currently behaves:

  1. Long build times = Even though your app might target AnyCPU, which means you only have to build it once, adding runtime identifiers for each supported platform means you'll have to build and publish it once for each of those platforms.

  2. The build output is not deterministic because it depends upon the platform of the build environment. When developer A gets the sources and builds the app on x64 machine, he'll get x64 EXE. Developer B takes the same sources, builds it on his ARM64 machine and he'll get ARM64-only EXE.

To address both issues, I tried to reverse-engineer the current build process and I came up with this:

<PropertyGroup>
    <!-- Do not generate the default app host which for Any CPU depends upon the OS where we build - x64 for x64 OS, ARM64 for ARM64 OS -->
    <UseAppHost>false</UseAppHost>
</PropertyGroup>
<Target Name="CustomAppHostBuild" AfterTargets="AfterBuild">
    <PropertyGroup>
        <!-- Console app or Windows UI app? -->
        <CustomUseWindowsGraphicalUserInterface Condition="'$(OutputType)'=='WinExe'">true</CustomUseWindowsGraphicalUserInterface>
        <MyProgramFilesPath>$([System.Environment]::ExpandEnvironmentVariables("%ProgramW6432%"))</MyProgramFilesPath>
    </PropertyGroup>
    <PropertyGroup>
        <CustomTargetPathx86>$(MyProgramFilesPath)\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x86</CustomTargetPathx86>
        <CustomTargetDirectoriesLengthMinusOnex86>$([MSBuild]::Subtract($([System.IO.Directory]::GetDirectories("$(CustomTargetPathx86)").Length), 1))</CustomTargetDirectoriesLengthMinusOnex86>
        <CustomTargetNetSdkx86>$([System.IO.Directory]::GetDirectories("$(CustomTargetPathx86)")[$(CustomTargetDirectoriesLengthMinusOnex86)])</CustomTargetNetSdkx86>

        <CustomTargetPathx64>$(MyProgramFilesPath)\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-x64</CustomTargetPathx64>
        <CustomTargetDirectoriesLengthMinusOnex64>$([MSBuild]::Subtract($([System.IO.Directory]::GetDirectories("$(CustomTargetPathx64)").Length), 1))</CustomTargetDirectoriesLengthMinusOnex64>
        <CustomTargetNetSdkx64>$([System.IO.Directory]::GetDirectories("$(CustomTargetPathx64)")[$(CustomTargetDirectoriesLengthMinusOnex64)])</CustomTargetNetSdkx64>

        <CustomTargetPathARM64>$(MyProgramFilesPath)\\dotnet\\packs\\Microsoft.NETCore.App.Host.win-arm64</CustomTargetPathARM64>
        <CustomTargetDirectoriesLengthMinusOneARM64>$([MSBuild]::Subtract($([System.IO.Directory]::GetDirectories("$(CustomTargetPathARM64)").Length), 1))</CustomTargetDirectoriesLengthMinusOneARM64>
        <CustomTargetNetSdkARM64>$([System.IO.Directory]::GetDirectories("$(CustomTargetPathARM64)")[$(CustomTargetDirectoriesLengthMinusOneARM64)])</CustomTargetNetSdkARM64>
    </PropertyGroup>
    
    <!-- Generate hosts per-platform: x64 will be the default app host no matter the OS where we build $(RuntimeFrameworkVersion) -->
    <CreateAppHost AppHostSourcePath="$(CustomTargetNetSdkx64)\runtimes\win-x64\native\apphost.exe" AppBinaryName="$(AssemblyName).dll" WindowsGraphicalUserInterface="$(CustomUseWindowsGraphicalUserInterface)" AppHostDestinationPath="$(TargetDir)\$(AssemblyName).exe" IntermediateAssembly="$(IntermediateOutputPath)\$(AssemblyName).dll" />
    <CreateAppHost AppHostSourcePath="$(CustomTargetNetSdkx86)\runtimes\win-x86\native\apphost.exe" AppBinaryName="$(AssemblyName).dll" WindowsGraphicalUserInterface="$(CustomUseWindowsGraphicalUserInterface)" AppHostDestinationPath="$(TargetDir)\$(AssemblyName)_x86.exe" IntermediateAssembly="$(IntermediateOutputPath)\$(AssemblyName).dll" />
    <CreateAppHost AppHostSourcePath="$(CustomTargetNetSdkARM64)\runtimes\win-arm64\native\apphost.exe" AppBinaryName="$(AssemblyName).dll" WindowsGraphicalUserInterface="$(CustomUseWindowsGraphicalUserInterface)" AppHostDestinationPath="$(TargetDir)\$(AssemblyName)_ARM64.exe" IntermediateAssembly="$(IntermediateOutputPath)\$(AssemblyName).dll" />
</Target>
<ItemGroup>
    <!-- Include the generated hosts in the publish output -->
    <None Include="$(TargetDir)\$(AssemblyName).exe">
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <Visible>False</Visible>
        <Link>%(Filename)%(Extension)</Link>
    </None>
    <None Include="$(TargetDir)\$(AssemblyName)_x86.exe">
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <Visible>False</Visible>
        <Link>%(Filename)%(Extension)</Link>
    </None>
    <None Include="$(TargetDir)\$(AssemblyName)_ARM64.exe">
        <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
        <Visible>False</Visible>
        <Link>%(Filename)%(Extension)</Link>
    </None>
</ItemGroup>

It creates all 3 hosts in the published output and it defaults to x64 as the main platform regardless of the build machine. Just add it to the csproj.

Working example can be found here: https://github.com/jimm98y/MultiTargetingSample