#Statically linking C# library into Rust crate

1 messages · Page 1 of 1 (latest)

warped flax
#

I have a C# library that exports a function which is then called from Rust.
The problem: The System library is not being linked properly, leading to lots of errors like rust-lld: error: undefined symbol: SystemNative_GetPwUidR.
I've also read that NativeAOT does not support statically linking officially, so I don't really know what to do now.
I think these are my options:

  • compile NativeAOT myself and then statically link
  • somehow make the generated .a file not require the system library (and load it at runtime)
  • not use NativeAOT at all and instead embed a "regular" dotnet runtime into my crate
  • build dynamically and then use some sort of self extracting archive to contain the dll/so

note: my experience with csharp/dotnet is really limited.
i just kind of want to make this work.
if there is any information i missed to post, please ask me for it.
also idk if this is the right place to ask this? idk, would a dotnet forum be better?

This is my csproj:

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<OutputType>library</OutputType>
<PublishAot>true</PublishAot>
<NativeLib>Static</NativeLib>
</PropertyGroup>
...

This is part of my build.rs:

let out_dir = env::var_os("OUT_DIR")?;

let exit_code = Command::new("dotnet")
.arg("publish")
.arg("csharp")
.arg("-c")
.arg("Release")
.arg("-r")
.arg("linux-x64)
.arg("-o")
.arg(&out_dir)
.arg("-p:NativeLib=Static")
.spawn()?
.wait()?;

// then:
println!("cargo::rustc-link-search=native={}", out_dir.display());
println!("cargo::rustc-link-lib=static=MyLibraryName");

Build output:

error: linking with `cc` failed: exit status: 1
rust-lld: error: undefined symbol: RhpNewPtrArrayFast
>>> referenced by MyLibraryName.o:(S_P_CoreLib_System_UnhandledExceptionEventHandler__InvokeObjectArrayThunk) in archive
 target/debug/deps/libunderanalyzer-ffbcf35c47c58571.rlib
>>> referenced 204 more times

and lots more.

white arrow
#

Could you share the block that you use in your rust code to link your C# functions? the extern block

warped flax
#
unsafe extern "C" {
  fn decompile_to_string(game_context: *const GameContext, code: *const Code) -> ReturnValue;
}

exported via

[UnmanagedCallersOnly(EntryPoint = "decompile_to_string")]
  static unsafe ReturnValue DecompileToString(GameContext* gameContext, GMCode* code);
white arrow
#

I'm not super familiar with Rust FFI but here's a few things that may be worth investigating:

  • If you are targetting Windows, the extern block should probably use the "system" ABI
  • the linker rust-lld, may have some issues, it could be worth trying to use other linker flavors, for example msvc or lld-link
  • maybe it's worth trying to install LLVM's lld and see if it differs from rust-lld
  • in your extern block, shouldn't you specify a lib name via #[link(name = "your_lib")]?
bright mirage
#

If you are targetting Windows, the extern block should probably use the "system" ABI
IIRC this depends on what part of windows you're targeting. I don't know whether the native bits of .NET use that ABI

#

in your extern block, shouldn't you specify a lib name via #[link(name = "your_lib")]?
You can also pass the library as a compiler flag

#

Or rely on the fact some other crate in your crate graph already did that

warped flax
# white arrow I'm not _super_ familiar with Rust FFI but here's a few things that may be worth...
  • i'm only doing linux support rn. if windows becomes an issue, i can try with cfg_attr
  • the problem isnt the linker (at least not right now, pretty sure) because i dont even explicitly link to c# system libraries. i would need to figure out how to get a libSystem.a or similar before that
  • .
  • oh yeah thanks for the heads up. interestingly, it found it even then before (i passed it with cargo::rustc-link-lib=static=.
warped flax
#

is it possible to turn dynamic libraries into static ones??? maybe that could solve the issue idk

bright mirage
#

If you had the source you could compile as a static library, and AFAIK it's possible but rare to make a library that can do both things

#

But if you have one kind of lib, it doesn't necessarily contain the information you'd need to turn it into the other kind of lib

#

(and even if it did, it might do something that relies on being the kind of lib it was before)

warped flax
#

uhhhh

#

so dynamic linking it is?

white arrow
# bright mirage Generally no

kinda invested in this subject too now — why isn't it possible for a dynamic library to also be used statically? Shouldn't it be possible to just include the lib in the final artifact and instead of calling the system to do dynamic linking, just link to the lib contained within the artifact?

bright mirage
#

The short version is that creating a dynamic library throws away some of the information required to link statically

#

Once that information is gone, it's gone. It's not there anymore. You can't get it back.

#

Now, you can theoretically create a dynamic library that straight up contains a static library, yes

#

But at that point, it would be easier to just create two separate files, a static and a dynamic library, and then distribute both

#

And it still doesn't let you derive the static library from the dynamic one.

warped flax
#

alright i went with dynamic linking now. i embed the .so into the binary and then create a tempfile at runtime