Search code examples
c#.netunmanagedstub

Programmatically creating a .NET Core AppHost for a DLL in C#


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?


Solution

  • Thanks to the comment posted by @HansPassant, I figured it out.

    Here are the steps to do it.

    1. Copy the apphost.exe file from 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)
    2. In our C# program to generate the AppHost, install the package Microsoft.NET.HostModel, version 3.0.0, via NuGet.
    3. Add this single method invocation:
    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
    );
    
    1. Add the necessary JSON files:
      • ConsoleApp1.deps.json:
    {
      "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": ""
        }
      }
    }
    
    • ConsoleApp1.runtimeconfig.json
    {
      "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.