2013-05-17 16 views
40

私はちょうど先週、C#のコードを私の日の仕事の一部として実行する方法を考え出しました。私たちはそれをいつまでも理解することができましたが、最終的な解決策はかなり簡単です。C#からHaskellを呼び出す

今私は興味があります...ハスケルをC#から呼び出すのはどれくらい難しいでしょうか? (注意深く:それはHaskell から C#までですが、それ以外の方法ではありませんので、主な実行ファイルはC#です)

本当に難しい場合は、私は気にしません。しかし、合理的に簡単であれば、私はそれを持って遊びをする必要があります...

基本的に、私たちはいくつかのC + +コードを書いています。 WindowsではDLLにコンパイルされ、Linuxでは共有オブジェクトにコンパイルされます(*.so)。その後、C#側でDllImportを実行し、何かを渡すのが面倒であれば、手動のメモリ管理コードを書いてください。 (例:配列、文字列など)

私はGHCが両方のプラットフォームで共有ライブラリを構築することをサポートしていますが、技術的な詳細はわかりません。何かをエクスポートする構文は何ですか?また、呼び出し元はDLLを最初に初期化するために特別な処理を行う必要がありますか?

具体的には、関数foobar :: FilePath -> IO Int32が存在するとします。誰かが小さなスケッチを一緒に投げることができます:

  • これを外の世界に公開するために私が書く必要があるものは何ですか。
  • GHCに、自己完結型のDLL/SOファイルを1つ作成するように指示する方法を教えてください。
  • 呼び出し側が特別な処理を行う必要があるものは何でも、foobar自体をバインドする通常の処理を超えてください。

私はC#側の実際の構文についてあまり心配していません。私は多かれ少なかれそれを困惑させたと思う。

P.S.私は簡単にhs-dotnetを見ましたが、これはWindows固有のようです。 (つまり、Monoでは動作しませんので、Linuxでは動作しません)

+5

FFIバインディングのプランBは、「Cで薄いラッパーを作成する」ことができます。どんな種類のFFIでもほとんどの言語はCと相互に作用することができます。 –

+2

ポインタ:GHCユーザーガイドの章4.13と8.2、http://www.haskell.org/haskellwiki/Calling_Haskell_from_C –

+0

GHCにはDLL作成に関する章があります:http://www.haskell.org/ghc/docs/latest/html/users_guide/win32-dl​​ls.html最近のバージョンのGHCではこのセクションが変更されているようです。 (!) – MathematicalOrchid

答えて

48

両方の言語に関する限り、基本的にはCコードとのインターフェースをしようとしているようです。

これは複雑なトピックなので、すべてを説明するのではなく、以下にリンクされているリソースを使用して簡単な例を作成することに焦点を当てます。

  1. まず、あなたはForeign.C.*モジュールの代わりに、通常のHaskellの型から型を使用し、あなたのHaskellの関数のラッパーを記述する必要があります。 Intの代わりにCIntの代わりにStringの代わりにCStringなどです。これは、特にユーザー定義型を処理する必要がある場合は、最も複雑な手順です。

    また、ForeignFunctionInterface拡張子を使用して、これらの関数の宣言を記述する必要があります(foreign export)。

    {-# 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) 
    
  2. に続いて、コンパイルするとき、あなたは共有ライブラリにするためにGHCを教えて:あなたが呼び出したい関数をインポートすることに加えて、あなたもする必要は、C#の側から

    $ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs 
    
  3. をimport hs_init()を呼び出して、Haskell関数を呼び出す前にランタイムシステムを初期化します。完了したらhs_exit()にも電話する必要があります。

    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(); 
          } 
         } 
        } 
    } 
    
  4. 今、私たちはコンパイルして実行します。

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

    それは働きます!

資源:参考

+0

これは私が欲したものとほとんど同じように見えます。しかし、...#1あなたは 'hs_exit()'も呼び出す必要はありませんか?#2これを実行すると、 'hs_init(null、null)'の2番目の引数に対してMarshalDirectiveExceptionが発生します。ええ? – MathematicalOrchid

+0

@MathematicalOrchid:#1:はい。 #2:Hm。上記のコードは私のマシン上で動作しますが、正しくマーシャリングする方法を理解しようとしていました。なんらかの理由で 'hs_init'は' char ** argv [] 'をとりますが、' char * argv [] 'が十分であると理解しています。私が見つけることができた例から、 'StringBuilder'の配列が後者の場合にはうまくいくはずですが、私は余分なレベルの間接処理に対処する方法を知りません。いずれにせよ、 'null 'を渡すだけであれば、どんなポインタ型でも動作するはずです。 – hammar

+1

修正しました。両方のパラメータを 'IntPtr'に変更しましたが、スタックの警告がアンバランスになってしまいました。どうやら、何らかの理由で 'CallingConvention = CallingConvention.Cdecl'を追加する必要があるようです...今は完全に動作するようです。 – MathematicalOrchid

10

、私は...

{-# 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 
Windowsで動作するように、次の手順を取得することができました

ghc --make -shared Fibonacci.hs 

これはHSdll.dllそのうちの1つは半ダースのファイルを生成し、これをコンパイルします。私は、そのVisual StudioのC#プロジェクトにコピーし、次のでした:

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()呼び出しが明らかにオプションです。

私はMono/Linuxの下でこれを実行しようとしていませんが、おそらく似ています。

要約すると、C++ DLLを動作させるのとほぼ同じ難点です。 (つまり、タイプシグネチャを一致させてマーシャリングを正しく行うことは難しいことです)

また、プロジェクト設定を編集して「安全でないコードを許可」を選択する必要がありました。

+1

'unsafe'ビットは、私が生ポインタを使っていたときだけ必要でした。 'IntPtr'では、それらがなければ動作します。 – hammar

+0

@ハマーOK、後でテストします... – MathematicalOrchid

+2

うん、それは本当です。 'IntPtr'に変更し、全ての' unsafe'キーワードを削除し、コンパイラオプションをオフにしてください。コンパイルしてもうまく動作します。 – MathematicalOrchid

関連する問題