Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/smx-smx/ezdotnet
Load a C# assembly from a native executable or a shared library
https://github.com/smx-smx/ezdotnet
coreclr csharp cygwin dotnet dotnetcore embedding hosting injection mono native shared-library
Last synced: about 1 month ago
JSON representation
Load a C# assembly from a native executable or a shared library
- Host: GitHub
- URL: https://github.com/smx-smx/ezdotnet
- Owner: smx-smx
- License: zlib
- Created: 2019-07-06T22:30:01.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2024-09-14T17:36:24.000Z (4 months ago)
- Last Synced: 2024-09-15T23:07:52.224Z (4 months ago)
- Topics: coreclr, csharp, cygwin, dotnet, dotnetcore, embedding, hosting, injection, mono, native, shared-library
- Language: CMake
- Homepage:
- Size: 116 KB
- Stars: 11
- Watchers: 4
- Forks: 4
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: COPYING
Awesome Lists containing this project
README
# EzDotNet
Load a C# assembly inside a native executable## Project structure:
There are 3 backends:
- CLRHost for Windows (.NET Framework v4.x)
- MonoHost for any platform supporting Mono (Windows/macOS/Linux/etc...)
- CoreCLR for platforms supporting dotnet coreThe backends expose the same interface so that it's possible to swap them while keeping the same code.
To load a managed assembly, we need to pull the EzDotnet APIs into our project.
This can be done in one of the following ways:
- Statically linking against one of the backends: `coreclrhost`, `monohost` or `clrhost`
- Dynamic linking (`dlopen`/`LoadLibrary`)
- Using the sample dynamic helper library (`ezdotnet_shared`)## C# Project setup
First, create a new console application:
```bash
dotnet new console -o ManagedSample
```Then, add the `Microsoft.NETCore.DotNetAppHost` nuget package, for example via the `dotnet` cli:
```bash
dotnet add package Microsoft.NETCore.DotNetAppHost
```Now create a EntryPoint for the native code, using the following code as a starting point:
```csharp
namespace ManagedSample
{
public class EntryPoint {
private static string[] ReadArgv(IntPtr args, int sizeBytes) {
int nargs = sizeBytes / IntPtr.Size;
string[] argv = new string[nargs];
for(int i=0; i
```
If you need multi-platform support, you will need to add multiple configurations for each runtime ID (`rid`) and OS comboThe following paragraph explains how to setup the native loader:
## Native setup
A sample dynamic helper is provided to ease the process of loading .NET and calling the entry point of an assembly.Otherwise, refer to the [API Documentation](#api-documentation) to use static/dynamic linking yourself.
### Dynamic helper
If you decide to use the dynamic helper, you have to load `ezdotnet_shared` and resolve the `int main(int argc, char *argv[])` method.
Refer ot the following sample for details:
```cpp
typedef int (*pfnEzDotNetMain)(int argc, const char *argv[]);HMODULE ezDotNet = LoadLibraryA("libezdotnet_shared.dll");
pfnEzDotNetMain main = reinterpret_cast(GetProcAddress(ezDotNet, "main"));
const char *argv[] = {
// name of the program (argv0) - unused (can be set to anything)
"ezdotnet",
// path of the .NET backend to use
"libcoreclrhost.dll",
// path of the .NET assembly to load
"bin/x86/Debug/net7.0/publishManagedSample.dll",
// fully qualified class name to invoke
"ManagedSample.EntryPoint",
// name of the entry method inside the class (can be private)
"Entry"
};
// call main(argc, argv)
pfnMain(5, argv);
```### API documentation
The backends share a common interface:
#### clrInit
- `ASMHANDLE clrInit(const char *assemblyPath, const char *baseDir, bool enableDebug)`
Loads the assembly specified by `assemblyPath`, sets the base search directory to `baseDir`
> NOTE: `enableDebug` is supported on Windows only (it's ignored on other platforms). If set, the JIT debugger will be invoked
Returns: a handle to the loaded assembly
#### clrDeInit
- `bool clrDeInit(ASMHANDLE handle)`Deinitializes the execution environment.
> NOTE: on some runtimes, it might be impossible to call `clrInit` again afterwards
#### runMethod
- `int runMethod(ASMHANDLE handle, const char *typeName, const char *methodName)`Runs the method `methodName` inside the class `typeName`, given a `handle` to an assembly loaded by a previous `clrInit` call.
The C# method is expected to have the following signature:
```csharp
private static int Entry(IntPtr args, int sizeBytes) {
string[] argv = ReadArgv(args, sizeBytes);
Main(argv);
return 0;
}
```
> NOTE: The C# method visibility is ignored, so you can supply `private` methods as wellIn this example, the `Entry` function reads arguments passed from the native host, and calls the Program `Main` entry (for details, see the [Cygwin Sample](https://github.com/smx-smx/EzDotnet/blob/6a44ed661c4ea41f74c47698d908117628545717/samples/Managed/Cygwin/Program.cs#L29) or the [Project Setup](#c-project-setup)).
## Use cases
### Executable or Library
You can use EzDotnet inside an executable or a library.
You can either link statically against a single loader or use dynamic linking (e.g. `dlopen`) so that the engine to use (CLR/CoreCLR/Mono) can be chosen at runtime.**WARNING**
If you're loading EzDotnet from a DLL, avoid loading the CLR from library constructors like `DllMain`. Doing so will cause a deadlock in `clrInit`.
Instead, create a separate thread and use it to load the CLR, so that `DllMain` is free to return.
### Cygwin Interoperability
This project enables you to call Cygwin code from .NET.
For this use case, the .NET host/loader (for example `samples/cli/ezdotnet`, or `libcoreclrhost`) **MUST** be compiled under Cygwin.In other words, you can call code Cygwin code from .NET only if you're starting with a Cygwin process, and you load .NET afterwards.
Starting from Win32 and calling into Cygwin will **NOT** workTherefore, if you want to build a typical CLI or Windows Forms application with Cygwin features, you will need to start the application with the `ezdotnet` CLI for it to work properly.
**NOTE**: The `ezdotnet` CLI **MUST** be compiled as a Cygwin application.
### Process Injection
If you're building a shared library, you can inject it into another process to enable it to run .NET code.
For this use case you will need to use a library injector.
There are several tools and ways to achieve this, for example:#### Windows
- [Detours](https://github.com/microsoft/Detours) has an API to spawn a process with a DLL
- [SetSail](https://github.com/TheAssemblyArmada/SetSail) can inject a DLL at the EXE Entrypoint#### Unix-like
- [LD_PRELOAD](https://man7.org/linux/man-pages/man8/ld.so.8.html) (Linux, FreeBSD, and others) can be used to preload a library in a executable (at launch time)#### Universal
- [ezinject](https://github.com/smx-smx/ezinject) can inject a library in a running executable
- or use your favorite injector