This project demonstrates a way for a native process to host .NET using the nethost
and hostfxr
libraries. Documentation on the nethost
and hostfxr
APIs can be found here.
This sample is part of the .NET hosting tutorial. Please see that topic for a more detailed explanation of the sample and the steps necessary to host .NET.
The host is small and bypasses a lot of complexity (thorough error checking, etc.) that a real host would have. Hopefully by remaining simple, though, it will be useful for demonstrating the core concepts of hosting managed .NET code in a native process.
Demonstrates how to locate and initialize .NET runtime from a non-.NET process and subsequently load and call into a .NET assembly.
The nethost
header and library are part of the Microsoft.NETCore.DotNetAppHost
package and are also installed as a runtime pack by the .NET SDK. The library should be deployed alongside the host. This sample uses the files installed with the .NET SDK.
Note: The Microsoft.NETCore.DotNetAppHost
package is a metapackage that doesn't actually contain the files. It only references RID-specific packages that contain the files. For example, the package with the actual files for linux-x64
is runtime.linux-x64.Microsoft.NETCore.DotNetAppHost
.
The coreclr_delegates.h
and hostfxr.h
files are copied from the dotnet/runtime repo - coreclr_delegates.h and hostfxr.h.
Additional comments are contained in source and project files.
-
.NET Core 6.0 SDK or a later version
-
C++ compiler
- Windows:
cl.exe
- Linux/OSX:
g++
- Windows:
-
In order to build and run, all prerequisites must be installed. The following are also required:
- On Linux/macOS, the C++ compiler (
g++
) must be on the path. - The C++ compiler (
cl.exe
org++
) anddotnet
must be the same bitness (32-bit versus 64-bit).- On Windows, the sample is set up to use the bitness of
dotnet
to find the correspondingcl.exe
- On Windows, the sample is set up to use the bitness of
- On Linux/macOS, the C++ compiler (
-
Navigate to the root directory.
-
Run the samples. Do one of the following:
- Use
dotnet run
(which will build and run at the same time). - Use
dotnet build
to build the executable. The executable will be inbin
under a subdirectory for the configuration (Debug
is the default).- Windows:
bin\Debug\nativehost.exe
- Non-Windows:
bin/Debug/nativehost
- Windows:
- Use
The expected output will come from the DotNetLib
class library and include the arguments passed to the managed library from the host:
Hello, world! from Lib [count: 1]
-- message: from host!
-- number: 0
Hello, world! from Lib [count: 2]
-- message: from host!
-- number: 1
Hello, world! from Lib [count: 3]
-- message: from host!
-- number: 2
Hello, world! from CustomEntryPoint in Lib
-- message: from host!
-- number: -1
Hello, world! from CustomEntryPointUnmanagedCallersOnly in Lib
-- message: from host!
-- number: -1
To make this sample run a managed app instead of loading a class library, launch the nativehost
passing app
as a command line argument. The expected output will come from the App
application:
App started - args = [ app_arg_1, app_arg_2 ]
Hello, world! from App [count: 1]
-- message: from host!
Hello, world! from App [count: 2]
-- message: from host!
Hello, world! from App [count: 3]
-- message: from host!
Signaling app to close
Note: The way the sample is built is relatively complicated. The goal is that it's possible to build and run the sample with simple dotnet run
with minimal requirements on pre-installed tools. Typically, real-world projects that have both managed and native components will use different build systems for each; for example, msbuild/dotnet for managed and CMake for native.
The src\HostWithHostFxr.sln
solution file can be used to open the sample in Visual Studio 2019. To run the sample, set the startup project to build/NativeHost
.
Note that with mixed mode debugging (that is, a debugger that can see both native and managed code at the same time), there's a known limitation where no breakpoints will be hit before the runtime starts. So it is not possible to debug the parts of the sample before (and including) the call to load_assembly_and_get_function_pointer
like that. To debug those, start the process from a native-only debugger.