runtime: Dependency on System.Runtime.InteropServices.RuntimeInformation causes runtime error for apps targeting net451 on *nix systems (i.e. running on Mono)

Scenario

  1. Running a net451 app depending on System.Runtime.InteropServices.RuntimeInformation on OSX/Ubuntu will produce the following exception at runtime:
 dotnet -v run -f net451                                                                                                                                                                               1 ↵
Telemetry is: Enabled
Project RuntimeServiceOnMono (.NETFramework,Version=v4.5.1) was previously compiled. Skipping compilation.
Running /usr/local/bin/mono --debug /Users/jtluo/Documents/workspace/juntaoluo/tp/RuntimeServiceOnMono/bin/Debug/net451/osx.10.11-x64/RuntimeServiceOnMono.exe
Process ID: 29130

Unhandled Exception:
System.DllNotFoundException: System.Native
  at (wrapper managed-to-native) Interop/Sys:GetUnixNamePrivate ()
  at Interop+Sys.GetUnixName () [0x00000] in <filename unknown>:0
  at System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (OSPlatform osPlatform) [0x00000] in <filename unknown>:0
  at ConsoleApplication.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0
[ERROR] FATAL UNHANDLED EXCEPTION: System.DllNotFoundException: System.Native
  at (wrapper managed-to-native) Interop/Sys:GetUnixNamePrivate ()
  at Interop+Sys.GetUnixName () [0x00000] in <filename unknown>:0
  at System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform (OSPlatform osPlatform) [0x00000] in <filename unknown>:0
  at ConsoleApplication.Program.Main (System.String[] args) [0x00000] in <filename unknown>:0

Example

See repro at https://github.com/JunTaoLuo/RuntimeServiceOnMono. To run the sample, run dotnet restore and dotnet run -f net451

I understand Mono is not a scenario that’s actively being developed for but we should not be causing a exception like this especially since we intend to use the InteropServices APIs in often used ASP.NET packages like Logging.

Currently this means that most of our samples and apps cannot run on Mono (full CLR on *nix).

Environment

dotnet --version:

Microsoft .NET Core Shared Framework Host

  Version  : 1.0.1-rc3-004312-00
  Build    : 1db6c07638a70a621b312e78d4dc9fb31a530f2f

mono -V:

Mono JIT compiler version 4.0.5 ((detached/1d8d582 Tue Oct 20 15:15:33 EDT 2015)
Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
    TLS:           normal
    SIGSEGV:       altstack
    Notification:  kqueue
    Architecture:  x86
    Disabled:      none
    Misc:          softdebug
    LLVM:          yes(3.6.0svn-mono-(detached/a173357)
    GC:            sgen

cc @BrennanConroy @muratg @Eilon

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 95 (90 by maintainers)

Most upvoted comments

People would like to use Asp.Net Core even where .NET Core cannot run. I would not want to be that person held off from using it to its full potential just because of this.

Maybe for some things. But that abandons the notion that Mono is an alternate implementation of some/most of net46 – which is its entire purpose today.

If you abandon that design, you are leaving behind almost all of Mono’s consumers.

@jkotas I thought the point of “netstandard” was that it would allow people to build different runtimes targeting the same standard.

If Mono is an implementation of “net46” how can it not be expected to be compatible with netstandard libraries compiled against “net46”?

What’s the plan to address this? We just discovered that this package slipped into the desktop C# compiler causing it to no longer run on Mono.

cc @jaredpar and @agocke

For anyone looking for a workaround (on macOS/Darwin at least), here are some notes on getting things working on OS X (and in theory they should work on Ubuntu with little or no modification, but I haven’t tested):

  1. Find your loadable native images (.dylib on macOS/Darwin, .so on Linux). The location depends on what your dev/packaging platform is.

    1. If you’re building and running on the same platform, you can likely find them at /usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0.
    2. If you’re cross-compiling, or want to be more generalized, you can find them for each installed platform using this command:
    ls ~/.nuget/packages/runtime.*.*.*.runtime.native.System/1.0.1*/runtimes/*/native
    
  2. Make your native image files loadable. The loader function, dlopen, is going to probe the filesystem for a file prefixed with lib. Even if it knows where to find your images, without the prefix, dlopen will not find them. You have to give dlopen a little help by choosing one of methods below.

    Note: If you want to watch this process happen, just prefix the dotnet command like this:

      MONO_LOG_LEVEL=debug dotnet run
    

    You’ll see all the probing going one, like:

    Mono: DllImport error loading library '/path/to/your/src/project/bin/Debug/net451/osx.10.11-x64/libSystem.Native.dylib': 'dlopen(/path/to/your/src/project/bin/Debug/net451/osx.10.11-x64/libSystem.Native.dylib, 9): image not found'.
    
    1. Rename/Copy/Symlink from System.*.{dylib|so} to libSystem.*.{dylib|so}.
    2. Map the expected name to the actual name. This adds an rpath that lets dlopen see lib{FOO} even though the filesystem only has {FOO}:
    sudo install_name_tool -add_rpath /usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0/libSystem.Native.dylib /usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0/System.Native.dylib
    

    Notes: On Linux, looks like you’d probably use patchelf instead. For either option, you can automate this process for all your files with a little awk fu:

    sudo ls /usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0/System*.dylib | awk '{split($1,arr,"/");name=arr[length(arr)];i=index($1, name);path=substr($1, 0, i - 1);newName=path "lib" name;cmd="sudo install_name_tool -add_rpath " newName " "  $1;cmd | getline; print}'
    
  3. [Only if you chose 2.i] Put your native images in dlopen’s search path. You have a few choices, and depends on what your ultimate goals are.

    1. Copy the respective libSystem.Native.{dylib|so} files from Step 2 to bin/{config}/{framework}/{platform}/libSystem.Native.{dylib|so}. Nothing fancy/brute force. Do this for each project. If someone does a clean clone of a repo, or you’re doing stuff in CI, you should really add a pre-compile task to get this done. Be mindful of any tooling/automation that might periodically rm -Rf bin/Debug/*.
    2. Set LD_LIBRARY_PATH to one or more paths containing your native images. Normally on macOS/Darwin this would be DYLD_LIBRARY_PATH and it’s ilk (see man DYLD_LIBRARY_PATH for the details), but seems Mono fine with either one. I don’t know if they ever plan to change that, so YMMV.

Tip: Make sure you are running mono in 64-bit mode, otherwise it won’t be able to load the native images. Since there isn’t a way to pass this flag via dotnet CLI, you use the ENV variable instead. This command will add it to your shell startup script:

   echo export MONO_ENV_OPTIONS=--arch=64 >> ~/.zshrc` # or '~.bashrc' if you use bash)

Notes

Regardless of your choice in Step 3, only libuv.{dylib|so} will make into the published folder. For files like libSystem.Native you’ll have to copy that into the publish folder yourself…unless you already installed the .NET Core on the machine you’re deploying too. In that case, you could wrap your executable in a simple one-line shell script that looked something like: LD_LIBRARY_PATH=/usr/local/share/dotnet/shared/Microsoft.NETCore.App/1.0.0 mono bin/Debug/net451/osx.10.11-x64/my-app.exe. If you were to symlink Microsoft.NETCore.App/Current to Microsoft.NETCore.App/1.0.0 you could reasonably easily update without touching your script. I’m not entirely sure what the best practice/intent is concerning /usr/local/share/dotnet paths versus ~/.nuget paths.

When the above doesn’t work, I’ve also found it helpful to run brew update && brew upgrade and if you get errors and warnings, run brew doctor to see what needs to get cleaned up. You might also need to run brew prune to fix broken symlinks in /usr/local before brew upgrade will succeed.

Instead I’d wrap the actual code from corefx.