When creating cross-platform games in .NET Core, you'll typically use the dotnet new console
template for the project. When creating a console app, you end up with a project file like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>
Running this application under Windows results in a console window showing up even if the bulk of the application runs inside a graphical window (created by SDL or some other middleware). The console window is fine for development but no good when it comes time to ship the application. Unlike macOS or Linux, applications in Windows have a flag embedded into the binary itself dictating whether it should spawn a console window.
Before .NET Core 3.0 was released, I could only find one real option to deal with this: the tool editbin.exe
, which ships with Visual Studio (not VS Code).
editbin.exe /subsystem:windows yourapp.exe
And this works, but I hate that my build environment requires editbin.exe
be installed and added to my PATH
. It's just more stuff that can go wrong; I wanna keep things simple.
Thankfully, .NET Core 3.0 introduced a new way to solve this problem: simply change your OutputType
to WinExe
in your .csproj
file! I much prefer this solution because it brings me back to only needing the .NET Core SDK and nothing else. Complexity is the enemy, and I will slay it whenever I can.
However, permanently changing my OutputType
complicates things: I want that console window almost all the time during development. Not only does it let me see my console debug output, it gives me another handy way to close the application in case it hangs or something: I can just close the console window itself. So, ideally, I want the OutputType
to be Exe
except when publishing my application.
So, initially, I created my script publish.cmd
to contain this:
dotnet publish -c Release -r win-x64 -p:CopyOutputSymbolsToPublishDirectory=false -p:OutputType=WinExe
And this works... but not really. The problem is that the moment you introduce a dependency to your project, this path fails. Let's say you are building a shared library the same time you are building your desktop application.
<ItemGroup>
<ProjectReference Include="..\Kelly.Shared\Kelly.Shared.csproj" />
</ItemGroup>
You will suddenly run into this error:
CSC : error CS5001: Program does not contain a static 'Main' method suitable for an entry point [C:\Users\kelly\Documents\Kelly.Shared\Kelly.Shared.csproj]
What is going on here? It turns out that when you specify -p:OutputType=WinExe
, it happens globally. The property is propagated to all projects being built. So, your shared library is no longer happy to just be a library; it wants to be an executable! And that just can't happen because, as the error points out, it has no Main
method.
So, back to the drawing board. How can I clearly indicate that I want only Kelly.Client
to be WinExe
? Well, I solved it by introducing a property into my project that no other project cares about.
<PropertyGroup>
<KellyClientOutputType>Exe</KellyClientOutputType>
<OutputType>$(KellyClientOutputType)</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
Now my project looks to the property KellyClientOutputType
to know what the output type is. This means I can alter my script to change this other property, and it will not affect any dependencies.
rmdir /s /q bin\Release
rmdir /s /q obj\Release
dotnet publish -c Release -r win-x64 -p:CopyOutputSymbolsToPublishDirectory=false -p:KellyClientOutputType=WinExe
Voila. Now I can just run this script whenever I wish to make a clean build that I want to share with outside parties.
Top comments (1)
Hi Kelly,
I'm in a similar situation trying to create a dotnet binary that will run on Windows, Linux and MacOS for my upcoming game. I was wondering if you'd tried tweaking MSBuild, maybe using a conditional, so that on/for Windows it uses WinExe (except for UWP apps) and for the other platforms it uses Exe?
D.