Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous Improvements #11

Merged
merged 13 commits into from
Jan 7, 2025
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ Cargo.lock
*.dump
# These are backup files generated by rustfmt
**/*.rs.bk

# JetBrains IDE files
.idea/**
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ bencher = "0.1.5"
[workspace]
members = ["src/wrapped_mono_macros"]
[features]
default = ["referneced_objects"]
default = ["referenced_objects"]
## Disables boxing/unboxing safety checks. Normally, when an object is unboxed, it's type is checked to prevent crashes and errors. Enabling unsafe_unboxing will make wrapped_mono assume that type given by the user is always correct.
unsafe_boxing = []
## Disables array safety checks. Normally, when an array is created, it will make checks to ensure that its managed type matches its unmanaged type.
Expand All @@ -38,7 +38,7 @@ regen_binds = ["bindgen"]
## Dumps code created as results of macros into "macro.dump" file. Use for debugging when macros do not behave as expected.
dump_macro_results = ["wrapped_mono_macros/dump_macro_results"]
## Prevents objects in use by rust from being removed by mono runtime, adds slight overhead but is essential for storing objects long term. Can be disabled, but disabling it forces manual management of object lifetimes using GC handles.
referneced_objects = []
referenced_objects = []
## Uses some old variants of mono API. Try if `mono_threads_enter_gc_unsafe_region` symbol is missing during linking.
old_gc_unsafe = []

Expand Down
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
# wrapped_mono
`wrapped_mono` is a safe, lightweight wrapper around the mono library. It allows embedding of the mono runtime inside a rust project. Inside this embedded runtime code written in languages supporting the .NET framework, such as C# and F#, can be run. This allows usage of libraries written in those languages, and using them as a scripting language. The mono runtime is used by many game engines, and this wrapper allows using it with projects written in Rust too.
`wrapped_mono` is a safe, lightweight wrapper around the mono library.
It allows embedding of the mono runtime inside a rust project.
Inside this embedded runtime code written in languages supporting the .NET framework, such as C# and F#, can be run.
This allows usage of libraries written in those languages, and using them as a scripting language.
The mono runtime is used by many game engines, and this wrapper allows using it with projects written in Rust too.

# WIP
## Lacking APIs
While `wrapped_mono` already has support for most of the features of the mono runtime, some minor APIs don't have finished and fully tested wrappers. Those unfinished APIs are usually niche(eg. advanced debugging, access to pro filer(data about performance), access to assembly Metadata, dynamic code generation) and always have an alternative unsafe bindings that can be used.
While `wrapped_mono` already has support for most of the features of the mono runtime, some minor APIs don't have finished and fully tested wrappers. Those unfinished APIs are usually niche (eg. advanced debugging, access to profiler (data about performance), access to assembly Metadata, dynamic code generation) and always have an alternative unsafe bindings that can be used.

## Safety checks
This API tries to follow rusts rules about safety and error handling as much as possible, but some checks are unfinished and can't catch all potential problems, or are not done, since they would introduce a serious performance hit, while only covering a niche case that is clearly marked in documentation. A good example of this kind of case is accessing an object after deleting the domain it is in or shutting down the runtime. Most of possible errors are checked for, and those checks can be disabled to speed up `wrapped_mono` even more, but this is not advised. Cost of those checks is usually negligible(less than 1% of the cost of calling a function), and they prevent a lot of potential mistakes.
This API tries to follow Rust's rules about safety and error handling as much as possible, but some checks are unfinished and can't catch all potential problems, or are not done, since they would introduce a serious performance hit, while only covering a niche case that is clearly marked in documentation. A good example of this kind of case is accessing an object after deleting the domain it is in or shutting down the runtime. Most of possible errors are checked for, and those checks can be disabled to speed up `wrapped_mono` even more, but this is not advised.
The cost of these checks is usually negligible (less than 1% of the cost of calling a function), and they prevent a lot of potential mistakes.

# Supported platforms
`wrapped_mono` supports Linux(tested on Fedora 37, Debian Bullseye and Arch), and Windows(tested on Windows 10). Other platforms, such as MacOS are not officially supported, but can be easily added by changing the `build.rs` to include platform-specific link flags.
`wrapped_mono` supports Linux (tested on Fedora 37, Debian Bullseye and Arch), and Windows (tested on Windows 10).
Other platforms, such as MacOS are not officially supported, but can be easily added by changing the `build.rs` to include platform-specific link flags.
# Dependencies
## External
* Mono library - the library this crate wraps around. Can be downloaded <a href="https://www.mono-project.com/download/stable/">here</a>. When installing, use default instructions from the website. Only needed on the system crate is compiled on (linked statically).
* Mono library - the library this crate wraps around. Can be downloaded <a href="https://www.mono-project.com/download/stable/">here</a>. When installing, use default instructions from the website. Only needed on the system the crate is compiled on (linked statically).
## Rust
* `wrapped_mono_macros` - sub crate containing custom macros used by wrapped_mono. Separate, because proc\_macro's must be separate crates.
* `document-features` - used for documentation
Expand All @@ -28,7 +37,7 @@ This API tries to follow rusts rules about safety and error handling as much as
- [X] Raise and catch exceptions
- [X] Create n-dimensional Arrays, read and set their elements at any indices.
- [X] Pass basic types(integers, chars, floating-point numbers, pointers, arrays, strings, exceptions,objects,types,delegates) between managed and unmanaged code
- [X] Invoke deleagtes
- [X] Invoke delegates
- [X] Implement simple traits to pass any type between rust and C#/F# code!
- [X] Automatically implement interop helper traits for any structs made from other types implementing the helper traits.
- [X] Pass back and forth simple rust enums.
Expand All @@ -48,7 +57,7 @@ This API tries to follow rusts rules about safety and error handling as much as
```rust
use wrapped_mono::*;
fn main(){
// Initialise the runtime with default version(`None`), and root domian named "main_domain"
// Initialise the runtime with default version(`None`), and root domain named "main_domain"
let domain = jit::init("main_domain",None);

// Load assembly "SomeAssembly.dll"
Expand Down Expand Up @@ -82,7 +91,7 @@ fn main(){
}
// Replace a method with "[MethodImplAttribute(MethodImplOptions.InternalCall)]" atribute with a rust function
add_internal_call!("SomeClass::SqrtInternalCall",sqrt);
// This supports all types with `InteropRecive` trait
// This supports all types with `InteropReceive` trait
#[invokable]
fn avg(input:Array<Dim1D,f32>)->f32{
let mut avg = 0.0;
Expand Down
18 changes: 9 additions & 9 deletions examples/interop/custom_struct_interop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace Vec3Namespace{
}
}
*/
//this types layout does not differ on managed and unmanged side.
#[derive(InteropSend,InteropRecive)]
//this types layout does not differ on managed and unmanaged side.
#[derive(InteropSend,InteropReceive)]
struct Vec3{
x:f32,
y:f32,
Expand Down Expand Up @@ -58,31 +58,31 @@ struct SomeObjectClass{
obj:Object,
}

//Reciving`SomeObjectClass` as a non-nullable!
impl InteropRecive for SomeObjectClass{
// Receiving `SomeObjectClass` as a non-nullable!
impl InteropReceive for SomeObjectClass{
type SourceType = *mut MonoObject;
fn get_rust_rep(src:Self::SourceType)->Self{
return unsafe{Object::from_ptr(src)}.expect("Got null on a non nullable type!");
}
}
//Reciving `SomeObjectClass` as a nullable!
impl InteropRecive for Option<SomeObjectClass>{
// Receiving `SomeObjectClass` as a nullable!
impl InteropReceive for Option<SomeObjectClass>{
type SourceType = Option<Object>;
fn get_rust_rep(src:Self::SourceType)->Self{
return src;
}
}
//Sending` SomeObjectClass` as a non-nullable!
// Sending `SomeObjectClass` as a non-nullable!
impl InteropSend for SomeObjectClass{
type TargetType = *mut MonoObject;
fn get_mono_rep(src:Self)->Self::TargetType{
return src.get_ptr();
}
}
use core::ptr::null_mut;
//Sending `SomeObjectClass` as a nullable!
// Sending `SomeObjectClass` as a nullable!
impl InteropSend for Option<SomeObjectClass>{
type Targetype = *mut MonoObject;
type TargetType = *mut MonoObject;
fn get_mono_rep(src:Self)->Self::TargetType{
match src{
Some(src)=>return src.get_ptr(),
Expand Down
59 changes: 30 additions & 29 deletions src/array.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::binds::MonoObject;
use crate::gc::{gc_unsafe_enter, gc_unsafe_exit, GCHandle};
use crate::interop::{InteropClass, InteropRecive, InteropSend};
use crate::interop::{InteropClass, InteropReceive, InteropSend};
use crate::{dimensions::DimensionTrait, domain::Domain, Class, Object, ObjectTrait};
use core::marker::PhantomData;
use core::ptr::null_mut;
Expand All @@ -9,25 +9,26 @@ use std::ops::Index;
// Documentation finished.
/// Safe, rust representation of `MonoArray` (a reference to a managed array).
/// # Nullable support
/// [`Array<T>`] is non-nullable on default and will panic when null passed as argument form managed code. For nullable support use [`Option<Array<T>>`].
/// [`Array<Dim, T>`] is non-nullable on default and will panic when null passed as argument form managed code.
/// For nullable support use [`Option<Array<Dim, T>>`].
/*
why is there a weird constraint "where [();DIMENSIONS as usize]:Copy" in array type? It guarantees that Dimensions is higher than 0 and size array is larger than 0,
so Array<DIMENSIONS,T> can exist.
*/
pub struct Array<Dim: DimensionTrait, T: InteropSend + InteropRecive + InteropClass>
pub struct Array<Dim: DimensionTrait, T: InteropSend + InteropReceive + InteropClass>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy,
<Dim::Lengths as std::ops::Index<usize>>::Output: BorrowMut<usize>,
<<Dim as DimensionTrait>::Lengths as Index<usize>>::Output: Sized + Into<usize> + Copy,
{
#[cfg(not(feature = "referneced_objects"))]
#[cfg(not(feature = "referenced_objects"))]
arr_ptr: *mut crate::binds::MonoArray,
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
handle: GCHandle,
pd: PhantomData<T>,
lengths: Dim::Lengths,
}
impl<T: InteropSend + InteropRecive + InteropClass, Dim: DimensionTrait> Array<Dim, T>
impl<T: InteropSend + InteropReceive + InteropClass, Dim: DimensionTrait> Array<Dim, T>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy + Copy,
<Dim::Lengths as std::ops::Index<usize>>::Output: BorrowMut<usize>,
Expand Down Expand Up @@ -79,7 +80,7 @@ where
/// ```
pub fn get(&self, indices: Dim::Lengths) -> T {
let index = self.get_index(indices);
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_possible_wrap)]
Expand All @@ -91,7 +92,7 @@ where
) as *const T::SourceType)
};
let rr = T::get_rust_rep(src);
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
rr
}
Expand Down Expand Up @@ -124,7 +125,7 @@ where
T: InteropSend,
{
let index = self.get_index(indices);
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
let ptr = unsafe {
#[allow(clippy::cast_possible_truncation)]
Expand All @@ -141,7 +142,7 @@ where
unsafe { (*ptr.cast()) = value };
}

#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
}

Expand All @@ -163,10 +164,10 @@ where
/// ```
#[must_use]
pub fn len(&self) -> usize {
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
let len = unsafe { crate::binds::mono_array_length(self.get_ptr().cast()) };
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
len
}
Expand All @@ -187,11 +188,11 @@ where
/// |self| &Array | array to cast to object|
#[must_use]
pub fn to_object(&self) -> Object {
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
let res = unsafe { Object::from_ptr(self.get_ptr().cast()) }
.expect("Could not create object from array!");
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
res
}
Expand All @@ -215,7 +216,7 @@ where
pub fn new(domain: &Domain, size: &Dim::Lengths) -> Self {
#[allow(clippy::cast_possible_truncation)]
let class = <T as InteropClass>::get_mono_class().get_array_class(Dim::DIMENSIONS as u32);
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
let arr = unsafe {
Self::from_ptr(
Expand All @@ -229,7 +230,7 @@ where
)
}
.expect("could not create a new array!");
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
arr
}
Expand All @@ -240,12 +241,12 @@ where
/// |self|&Array|Array to clone|
#[must_use]
pub fn clone_managed_array(&self) -> Self {
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let marker = gc_unsafe_enter();
let res =
unsafe { Self::from_ptr(crate::binds::mono_array_clone(self.get_ptr().cast()).cast()) }
.expect("coud not create copy of an array!");
#[cfg(feature = "referneced_objects")]
.expect("could not create copy of an array!");
#[cfg(feature = "referenced_objects")]
gc_unsafe_exit(marker);
res
}
Expand All @@ -263,11 +264,11 @@ where
/// |Name |Type |Description|
/// |-------|-------|------|
/// |self|&Array|Array to get size of|
pub fn get_lenghts(&self) -> Dim::Lengths {
pub fn get_lengths(&self) -> Dim::Lengths {
self.lengths
}
}
impl<Dim: DimensionTrait, T: InteropSend + InteropRecive + InteropClass> InteropClass
impl<Dim: DimensionTrait, T: InteropSend + InteropReceive + InteropClass> InteropClass
for Array<Dim, T>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy,
Expand All @@ -278,7 +279,7 @@ where
Self::get_class()
}
}
impl<Dim: DimensionTrait, T: InteropSend + InteropRecive + InteropClass> ObjectTrait
impl<Dim: DimensionTrait, T: InteropSend + InteropReceive + InteropClass> ObjectTrait
for Array<Dim, T>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy,
Expand All @@ -287,21 +288,21 @@ where
{
#[must_use]
fn get_ptr(&self) -> *mut crate::binds::MonoObject {
#[cfg(not(feature = "referneced_objects"))]
#[cfg(not(feature = "referenced_objects"))]
return self.arr_ptr.cast();
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
return self.handle.get_target();
}
#[must_use]
unsafe fn from_ptr_unchecked(ptr: *mut MonoObject) -> Self {
use crate::Method;
#[cfg(not(feature = "referneced_objects"))]
#[cfg(not(feature = "referenced_objects"))]
let mut res = Self {
arr_ptr: ptr.cast(),
pd: PhantomData,
lengths: Dim::zeroed(),
};
#[cfg(feature = "referneced_objects")]
#[cfg(feature = "referenced_objects")]
let mut res = Self {
handle: GCHandle::create_default(ptr.cast()),
pd: PhantomData,
Expand Down Expand Up @@ -343,7 +344,7 @@ where
res
}
}
impl<Dim: DimensionTrait, T: InteropSend + InteropRecive + InteropClass> Clone for Array<Dim, T>
impl<Dim: DimensionTrait, T: InteropSend + InteropReceive + InteropClass> Clone for Array<Dim, T>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy,
<Dim::Lengths as std::ops::Index<usize>>::Output: BorrowMut<usize>,
Expand All @@ -353,7 +354,7 @@ where
unsafe { Self::from_ptr(self.get_ptr().cast()).unwrap() } //If object exists then it can't be null
}
}
impl<Dim: DimensionTrait, T: InteropSend + InteropRecive + InteropClass, O: ObjectTrait>
impl<Dim: DimensionTrait, T: InteropSend + InteropReceive + InteropClass, O: ObjectTrait>
PartialEq<O> for Array<Dim, T>
where
Dim::Lengths: std::ops::IndexMut<usize> + BorrowMut<[usize]> + Copy,
Expand All @@ -366,7 +367,7 @@ where
}
use crate::dimensions::Dim1D;

impl<T: InteropSend + InteropRecive + InteropClass + Clone> From<&[T]> for Array<Dim1D, T> {
impl<T: InteropSend + InteropReceive + InteropClass + Clone> From<&[T]> for Array<Dim1D, T> {
fn from(src: &[T]) -> Self {
let size = src.len();
let dom = Domain::get_current().expect("Can't create arrays before JIT starts!");
Expand Down
Loading