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
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
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.