Starting with .NET Core, when we build an executable project in C# (such as a Console App or a desktop application like Windows Forms, WPF, etc), it generates more than just one .exe file - the actual IL code is stored in the DLL file, and the .exe is an unmanaged stub that locates .NET and passes the DLL. There's also the .deps.json & .runtimeconfig.json files. Example:
ConsoleApp1.deps.json - Dependencies
ConsoleApp1.dll - IL Code
ConsoleApp1.exe - Unmanaged Stub
ConsoleApp1.pdb - Program Debug Database, e.g. variable names
ConsoleApp1.runtimeconfig.json - .NET Runtime Configuration
Now, here's the problem. I have a DLL file generated using Cecil that contains the IL code, including an entry point. So I need the .exe file that I talked about - the unmanaged stub, ConsoleApp1.exe
, to ensure that I can execute the only DLL file I have.
My question is, is it possible to generate the AppHost - that unmanaged executable serving as a .NET stub, ConsoleApp1.exe
- programmatically in C#, for my own generated DLL containing IL code?
Thanks to the comment posted by @HansPassant, I figured it out.
Here are the steps to do it.
C:\Program Files\dotnet\packs\Microsoft.NET.App.Host.win-x64\<SDK VERSION>\runtimes\win-x64\native
(replace <SDK VERSION>
with the version of the .NET sdk like 8.0.12. Optionally replace win-x64 with the target OS-processor)Microsoft.NET.HostModel
, version 3.0.0, via NuGet.using Microsoft.NET.HostModel.AppHost;
HostWriter.CreateAppHost(
appHostSourceFilePath: "apphost.exe",
// ^^^
// Simple copy of:
// C:\Program Files\dotnet\Microsoft.NETCore.App.Host.win-x64\<SDK VERSION>\runtimes\win-x64\native\apphost.exe
appHostDestinationFilePath: "ConsoleApp1.exe",
// ^^^
// Save apphost.exe as <appHostDestinationFilePath>
appBinaryFilePath: "ConsoleApp1.dll",
// ^^^
// Path to the managed DLL, in my case, the one generated with Cecil
assemblyToCopyResorcesFrom: "ConsoleApp1.dll",
// ^^^
// Can be used to copy assembly info like title, version, company, etc
windowsGraphicalUserInterface: false
// ^^^
// false - console window is shown
// true - console window is hidden, typically for desktop apps like windows forms or WPF
);
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v8.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v8.0": {
"ConsoleApp1/1.0.0": {
"runtime": {
"ConsoleApp1.dll": {}
}
}
}
},
"libraries": {
"ConsoleApp1/1.0.0": {
"type": "project",
"serviceable": false,
"sha512": ""
}
}
}
{
"runtimeOptions": {
"tfm": "net8.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "8.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}
The apphost is generated, and it works as tested.