Haskell call from C #

I just spent the last week or so figuring out how to execute C ++ code with C # as part of my day job. It took us endlessly to figure this out, but the final solution is pretty simple.

Now I'm curious ... How hard would it be to name Haskell with C #? (Note: this is a Haskell call from C #, not the other way around. Therefore, C # is the main executable.)

If it is really difficult, I will not worry. But if it is easy enough, I may have to play with him ...

Basically, we wrote C ++ code. On Windows, it compiles to a DLL, and on Linux it compiles to a shared object ( *.so ). Then on the C # side, you do DllImport and write some manual memory management code if you are trying to pass something non-trivial. (E.g. arrays, strings, etc.)

I know that the GHC should support the creation of shared libraries on both platforms, but I'm not sure about the technical details. What is the syntax for exporting material, and the caller must do something special to initialize the DLL first?

Specifically: suppose that there is a function foobar :: FilePath -> IO Int32 . Can someone throw together a small sketch drawing:

  • What Haskell ads I need to write to expose this to the outside world.
  • How to tell GHC to create a separate standalone DLL / SO file.
  • All the caller has to do, besides the usual binding process of the foobar itself.

I'm not too worried about the actual syntax for the C # side; I think I was more or less puzzled by this.

PS I looked briefly at hs-dotnet , but it looks like Windows. (Ie, it won’t work with Mono, so it won’t work on Linux.)

+40
c # haskell ffi
May 17 '13 at 18:27
source share
2 answers

For both languages, you can basically pretend to be trying to interact with C code.

This is a complex topic, so instead of trying to explain all this, I will focus on making a simple example that you can use using the resources listed below.

  • First you need to write wrappers for your Haskell functions that use types from Foreign.C.* Modules instead of the usual haskell types. CInt instead of Int , CString instead of String , etc. This is the most difficult step, especially when you have to deal with custom types.

    You also need to write foreign export declarations for these functions using the ForeignFunctionInterface extension.

     {-# LANGUAGE ForeignFunctionInterface #-} module Foo where import Foreign.C.String import Foreign.C.Types foreign export ccall foo :: CString -> IO CInt foo :: CString -> IO CInt foo c_str = do str <- peekCString c_str result <- hs_foo str return $ fromIntegral result hs_foo :: String -> IO Int hs_foo str = do putStrLn $ "Hello, " ++ str return (length str + 42) 
  • Then, when compiling, you tell GHC to create a shared library:

     $ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs 
  • On the C # side, in addition to importing the function you want to call, you also need to import hs_init() and call it to initialize the runtime system before you can call any Haskell functions. You should also call hs_exit() when done.

     using System; using System.Runtime.InteropServices; namespace Foo { class MainClass { [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern void hs_init(IntPtr argc, IntPtr argv); [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern void hs_exit(); [DllImport("Foo.so", CallingConvention = CallingConvention.Cdecl)] private static extern int foo(string str); public static void Main(string[] args) { Console.WriteLine("Initializing runtime..."); hs_init(IntPtr.Zero, IntPtr.Zero); try { Console.WriteLine("Calling to Haskell..."); int result = foo("C#"); Console.WriteLine("Got result: {0}", result); } finally { Console.WriteLine("Exiting runtime..."); hs_exit(); } } } } 
  • Now we compile and run:

     $ mcs -unsafe Foo.cs $ LD_LIBRARY_PATH=. mono Foo.exe Initializing runtime... Calling to Haskell... Hello, C# Got result: 44 Exiting runtime... 

    It works!

Resources

+48
May 17 '13 at 21:15
source share

For reference, I was able to get the following procedure for working under Windows ...

 {-# LANGUAGE ForeignFunctionInterface #-} module Fibonacci () where import Data.Word import Foreign.C.Types fibs :: [Word32] fibs = 1 : 1 : zipWith (+) fibs (tail fibs) fibonacci :: Word8 -> Word32 fibonacci n = if n > 47 then 0 else fibs !! (fromIntegral n) c_fibonacci :: CUChar -> CUInt c_fibonacci (CUChar n) = CUInt (fibonacci n) foreign export ccall c_fibonacci :: CUChar -> CUInt 

Compile this with

 ghc --make -shared Fibonacci.hs 

This creates half a dozen files, one of which is HSdll.dll . Then I copied this to a Visual Studio C # project and did the following:

 using System; using System.Runtime.InteropServices; namespace ConsoleApplication1 { public sealed class Fibonacci : IDisposable { #region DLL imports [DllImport("HSdll.dll", CallingConvention=CallingConvention.Cdecl)] private static extern unsafe void hs_init(IntPtr argc, IntPtr argv); [DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern unsafe void hs_exit(); [DllImport("HSdll.dll", CallingConvention = CallingConvention.Cdecl)] private static extern UInt32 c_fibonacci(byte i); #endregion #region Public interface public Fibonacci() { Console.WriteLine("Initialising DLL..."); unsafe { hs_init(IntPtr.Zero, IntPtr.Zero); } } public void Dispose() { Console.WriteLine("Shutting down DLL..."); unsafe { hs_exit(); } } public UInt32 fibonacci(byte i) { Console.WriteLine(string.Format("Calling c_fibonacci({0})...", i)); var result = c_fibonacci(i); Console.WriteLine(string.Format("Result = {0}", result)); return result; } #endregion } } 

Console.WriteLine() calls are obviously optional.

I have not tried working under Mono / Linux yet, but it seems to be similar.

In general, this is about the same difficulty as working with the C ++ DLL. (Ie, getting type signatures to match and marshaling properly is a hard bit.)

I also had to edit the project settings and select "allow insecure code".

+10
May 18 '13 at 11:49
source share



All Articles