GithubHelp home page GithubHelp logo

openbytedev / netcorehost Goto Github PK

View Code? Open in Web Editor NEW
99.0 5.0 7.0 62.72 MB

A .NET Core hosting library written in Rust with included bindings for nethost and hostfxr.

License: MIT License

Rust 99.43% C# 0.57%
hostfxr nethost hosting coreclr dotnet-core

netcorehost's Introduction

netcorehost

CI crates.io Documentation dependency status MIT

A Rust library for hosting the .NET Core runtime.

It utilizes the .NET Core hosting API to load and execute managed code from withing the current process.

Usage

Running an application

The example below will setup the runtime, load Test.dll and run its Main method:

let hostfxr = nethost::load_hostfxr().unwrap();
let context = hostfxr.initialize_for_dotnet_command_line(pdcstr!("Test.dll")).unwrap();
let result = context.run_app().value();

The full example can be found in examples/run-app.

Calling a managed function

A function pointer to a managed method can be aquired using an AssemblyDelegateLoader. This is only supported for HostfxrContext's that are initialized using Hostfxr::initialize_for_runtime_config. The runtimeconfig.json is automatically generated for executables, for libraries it is neccessary to add <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles> to the projects .csproj file.

Using the default signature

The default method signature is defined as follows:

public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);

A method with the default signature (see code below) can be loaded using AssemblyDelegateLoader::get_function_with_default_signature.

C#

using System;

namespace Test {
    public static class Program {
        public static int Hello(IntPtr args, int sizeBytes) {
            Console.WriteLine("Hello from C#!");
            return 42;
        }
    }
}

Rust

let hostfxr = nethost::load_hostfxr().unwrap();
let context =
    hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
let fn_loader =
    context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
let hello = fn_loader.get_function_with_default_signature(
    pdcstr!("Test.Program, Test"),
    pdcstr!("Hello"),
).unwrap();
let result = unsafe { hello(std::ptr::null(), 0) };
assert_eq!(result, 42);

Using UnmanagedCallersOnly

A function pointer to a method annotated with UnmanagedCallersOnly can be loaded without specifying its signature (as these methods cannot be overloaded).

C#

using System;
using System.Runtime.InteropServices;

namespace Test {
    public static class Program {
        [UnmanagedCallersOnly]
        public static void UnmanagedHello() {
            Console.WriteLine("Hello from C#!");
        }
    }
}

Rust

let hostfxr = nethost::load_hostfxr().unwrap();
let context =
    hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
let fn_loader =
    context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
let hello = fn_loader.get_function_with_unmanaged_callers_only::<fn()>(
    pdcstr!("Test.Program, Test"),
    pdcstr!("UnmanagedHello"),
).unwrap();
hello(); // prints "Hello from C#!"

Specifying the delegate type

Another option is to define a custom delegate type and passing its assembly qualified name to AssemblyDelegateLoader::get_function.

C#

using System;

namespace Test {
    public static class Program {
        public delegate void CustomHelloFunc();
    
        public static void CustomHello() {
            Console.WriteLine("Hello from C#!");
        }
    }
}

Rust

let hostfxr = nethost::load_hostfxr().unwrap();
let context =
    hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
let fn_loader =
    context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
let hello = fn_loader.get_function::<fn()>(
    pdcstr!("Test.Program, Test"),
    pdcstr!("CustomHello"),
    pdcstr!("Test.Program+CustomHelloFunc, Test")
).unwrap();
hello(); // prints "Hello from C#!"

The full examples can be found in examples/call-managed-function.

Passing complex parameters

Examples for passing non-primitive parameters can be found in examples/passing-parameters.

Features

  • nethost - Links against nethost and allows for automatic detection of the hostfxr library.
  • download-nethost - Automatically downloads the latest nethost binary from NuGet.

Related crates

Additional Information

License

Licensed under the MIT license (LICENSE or http://opensource.org/licenses/MIT)

netcorehost's People

Contributors

a-ctor avatar openbytedev avatar spencercw avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

netcorehost's Issues

Executing from Bytes instead of from Disk

There is a Load method from which you can load an assembly from Bytes in memory versus having the path on disk. I have been unable to find support for this in this crate. Are there any plans for this, or is there anyway it is already possible?

Building the project using the stable toolchain

Hi,

I have just found your project and am trying to use it with the stable toolchain but it won't build. This is because of the features in lib.rs:

#![feature(maybe_uninit_uninit_array, maybe_uninit_slice, try_trait_v2)]

I assume this is intended? It would be nice if it was possible to build the project with the stable toolchain as well. I created a fork where I removed the usages of the features and it seems to work. The changes are about 40 lines of code that are changed removed.

I think the maybe_uninit_uninit_array and maybe_uninit_slice features can be removed entirely. Maybe it is possible to wrap the try_trait_v2 in its own feature? This way I could opt out and use the stable toolchain to build.

Thank you for your work by the way works great :)

Call rust functions using the extern keyword?

Can i define rust functions in the dotnet host so that C# code can access them using the extern keyword?

pub fn some_func() { }
public static extern void some_func();

Is this possible?

calling C# from Rust

So, can we calling C# from Rust, Such as dotnet release a dll file, Rust call instance or method?
image
like this

Error: proc-macro derive panicked

Hello, I unable to build this after applying the fix described here: #25

The error linked to nethost-sys is now fixed, but I still get an error linked to hostfxr-sys:

image

Compiling hostfxr-sys v0.5.0
Compiling XXX v0.1.0 (xxxxxxx)
Compiling nethost-sys v0.6.1 (https://github.com/OpenByteDev/nethost-sys#3bae0526)
error: proc-macro derive panicked
--> C:\Users\xxxxx\hostfxr-sys-0.5.0\src\lib.rs:232:22
|
232 | #[derive(WrapperApi)]
| ^^^^^^^^^^
...
243 | / derive_apis! {
244 | | pub struct Hostfxr {
245 | | /// Run an application.
246 | | ///
... |
676 | | }
677 | | }
| |_- in this macro invocation
|
= help: message: Only bare functions, references and pointers are allowed in structures implementing WrapperApi trait
= note: this error originates in the macro 'derive_apis' (in Nightly builds, run with -Z macro-backtrace for more info)

error: could not compile 'hostfxr-sys' due to previous error

Would you have an idea how to fix this ?

error[E0554]: `#![feature]` may not be used on the stable release channel

Hey ,

I got below error , any suggession ?

Compiling netcorehost v0.3.1
error[E0554]: #![feature] may not be used on the stable release channel
--> D:\RUST\CARGO\registry\src\github.com-1ecc6299db9ec823\netcorehost-0.3.1\src\lib.rs:1:1
|
1 | / #![feature(
2 | | maybe_uninit_uninit_array,
3 | | maybe_uninit_extra,
4 | | maybe_uninit_slice,
5 | | negative_impls
6 | | )]
| |__^

error: aborting due to previous error

error: failed to run custom build command for `nethost-sys v0.5.1`

I got the below error after update to nightly-x86_64-pc-windows-msvc - rustc 1.66.0-nightly (6e95b6da8 2022-10-22), any clue ? This was working for a long time.

Caused by:
process didn't exit successfully: H:\TEST\nethost8\target\debug\build\nethost-sys-440c9e86335f8dee\build-script-mod (exit code: 101)
--- stderr
thread 'main' panicked at 'Failed to parse json response from nuget.org.: reqwest::Error { kind: Decode, source: Error("missing field items", line: 1, column: 689) }', C:\RUST\CARGO\registry\src\github.com-1ecc6299db9ec823\nethost-sys-0.5.1\build\download.rs:119:10
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace
warning: build failed, waiting for other jobs to finish...

[dependencies]
netcorehost = "0.12.0"
path-absolutize = "^3.0.10"
wchar = "^0.11.0"
json = "*"

How to use initialize_for_dotnet_command_line_with_args with command line args?

I can't figure out how to call initialize_for_dotnet_command_line_with_args with env::args(). I need to translate the String that args() produces into a PdCstr, but the latter seems to want fixed strings known at compile-time. I'm very new to rust, so I apologize if this comes off as a silly question. Your help is appreciated!

Here is what I've tried:

use std::env;

use netcorehost::{nethost, pdcstr, pdcstring::PdCStr};

fn main() {
    let hostfxr = nethost::load_hostfxr().unwrap();

    // Pass all arguments to the .NET Core application
    let context = hostfxr.initialize_for_dotnet_command_line_with_args(env::args().skip(1).into_iter().map(|f| pdcstr!(f)).collect()[..]).unwrap();
    let result = context.run_app().as_hosting_exit_code().unwrap();
    println!("Exit code: {}", result);
}
    Checking dotnet-sdk-matching-runtime v0.1.0 (...)
error[E0435]: attempt to use a non-constant value in a constant
  --> src\main.rs:10:120
   |
10 |     let context = hostfxr.initialize_for_dotnet_command_line_with_args(env::args().skip(1).into_iter().map(|f| pdcstr!(f)).collect()[..]).unwrap();
   |                                                                                                                --------^-
   |                                                                                                                |       |
   |                                                                                                                |       non-constant value
   |                                                                                                                help: consider using `let` instead of `const`: `let _WIDESTRING_U16_MACRO_UTF8`

could not find `u16cstr` in the list of imported crates

Any idea why this error ?
--> src\main.rs:17:9
|
17 | pdcstr!("Test.Program, Test"),
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ could not find u16cstr in the list of imported crates
|
= note: this error originates in the macro pdcstr (in Nightly builds, run with -Z macro-backtrace for more info)

Add missing APIs to bindings and abstractions

Some APIs are completely missing, like:

  • hostfxr_resolve_sdk2
  • hostfxr_get_available_sdks
  • hostfxr_get_native_search_directories
  • hostpolicy as a whole

Some functions are present in the bindings, but have no matching component in the higher level API:

  • hostfxr_set_error_writer
  • hostfxr_main
  • hostfxr_main_startupinfo
  • hostfxr_main_bundle_startupinfo

Cross compiling fails to build

Hi I am trying to cross compile a .dll on linux for windows using x86_64-pc-windows-gnu as, my target, however, the build keeps failing. Is this library not compatible for cross compiling yet?

PowerShell 7 Native Host

Hi! I just found out about your project and wanted to let you know I've been working on something similar on my side meant to load PowerShell 7 entirely at runtime from its installation path. I finished my initial proof-of-concept code in C, after which I intend to port it to Rust. Your project is definitely much more advanced on the Rust side, but you may be interested in taking some bits and pieces from the dynamic loading technique that eliminates the need for statically linking anything from .NET.

Here are the relevant issues:
PowerShell/PowerShell#14652
PowerShell/PowerShell#14641
dotnet/runtime#46652

And the reference code I have so far:
https://github.com/awakecoding/pwsh-native-host
https://github.com/awakecoding/pwsh-host-rs

Since PowerShell 7 ships a full copy of .NET 5, my project aims at detecting PowerShell and then loading it. Nothing really prevents loading another .NET 5 runtime from another location, and re-implementing the nethost static library that exports a single function to facilitate detection. My intent is really to make PowerShell (or .NET 5) fully usable as a library without installing additional files.

You will notice the related issues are all about adding a single helper function to make it possible to load an assembly file in memory. Rust makes it quite easy to embed files, but for the C code I wrote a script to generate a static array. PowerShell 7.2-preview6 now includes "LoadAssemblyFromNativeMemory" but this function could be provided by a separate DLL and ported to anything, really, if you can afford to ship an additional DLL with your program.

Let me know if some of the things I've done on my side could interest you for this project. Your Rust code is much more advanced than my current C code I've barely begun porting to Rust. I'll be working on this for the next week or so.

Load multiple functions with one AssemblyDelegateLoader crashes.

Currently, the test examples in this crate runs 1 function from the AssemblyDelegateLoader:

    let hostfxr = nethost::load_hostfxr()?;
    let context =hostfxr.initialize_for_runtime_config(PdCString::from_os_str(runtime_config_path)?)?;
    let fn_loader = context.get_delegate_loader_for_assembly(PdCString::from_os_str(assembly_path)?)?;

    let hello = fn_loader.get_function_pointer_with_default_signature(
        pdcstr!("Test.Program, Test"),
        pdcstr!("Hello"),
    )?;
    let result = unsafe { hello(ptr::null(), 0) };
    assert_eq!(result, 42);

Consider the following:

    let hostfxr = nethost::load_hostfxr()?;
    let context = hostfxr.initialize_for_runtime_config(PdCString::from_os_str(runtime_config_path)?)?;
    let fn_loader = context.get_delegate_loader_for_assembly(PdCString::from_os_str(assembly_path)?)?;

    let hello_one = fn_loader.get_function_pointer_with_default_signature(
        pdcstr!("Test.Program, Test"),
        pdcstr!("Hello"),
    )?;
    let result = unsafe { hello_one(ptr::null(), 0) };
    assert_eq!(result, 42);

    let hello_two = fn_loader.get_function_pointer_with_default_signature(
        pdcstr!("Test.Program, Test"),
        pdcstr!("HelloTwo"),
    )?;
    let result = unsafe { hello_two(ptr::null(), 0) };
    assert_eq!(result, 42);

HelloTwo being a copy of the Hello function.

This errors with Hostfxr(Unknown(2147942402)) (file not found?). Oddly, hostfxr actually appears to complete it's tasks successfully, the detailed logs don't seem to mention any errors.

The solution is to have two let fn_loader = context.get_delegate_loader_for_assembly(PdCString::from_os_str(assembly_path)?)?;, one per method, before each get pointer fn.

Since accessing multiple functions from one assembly/loader is an expected use case, and it errors with an unknown error rather than pointing it out as a limitation, I'm assuming it's correct to report this as a bug.

Certainly, It does not feel correct to continuously point to the assembly and create loaders of it per function call? I would assume that you lazy-load it once, when creating a delegate loader, and then you get any amount of method references from it?

AccessViolationException when passing string to .NET

Hi,

I got below error, any idea to pass value to c# funciton

Compiling nethost2 v0.1.0 ()
Finished dev [unoptimized + debuginfo] target(s) in 1.41s
Running target\debug\nethost2.exe
argLength 1
Hello, world! from Program [count: 1]
Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.SpanHelpers.IndexOf(Char ByRef, Char, Int32)
at System.String.Ctor(Char*)
at System.Runtime.InteropServices.Marshal.PtrToStringUni(IntPtr)
at Test.Program.PrintLibArgs(LibArgs)
at Test.Program.Hello(IntPtr, Int32)
error: process didn't exit successfully: target\debug\nethost2.exe (exit code: 0xc0000005, STATUS_ACCESS_VIOLATION)

Link nethost statically

Hi!

Thank you for this nice crate.
I was wondering if it would be possible to link against nethost statically?

Why `and Delegate Loaded don't implement the send trait ?

My apologies, I've just seen I wrote the title a little to fast, I'm talking about DelegateLoader and HostfxrContext<>.

Since my application heavly rely on C# interop, I would like to load CoreClr at static time, I tried to work around by using Arc<> and Mutex<> but there is no way to put HostfxrContext in LazyLock ? (or a [ctor] from ctor crate) ?

Easier method of executing Rust code from C#

Hello, I was wondering of an easier method to execute Rust code (unmanaged) from C# (managed). I already know of the return-string-from-managed example and was able to easily get it working, but it required a field and two methods to be created for a single unmanaged method:

// Required since only unsafe code can use function pointers.
private static unsafe void DoRustyThings() {
    DoRustyThingsPtr();
}

private static unsafe delegate*<void> DoRustyThingsPtr;

[UnmanagedCallersOnly]
public static unsafe void SetDoRustyThingsPtr(delegate*<void> doRustyThingsPtr) => DoRustyThingsPtr = doRustyThingsPtr;

Mono has a really simple way to call managed code from unmanaged code by adding an attribute:

[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static void DoRustyThings();

Is there anyway that running managed code from unmanaged code could become easier and lead to less boilerplate? If its not implemented just yet, I may be able to mess around with implementing it and make a PR, or just hack something up to get it working for me. Thanks!

Execution from multiple threads

Thanks for the library! It works great most of the cases.

But my use-case requires using global static instances of AssemblyDelegateLoader and ManagedFunction.

I workarounded the ManagedFunction part by just using regular function pointers like this

let fn_init_dotnet_ptr: ManagedFunction<extern "system" fn() -> *const usize> = fn_init_dotnet_ptr?;
let fn_init_dotnet_ptr = *fn_init_dotnet_ptr.deref();

But I can not do the same for AssemblyDelegateLoader. Now I'm using thread_local, but I want to use OnceCell like this:

static DELEGATE_LOADER: OnceCell<Arc<AssemblyDelegateLoader<&'static PdCStr>>> = OnceCell::new();

However it is compile time error:

error[E0277]: `Rc<netcorehost::dlopen2::wrapper::Container<netcorehost::bindings::hostfxr_sys::wrapper::Hostfxr>>` cannot be sent between threads safely
  --> src/lib.rs:17:26
   |
17 | static DELEGATE_LOADER: OnceCell<Arc<AssemblyDelegateLoader<&'static PdCStr>>> = OnceCell::new();
   |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rc<netcorehost::dlopen2::wrapper::Container<netcorehost::bindings::hostfxr_sys::wrapper::Hostfxr>>` cannot be sent between threads safely
   |
   = help: within `AssemblyDelegateLoader<&'static PdCStr>`, the trait `Send` is not implemented for `Rc<netcorehost::dlopen2::wrapper::Container<netcorehost::bindings::hostfxr_sys::wrapper::Hostfxr>>`
   = note: required because it appears within the type `DelegateLoader`
   = note: required because it appears within the type `AssemblyDelegateLoader<&'static PdCStr>`
   = note: required for `Arc<AssemblyDelegateLoader<&'static PdCStr>>` to implement `Sync`
   = note: 1 redundant requirement hidden
   = note: required for `once_cell::imp::OnceCell<Arc<AssemblyDelegateLoader<&'static PdCStr>>>` to implement `Sync`
   = note: required because it appears within the type `once_cell::sync::OnceCell<Arc<AssemblyDelegateLoader<&'static PdCStr>>>`
   = note: shared static variables must have a type that implements `Sync`

Can you please provide the Sync option for this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.