私は先週かそこらを過ごして、日常業務の一部としてC#からC++コードを実行する方法を考え出しました。それを理解するのに私たちは永遠にかかりましたが、最終的な解決策はかなり簡単です。
今私は興味があります... C#からHaskellを呼び出すのはどれくらい難しいでしょうか? (注意してください:それはHaskell from C#と呼ばれ、その逆ではありません。したがって、メインの実行可能ファイルはC#です。)
それが本当に難しいなら、私は気にしません。しかし、それが合理的に簡単であるならば、私はそれで遊ぶ必要があるかもしれません...
基本的に、C++コードをいくつか作成しました。 WindowsではDLLにコンパイルされ、Linuxでは共有オブジェクト(*.so
)にコンパイルされます。次に、C#側で、DllImport
を実行し、重要なものを渡そうとしている場合は、手動のメモリ管理コードを記述します。 (例:配列、文字列など)
GHCが両方のプラットフォームでの共有ライブラリの構築をサポートすることになっていることは知っていますが、技術的な詳細はわかりません。ものをエクスポートするための構文は何ですか、そして呼び出し元は最初にDLLを初期化するために特別なことをする必要がありますか?
具体的には、関数foobar :: FilePath -> IO Int32
が存在するとします。誰かが以下を示す小さなスケッチを一緒に投げることができますか?
foobar
自体をバインドする通常のプロセスを超えて、呼び出し元が行う必要のある特別なこと。C#側の実際の構文についてはあまり心配していません。私は多かれ少なかれそれを戸惑ったと思います。
P.S. hs-dotnet
を簡単に調べましたが、これはWindows固有のようです。 (つまり、Monoでは機能しないため、Linuxでは機能しません。)
両方の言語に関する限り、基本的にはCコードとのインターフェースを試みているふりをすることができます。
これは複雑なトピックであるため、すべてを説明するのではなく、以下にリンクされているリソースを使用して構築できる簡単な例を作成することに焦点を当てます。
まず、通常のhaskell型の代わりに、_Foreign.C.*
_モジュールの型を使用するHaskell関数のラッパーを作成する必要があります。 CInt
の代わりにInt
、CString
の代わりに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)
_
次に、コンパイル時に、GHCに共有ライブラリを作成するように指示します。
_$ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs
_
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();
}
}
}
}
_
次に、コンパイルして実行します。
_$ mcs -unsafe Foo.cs
$ LD_LIBRARY_PATH=. mono Foo.exe
Initializing runtime...
Calling to Haskell...
Hello, C#
Got result: 44
Exiting runtime...
_
できます!
リソース:
参考までに、次の手順を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を機能させるのとほぼ同じ難しさです(つまり、型シグネチャを一致させてマーシャリングを正しく機能させるのは難しいことです)。
また、プロジェクト設定を編集して、「安全でないコードを許可する」を選択する必要がありました。