{"id":21820465,"url":"https://github.com/smx-smx/ezdotnet","last_synced_at":"2025-04-14T02:53:21.859Z","repository":{"id":73708570,"uuid":"195588278","full_name":"smx-smx/EzDotnet","owner":"smx-smx","description":"Load a C# assembly from a native executable or a shared library","archived":false,"fork":false,"pushed_at":"2025-02-04T00:32:47.000Z","size":122,"stargazers_count":13,"open_issues_count":0,"forks_count":4,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-27T16:56:04.487Z","etag":null,"topics":["coreclr","csharp","cygwin","dotnet","dotnetcore","embedding","hosting","injection","mono","native","shared-library"],"latest_commit_sha":null,"homepage":"","language":"CMake","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"zlib","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smx-smx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-07-06T22:30:01.000Z","updated_at":"2025-02-12T05:19:14.000Z","dependencies_parsed_at":"2024-11-27T16:47:04.303Z","dependency_job_id":null,"html_url":"https://github.com/smx-smx/EzDotnet","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smx-smx%2FEzDotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smx-smx%2FEzDotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smx-smx%2FEzDotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smx-smx%2FEzDotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smx-smx","download_url":"https://codeload.github.com/smx-smx/EzDotnet/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248813829,"owners_count":21165631,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coreclr","csharp","cygwin","dotnet","dotnetcore","embedding","hosting","injection","mono","native","shared-library"],"created_at":"2024-11-27T16:34:34.399Z","updated_at":"2025-04-14T02:53:21.841Z","avatar_url":"https://github.com/smx-smx.png","language":"CMake","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EzDotNet\nLoad a C# assembly inside a native executable\n\n## Project structure:\nThere are 3 backends:\n- CLRHost for Windows (.NET Framework v4.x)\n- MonoHost for any platform supporting Mono (Windows/macOS/Linux/etc...)\n- CoreCLR for platforms supporting dotnet core\n\nThe backends expose the same interface so that it's possible to swap them while keeping the same code.\n\nTo load a managed assembly, we need to pull the EzDotnet APIs into our project.\n\nThis can be done in one of the following ways:\n- Statically linking against one of the backends: `coreclrhost`, `monohost` or `clrhost`\n- Dynamic linking (`dlopen`/`LoadLibrary`)\n- Using the sample dynamic helper library (`ezdotnet_shared`)\n\n## C# Project setup\nFirst, create a new console application:\n```bash\ndotnet new console -o ManagedSample\n```\n\nThen, add the `Microsoft.NETCore.DotNetAppHost` nuget package, for example via the `dotnet` cli:\n```bash\ndotnet add package Microsoft.NETCore.DotNetAppHost\n```\n\nNow create a EntryPoint for the native code, using the following code as a starting point:\n\n```csharp\nnamespace ManagedSample\n{\n\tpublic class EntryPoint {\n\t\tprivate static string[] ReadArgv(IntPtr args, int sizeBytes) {\n\t\t\tint nargs = sizeBytes / IntPtr.Size;\n\t\t\tstring[] argv = new string[nargs];\n\t\t\t\n\t\t\tfor(int i=0; i\u003cnargs; i++, args += IntPtr.Size) {\n\t\t\t\tIntPtr charPtr = Marshal.ReadIntPtr(args);\n\t\t\t\targv[i] = Marshal.PtrToStringAnsi(charPtr);\n\t\t\t}\n\t\t\treturn argv;\n\t\t}\n\t\t\n\t\tprivate static int Entry(IntPtr args, int sizeBytes) {\n\t\t\tstring[] argv = ReadArgv(args, sizeBytes);\n\t\t\tMain(argv);\n\t\t\treturn 0;\n\t\t}\n\n\t\tpublic static void Main(string[] args){\n\t\t\tConsole.WriteLine(\"Hello, World\");\n\t\t}\n\t}\n}\n```\n\nNote down the fully qualified class name (the class name including namespace - `ManagedSample.EntryPoint`), and the method name (`Entry`). We will need them later in the native code.\n\n**NOTE**: It's *strongly* recommended to build the C# project as a console application in order to generate the necessary `runtimeconfig` json files, which are required if you're going to use the `CoreCLR` host.\n\n**NOTE**: The `AnyCPU` processor architecture should work normally. In case of issues, change the target processor architecture to match the one used by the native loader.\n\n**NOTE**: When building the project, you **MUST** use a `publish` command like the following (either from the IDE or `dotnet publish` from the command line):\n\n```bash\ndotnet publish -r linux-x64 --no-self-contained\n```\n\nThis is important to make sure the correct `nethost.dll` and required dependencies are copied to the output directory.\n`--no-self-contained` is also required, or you will get the following error at load time: `Initialization for self-contained components is not supported`\n\n\nAs an alternative, and to have F5 debug working, you can add the following target to the `.csproj` file to copy the nethost at build time:\n```xml\n    \u003cItemGroup Condition=\"'$(OS)'=='Windows_NT'\"\u003e\n        \u003cPackageReference Include=\"runtime.win-x64.Microsoft.NETCore.DotNetAppHost\" Version=\"7.0.0\" /\u003e\n    \u003c/ItemGroup\u003e\n    \u003cTarget AfterTargets=\"Build\" Name=\"CopyHostFxr\" Condition=\"'$(OS)'=='Windows_NT'\"\u003e\n        \u003cMessage Importance=\"high\" Text=\"Copying nethost.dll\" /\u003e\n        \u003cCopy SourceFiles=\"$(OutDir)runtimes\\win-x64\\native\\nethost.dll\" DestinationFolder=\"$(OutDir)\" /\u003e\n    \u003c/Target\u003e\n```\nIf you need multi-platform support, you will need to add multiple configurations for each runtime ID (`rid`) and OS combo\n\n\nThe following paragraph explains how to setup the native loader:\n\n## Native setup\nA sample dynamic helper is provided to ease the process of loading .NET and calling the entry point of an assembly.\n\nOtherwise, refer to the [API Documentation](#api-documentation) to use static/dynamic linking yourself.\n\n### Dynamic helper\n\nIf you decide to use the dynamic helper, you have to load `ezdotnet_shared` and resolve the `int main(int argc, char *argv[])` method.\n\nRefer ot the following sample for details:\n\n```cpp\ntypedef int (*pfnEzDotNetMain)(int argc, const char *argv[]);\n\nHMODULE ezDotNet = LoadLibraryA(\"libezdotnet_shared.dll\");\npfnEzDotNetMain main = reinterpret_cast\u003cpfnEzDotNetMain\u003e(GetProcAddress(ezDotNet, \"main\"));\nconst char *argv[] = {\n\t// name of the program (argv0) - unused (can be set to anything)\n\t\"ezdotnet\",\n\t// path of the .NET backend to use\n\t\"libcoreclrhost.dll\",\n\t// path of the .NET assembly to load\n\t\"bin/x86/Debug/net7.0/publishManagedSample.dll\", \n\t// fully qualified class name to invoke\n\t\"ManagedSample.EntryPoint\", \n\t// name of the entry method inside the class (can be private)\n\t\"Entry\" \n};\n// call main(argc, argv)\npfnMain(5, argv);\n```\n\n### API documentation\n\nThe backends share a common interface:\n\n#### clrInit\n\n- `ASMHANDLE clrInit(const char *assemblyPath, const char *baseDir, bool enableDebug)`\n\nLoads the assembly specified by `assemblyPath`, sets the base search directory to `baseDir`\n\n\u003e NOTE: `enableDebug` is supported on Windows only (it's ignored on other platforms). If set, the JIT debugger will be invoked\n\nReturns: a handle to the loaded assembly\n\n\n#### clrDeInit\n- `bool clrDeInit(ASMHANDLE handle)`\n\nDeinitializes the execution environment.\n\n\u003e NOTE: on some runtimes, it might be impossible to call `clrInit` again afterwards\n\n\n#### runMethod\n- `int runMethod(ASMHANDLE handle, const char *typeName, const char *methodName)`\n\nRuns the method `methodName` inside the class `typeName`, given a `handle` to an assembly loaded by a previous `clrInit` call.\n\nThe C# method is expected to have the following signature:\n```csharp\n\t\tprivate static int Entry(IntPtr args, int sizeBytes) {\n\t\t\tstring[] argv = ReadArgv(args, sizeBytes);\n\t\t\tMain(argv);\n\t\t\treturn 0;\n\t\t}\n```\n\u003e NOTE: The C# method visibility is ignored, so you can supply `private` methods as well\n\nIn 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)).\n\n## Use cases\n\n### Executable or Library\nYou can use EzDotnet inside an executable or a library.\nYou 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.\n\n**WARNING**\n\nIf you're loading EzDotnet from a DLL, avoid loading the CLR from library constructors like `DllMain`. Doing so will cause a deadlock in `clrInit`.\n\nInstead, create a separate thread and use it to load the CLR, so that `DllMain` is free to return.\n\n### Cygwin Interoperability\nThis project enables you to call Cygwin code from .NET.\nFor this use case, the .NET host/loader (for example `samples/cli/ezdotnet`, or `libcoreclrhost`) **MUST** be compiled under Cygwin.\n\nIn other words, you can call code Cygwin code from .NET only if you're starting with a Cygwin process, and you load .NET afterwards.\nStarting from Win32 and calling into Cygwin will **NOT** work\n\nTherefore, 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.\n\n**NOTE**: The `ezdotnet` CLI **MUST** be compiled as a Cygwin application.\n\n### Process Injection\nIf you're building a shared library, you can inject it into another process to enable it to run .NET code.\nFor this use case you will need to use a library injector.\nThere are several tools and ways to achieve this, for example:\n\n#### Windows\n- [Detours](https://github.com/microsoft/Detours) has an API to spawn a process with a DLL\n- [SetSail](https://github.com/TheAssemblyArmada/SetSail) can inject a DLL at the EXE Entrypoint\n\n#### Unix-like\n- [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)\n\n#### Universal\n- [ezinject](https://github.com/smx-smx/ezinject) can inject a library in a running executable\n- or use your favorite injector","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmx-smx%2Fezdotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmx-smx%2Fezdotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmx-smx%2Fezdotnet/lists"}