DotNet Platform

A minimal .NET platform

Full Code

main.roc:

app [main] { platform: platform "./platform/main.roc" }

main = "Hi from roc! (in a .NET platform) 🔥🦅🔥"

platform/main.roc:

platform "dotnetplatform"
    requires {} { main : Str }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Str
mainForHost = main

platform/DotNetRocPlatform.csproj:

<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <PublishAot>true</PublishAot>
    <ImplicitUsings>true</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <None Update="interop.*">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </None>
  </ItemGroup>
</Project>

platform/Program.cs:

using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using static System.Console;
using static Platform;

NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), CustomResolver);

WriteLine("Hello from .NET!");

MainFromRoc(out var rocStr);

WriteLine(rocStr);

//Load native library even when the name doesn't exactly match the name of the library defined in `LibraryImport`
//eg: `interop.so.1.0` instead of `interop.so`
static IntPtr CustomResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
    var libFile = Directory
        .EnumerateFiles(Directory.GetCurrentDirectory())
        .FirstOrDefault(e => e.Contains(libraryName));

    if (libFile != null)
    {
        return NativeLibrary.Load(libFile, assembly, searchPath);
    }

    return IntPtr.Zero;
}

public static partial class Platform
{
    [LibraryImport("interop", EntryPoint = "roc__mainForHost_1_exposed_generic")]
    internal static partial void MainFromRoc(out RocStr rocStr);
}

public unsafe struct RocStr
{
    public byte* Bytes;
    public UIntPtr Len;
    public UIntPtr Capacity;

    public override string ToString() => Encoding.UTF8.GetString(Bytes, (int)Len.ToUInt32());

    public static implicit operator string(RocStr rocStr) => rocStr.ToString();
}

Build & Run

  1. Build the roc app that is using the dotnet platform:
$ cd examples/DotNetPlatform/
$ roc build main.roc --lib --output ./platform/interop

use arch -arm64 if you are running in a Apple Silicon mac.

This will produce a shared library file that we'll be able to import from a .NET context.

To run:

$ cd platform
$ dotnet run

This should print "Hello from .NET".

Build & Run Binary

If you want to build a binary for the app using native AOT:

  1. Publish the dotnet app
$ dotnet publish -c Release

use arch -arm64 if you are running in a Apple Silicon mac.

  1. cd into the into the publish folder and run the binary:
$ ./DotNetRocPlatform