Вызов Haskell из С#
Я просто провел последнюю неделю или около того, выяснив, как выполнить код С++ с С# как часть моей дневной работы. Нам потребовалось бесконечно, чтобы понять это, но окончательное решение довольно просто.
Теперь мне любопытно... Как трудно было бы назвать Haskell с С#? (Обратите внимание: это вызов Haskell из С#, а не наоборот. Таким образом, основным исполняемым файлом является С#.)
Если это действительно сложно, я не буду беспокоиться. Но если это будет достаточно легко, мне, возможно, придется играть с ним...
В принципе, мы написали код на С++. В Windows он компилируется в DLL, а в Linux он скомпилируется в общий объект (*.so
). Затем на стороне С# вы делаете DllImport
и записываете некоторый код управления ручной памятью, если вы пытаетесь передать что-либо нетривиальное. (Например, массивы, строки и т.д.)
Я знаю, что GHC должен поддерживать создание разделяемых библиотек на обеих платформах, но я не уверен в технических деталях. Какой синтаксис для экспорта материала, а вызывающий должен сделать что-нибудь особенное, чтобы сначала инициализировать DLL?
Конкретно: предположим, что существует функция foobar :: FilePath -> IO Int32
. Может кто-то бросить вместе небольшой рисунок эскиза:
- Какие объявления Haskell мне нужно написать, чтобы разоблачить это во внешнем мире.
- Как сообщить GHC о создании отдельного автономного файла DLL/SO.
- Все, что должен сделать вызывающий, помимо обычного процесса привязки самого
foobar
.
Я не слишком беспокоюсь о фактическом синтаксисе для С# стороны; Я думаю, что я более или менее озадачил это.
P.S. Я кратко просмотрел hs-dotnet
, но это похоже на Windows. (I.e., не будет работать с Mono, поэтому не будет работать в Linux.)
Ответы
Ответ 1
Что касается обоих языков, вы можете в основном притворяться, что пытаетесь взаимодействовать с кодом C.
Это сложная тема, поэтому вместо того, чтобы пытаться объяснить все это, я сосредоточусь на том, чтобы сделать простой пример, который вы можете использовать, используя ресурсы, перечисленные ниже.
-
Сначала вам нужно написать обертки для ваших функций Haskell, которые используют типы из модулей Foreign.C.*
вместо обычных типов haskell. CInt
вместо Int
, CString
вместо String
и т.д. Это самый сложный шаг, особенно когда вам приходится иметь дело с пользовательскими типами.
Вам также нужно написать объявления foreign export
для этих функций с помощью расширения ForeignFunctionInterface
.
{-# 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
-
Со стороны С#, помимо импорта функции, которую вы хотите вызвать, вам также нужно импортировать 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();
}
}
}
}
-
Теперь мы компилируем и запускаем:
$ mcs -unsafe Foo.cs
$ LD_LIBRARY_PATH=. mono Foo.exe
Initializing runtime...
Calling to Haskell...
Hello, C#
Got result: 44
Exiting runtime...
Это работает!
Ресурсы
Ответ 2
Для справки, я смог получить следующую процедуру для работы под 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
Это создает полдюжины файлов, один из которых HSdll.dll
. Затем я скопировал это в проект Visual Studio С# и сделал следующее:
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, но, похоже, это похоже.
В целом, это примерно такая же трудность, как и работа с С++ DLL. (I.e., получение сигнатур типа для соответствия и правильной работы маршалинга - это жесткий бит.)
Мне также пришлось отредактировать настройки проекта и выбрать "разрешить небезопасный код".