2013-05-17 65 views
40

我剛剛花了最後一個星期左右的時間,弄清楚如何從C#執行C++代碼作爲我日常工作的一部分。我們永遠需要弄清楚,但最終的解決方案非常簡單。從C#調用Haskell#

現在我很好奇......從C#調用Haskell有多難? (注意:這就是C#的的Haskell ,所以主要的可執行文件是C#。)

如果這真的很難,我不會打擾。但是,如果它相當容易,我可能不得不去玩它...

基本上,我們寫了一些C++代碼。在Windows上它被編譯成一個DLL,在Linux上它被編譯成一個共享對象(*.so)。然後在C#方面,你做一個DllImport並寫一些手動的內存管理代碼,如果你試圖傳遞任何不平凡的東西。 (例如,數組,字符串等)

我知道GHC是應該支持這兩個平臺上構建共享庫,但我不知道的技術細節。什麼是導出東西的語法,調用者必須做任何特殊的事情來初始化DLL?

要具體:假設存在一個函數foobar :: FilePath -> IO Int32。有人可以把一個小小的草圖顯示出來:

  • 什麼Haskell聲明我需要寫出來揭露這個外部世界。
  • 如何告訴GHC構建一個自包含的DLL/SO文件。
  • 除了綁定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函數。 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) 
    
  2. 然後,編譯時,你告訴GHC使共享庫:

    $ ghc -O2 --make -no-hs-main -optl '-shared' -o Foo.so Foo.hs 
    
  3. 從C#的一面,除了導入要調用的函數,你也有導入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)'中第二個參數的MarshalDirectiveException。胡務? – MathematicalOrchid

+0

@MathematicalOrchid:#1:是的。 #2:嗯。上述代碼適用於我的機器,但我試圖弄清楚如何正確編組。出於某種原因'hs_init'需要'char ** argv []',儘管據我所知'char * argv []'是足夠的。從我已經能夠找到的例子中,'StringBuilder'數組應該在後一種情況下工作,但我不知道如何處理額外的間接級別(至少在沒有手動執行所有操作的情況下)。在任何情況下,只要你傳遞'null',任何指針類型都可以工作。 – hammar

+1

修正了它。我將這兩個參數都改爲了'IntPtr',但之後我得到了不平衡的堆棧警告。顯然,我需要添加'CallingConvention = CallingConvention.Cdecl'出於某種原因...現在看起來很完美。 – MathematicalOrchid

10

僅供參考,我能得到下面的步驟在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 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

@hammar好吧,稍後再測試...... – MathematicalOrchid

+2

沒錯,這都是真的。切換到'IntPtr',刪除所有'unsafe'關鍵字,並關閉編譯器選項,它仍然編譯並運行得很好。 – MathematicalOrchid