web-dev-qa-db-ja.com

C#からHaskellを呼び出す

私は先週かそこらを過ごして、日常業務の一部としてC#からC++コードを実行する方法を考え出しました。それを理解するのに私たちは永遠にかかりましたが、最終的な解決策はかなり簡単です。

今私は興味があります... C#からHaskellを呼び出すのはどれくらい難しいでしょうか? (注意してください:それはHaskell from C#と呼ばれ​​、その逆ではありません。したがって、メインの実行可能ファイルはC#です。)

それが本当に難しいなら、私は気にしません。しかし、それが合理的に簡単であるならば、私はそれで遊ぶ必要があるかもしれません...

基本的に、C++コードをいくつか作成しました。 WindowsではDLLにコンパイルされ、Linuxでは共有オブジェクト(*.so)にコンパイルされます。次に、C#側で、DllImportを実行し、重要なものを渡そうとしている場合は、手動のメモリ管理コードを記述します。 (例:配列、文字列など)

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

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

  • これを外の世界に公開するために私が書く必要のあるHaskell宣言。
  • 単一の自己完結型DLL/SOファイルを作成するようにGHCに指示するにはどうすればよいですか。
  • foobar自体をバインドする通常のプロセスを超えて、呼び出し元が行う必要のある特別なこと。

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

P.S. hs-dotnetを簡単に調べましたが、これはWindows固有のようです。 (つまり、Monoでは機能しないため、Linuxでは機能しません。)

42

両方の言語に関する限り、基本的にはCコードとのインターフェースを試みているふりをすることができます。

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

  1. まず、通常のhaskell型の代わりに、_Foreign.C.*_モジュールの型を使用するHaskell関数のラッパーを作成する必要があります。 CIntの代わりにIntCStringの代わりにStringなど。これは、特にユーザー定義の型を処理する必要がある場合に最も複雑な手順です。 。

    また、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に共有ライブラリを作成するように指示します。

    _$ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs
    _
  3. C#側からは、呼び出したい関数をインポートするだけでなく、Haskell関数を呼び出す前に、hs_init()をインポートして呼び出し、ランタイムシステムを初期化する必要があります。完了したら、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...
    _

    できます!

リソース:

51
hammar

参考までに、次の手順を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
_

これをコンパイルします

_ghc --make -shared Fibonacci.hs
_

これにより、半ダースのファイルが生成され、そのうちの1つは_HSdll.dll_です。次に、それを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を機能させるのとほぼ同じ難しさです(つまり、型シグネチャを一致させてマーシャリングを正しく機能させるのは難しいことです)。

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

11