DEV Community

Calling Rust from C#

Jeremy Mill on October 02, 2017

Intro This is a guide for creating a Rust DLL and calling it from C#. We will cover native return values as well as structs. This guide ...
Collapse
 
lmtr0 profile image
lmtr0

I don't see strings as problems, i mean it was this easy to convert a c string to a rust string

#[no_mangle]
pub extern fn printc(c_buf: *const c_char)
{
    let c_str: &CStr = unsafe { CStr::from_ptr(c_buf) };
    let str_slice: &str = c_str.to_str().unwrap();
    let str_buf: String = str_slice.to_owned();  // if necessary
    println!("string is {}", str_slice);
}
Enter fullscreen mode Exit fullscreen mode

then in c# it would be something like

[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)]
public static extern void printc(string str);
Enter fullscreen mode Exit fullscreen mode

the result is:

string is Hello wolrd
Enter fullscreen mode Exit fullscreen mode

The rust code is thanks to this stackoverflow question

Collapse
 
kaije5 profile image
Kaije Henstra

Yes but rusties don't like unsafe code ;)

Collapse
 
someperson2 profile image
SomePerson2 • Edited

Yes but rusties don't like unsafe code ;)

That is wholly and completely untrue. We are not afraid of unsafe. There are times it is absolutely needed, and appropriate to use, and there's nothing wrong with that.

It just simply is, that a person should carefully consider whether they really need it, and if what they are doing is achievable perfectly in safe code; if they need unsafe, they should be careful to document and uphold the invariants in order to make sure things are ok and properly done.

If a person is doing ffi, it's pretty much a given that you are going to need unsafe somewhere. It comes with the territory.

Collapse
 
torkleyy profile image
Thomas Schaller

Hey, thanks for the post. As you noted, the string handling is not ideal. I suggest you allocate a Box / Vec for the string, pass it to C#. From there, you just copy it into its native String type and call a Rust-defined free_string function. For users who are unexperienced with memory management / unsafety, the additional overhead seems justified for me.

Another minor I've noticed is the unideomatic return in one function (can be skipped at the end) ;)

Collapse
 
living_syn profile image
Jeremy Mill

Hey, thanks for the reply. I have a lot more experience with this now than when I wrote this. It definitely needs to be updated, i'll try and get around to it sooner than later

Collapse
 
kelsonball profile image
Kelson Ball

Any links on how to do that?

Collapse
 
torkleyy profile image
Thomas Schaller

Don't have a link at hand, but I'd just return the char pointer directly (instead of storing it in a global) and ask for it again in the free function.

Collapse
 
dmitryvk profile image
Kalyanov Dmitry

You should annotate the structs (on the C# side) with [StructLayout(LayoutKind.Sequential)] - otherwise CLR is free to reorder fields or add/remove padding which will break ABI.

Collapse
 
living_syn profile image
Jeremy Mill

You're totally correct. I have that in the production code this is modeled after and I forgot it. I'll add it in, thanks!

Collapse
 
eivbsmed profile image
Eivind Brandth Smedseng

I followed you article to bind up some common code. But got a problem whit bool always being true. Any thing special whit exposing a function using bool??

Collapse
 
living_syn profile image
Jeremy Mill • Edited

Yes, I had issues with bools as well, I didn't cover it in the article because it's weird. What I ended up doing was setting them up in c# as a byte and evaluating if it was a 1 or a 0, which SUCKS, but is totally doable. I tried getting a bunch of the marshal-as methods to work, but as of yet, none of them have. if you figure it out, let me know!

example:

rust:

#[repr(C)]
pub struct test {
 pub isbool: bool,
}

c#:

[StructLayout(LayoutKind.Sequential)]
public struct test 
{
    public byte isbool;
}
Collapse
 
eivbsmed profile image
Eivind Brandth Smedseng

Thanks, it worked. Had the solution of sending and revising a u8. But whit the byte at least i don't have to change anything on the rust side.

Some suggested to use types from libc like c_bool, but i need to get abit better at rust. Have just started out. I'll let you know if i find a good solution

Thread Thread
 
living_syn profile image
Jeremy Mill

I've been doing rust FFI at work for a few more months since I wrote this post. There's some things that I'll probably need to go back and update when I get the time. c_bool is a possible solution, there's also some shorthand in c# that may end up working, but I'll make sure to let you know if/when I get it working!

Thread Thread
 
eivbsmed profile image
Eivind Brandth Smedseng

Thanks :-)

Collapse
 
yaketycats profile image
Paul

C# bools are Win32 BOOLs are 32-bit signed integers, for historical reasons.

Thread Thread
 
living_syn profile image
Jeremy Mill

Still, Marshall as book "should" work, correct?

Thread Thread
 
yaketycats profile image
Paul

bool on the Rust side but byte on the C# side, or (better) make a user-defined struct on the C# side, e.g. "ByteBool", that holds a single byte value and implements the conversions to/from System.Boolean.

[StructLayout(LayoutKind.Sequential)]
public struct test
{
public ByteBool isbool;
}

Collapse
 
pepyakin profile image
Sergey Pepyakin

Did you tried to use thread_local! instead of static mut?

Collapse
 
someperson2 profile image
SomePerson2

Yeah. And static mut is pretty much almost always unneeded, since there is a safe way to do this, with interior mutability. (I'm coming from the future, but there are apis like OnceLock, LazyLock, etc)

Collapse
 
living_syn profile image
Jeremy Mill

Nope, I'll look into it though. Thanks!

Collapse
 
peshor profile image
PeShor

Hey, hope its not to late for a Question. I try your Tutorial, but i have a Problem to call a second function. It throw that he cant find an entryPoint for the second Function. Have you any Idea how i can call an another function in the same dll? Or have you maybe an Exemple?

Collapse
 
pieflavor profile image
Adam Spofford

The explicit Int32ing makes no sense. You're not being 'explicit' about the type - int is 100% always shorthand for Int32. It's as meaningless as writing Object instead of object.

Collapse
 
mateuszlewko profile image
Mateusz Lewko

What if we use fixed size char (byte) array? Would that make passing string simpler? Do you know how to do that?

Collapse
 
living_syn profile image
Jeremy Mill

I haven't done it yet, though I can think of no reason it wouldn't work. I'll see if I can throw together an example sometime today

Collapse
 
ryanhossain9797 profile image
Raiyan

Not sure if I should be digging up something this old, but here goes.

I wanted to send Dictionaries and Lists back and forth. What would be the best way to do something like that?

Collapse
 
living_syn profile image
Jeremy Mill

hey! Sorry I don't check this very often. I'd recommend serializing it and deserializing it. I doubt there's a good way to do it over FFI