0

I wanted to use Nim inside my C# code, and I used this https://www.euantorano.co.uk/posts/nim-with-c-sharp-net/ example with no issue. Okay I said, lets get a string proc working.

proc HelloNim*(a, b: cint): cstring {.cdecl, exportc, dynlib.} =
  return "bob".cstring

then in C#

using System;
using System.Runtime.InteropServices;

namespace Miasma
{
    class Program
    {
        static void Main(string[] args)
        {
            string b = ""+HelloNim(1,2);
            Console.WriteLine(b);
        }

        [DllImport("HelloNim.dll")]
        public static extern void NimMain();

        [DllImport("HelloNim.dll")]
        public static extern string HelloNim(int a, int b);
    }
}

This does not error, but it doesn't print out any string either. Lorks. What has gone wrong?

SlightlyKosumi
  • 701
  • 2
  • 8
  • 24

1 Answers1

1

What goes wrong here is that C# thinks it is getting a managed string back, and tries to free it. But the string it got is not in its chunk of memory as it was allocated by the Nim DLL, so it crashes (if you look at the exit code of dotnet it should be 139 which means SIGSEGV, at least on Linux). To avoid this you can define the procedure as returning an IntPtr and then marshal that to a C# string by using Marshal.PtrToStringUTF8(returnValue) Another thing to note is that for strings (or anything else that uses the GC in Nim, and to initialise global variables) to work you need to call NimMain() before anything else. Combining this your code can be rewritten as:

using System;
using System.Runtime.InteropServices;

namespace Miasma
{
    class Program
    {
        static void Main(string[] args)
        {
            NimMain();
            IntPtr strPtr = HelloNim(1,2);
            var str = Marshal.PtrToStringUTF8(strPtr);
            Console.WriteLine(str);
        }

        [DllImport("HelloNim.dll")]
        public static extern void NimMain();

        [DllImport("HelloNim.dll")]
        public static extern IntPtr HelloNim(int a, int b);
    }
}

Also keep in mind that the pointer you receive points to Nim managed memory, so every time you call something from the Nim DLL there is a chance that it will be GC'ed. If you want to keep it in your C# code you should immediately copy it over (not sure if Marshall does this or not).

PMunch
  • 1,525
  • 11
  • 20
  • Thanks. I naively assumed that if a cstring option existed, it would be accepted straight away as a real string in C#. I'm rewriting some old C# and pushing some Nim inside it here and there - I hoped that a big 50000 line parse would be faster with Nim, but maybe Marshal will attack me. – SlightlyKosumi Mar 02 '20 at 15:10
  • 1
    Well the `cstring` is a native C string, ie. a `char *`. The problem is that C# uses managed memory and doesn't easily support these kinds of "unsafe" operations. It should be possible though to create a buffer in C# and populate it in Nim if you are afraid of copying things. But as I said I'm not 100% sure what marshal does, it might just work like some kind of casting. – PMunch Mar 03 '20 at 08:20
  • I've used the Marshall stuff for image processing before. Never looked very closely at what it was doing under the hood though! I will find out now. – SlightlyKosumi Mar 03 '20 at 08:51