使用FFI的可执行文件是否需要GHC选项?

时间:2018-04-12 12:01:50

标签: haskell ghc ffi ghci haskell-stack

我在Main中有一个模块Main.hs。该程序使用FFI(特别是FunPtr)。

当我运行stack exec ghci并加载模块(:l src/Main.hs)时,它运行正常。

然而,当我将模块编译为可执行文件并运行可执行文件时,我遇到了崩溃,即分段错误。

因此,我想知道是否需要使用某个选项进行编译。在处理FFI时是否有特定的选择?我试过-O0-fllvm,没办法。也许stack exec ghci包含可以与GHC一起使用的选项?

此外,是否有一个调试选项可以设置为GHC以便使用gdb运行可执行文件?我尝试了-g选项,但gdb找不到调试符号。 编辑这一点已经解决:gdb在使用stack exec -- ghc -g -rtsopts src/Main.hs进行编译时找到调试符号。

我在Linux Ubuntu上使用GHC 8.2.2。

修改

这是一个反映我真实程序结构的最小程序。这个工作正常(在GHCI或作为可执行文件),但我包括它。

helloffi.c

#include <stdlib.h>

double** evalf(double (*f)(double), double x){
    double** out = malloc(2 * sizeof(double*));
    for(unsigned i=0; i<2; i++){
        out[i] = malloc(2 * sizeof(double));
        out[i][0] = (*f)(x);
        out[i][1] = (*f)(x+1);
    }
    return out;
}

double sumpointer(double** pptr){
    double x=0;
    for(unsigned i=0; i<2; i++){
        for(unsigned j=0; j<2; j++){
            x += pptr[i][j];
        }
    }
    return x;
}

库的主要模块,Lib.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module Lib
  where
import           Foreign.C.Types       
import           Foreign.Ptr           (Ptr, FunPtr, freeHaskellFunPtr)

type CFunction = CDouble -> IO CDouble

foreign import ccall "wrapper" functionPtr 
    :: CFunction -> IO (FunPtr CFunction)

foreign import ccall "evalf" c_evalf
    :: FunPtr CFunction
    -> CDouble
    -> IO (Ptr (Ptr CDouble))

fun2cfun :: (Double -> Double) -> CFunction
fun2cfun f x = 
    return $ realToFrac (f (realToFrac x))

evalFun :: (Double -> Double) -> Double -> IO (Ptr (Ptr CDouble))
evalFun f x = do
    fPtr <- functionPtr (fun2cfun f)
    result <- c_evalf fPtr (realToFrac x)
    freeHaskellFunPtr fPtr
    return result

foreign import ccall "sumpointer" c_sumpointer
    :: Ptr (Ptr CDouble) -> IO CDouble

模块Main.hs,待编译

module Main
  where
import           Lib

main :: IO ()
main = do
    x <- evalFun (\x -> x*x) 2
    y <- c_sumpointer x
    print y

1 个答案:

答案 0 :(得分:3)

嗯,这不是问题的答案,但我痛苦地解决了这个问题,我认为这可能对其他人有所帮助。值得分享。所以我发布了一个显示问题的最小例子。一个非常小的例子。

helloffi/
├── C
│   ├── array.c
│   ├── helloffi.c
│   └── helloffi.h
├── helloffi.cabal
├── Setup.hs
├── src
│   ├── Lib.hs
│   └── Main.hs
└── stack.yaml

array.c :在C文件中定义一个数组,例如:

int array[2][3] =  
    {{1, 24, 1},
     {2, 19, 1}};

helloffi.c :定义一个使用此数组的函数,例如:

#include "helloffi.h"

int getCoef(unsigned i, unsigned j){
    return array[i][j];
}

helloffi.h ,头文件:

int array[2][3];
int getCoef(unsigned, unsigned);

这就是C部分的全部内容。现在是Haskell的一部分。使库模块导入C函数。

Lib.hs

{-# LANGUAGE ForeignFunctionInterface #-}
module Lib
  where
import           Foreign.C.Types       

foreign import ccall "getCoef" c_getCoef
    :: CUInt -> CUInt -> IO CInt

Main.hs ,要编译为可执行文件:

module Main
  where
import           Lib

main :: IO ()
main = do
    x <- c_getCoef 0 1
    print x

这就是全部。而现在,这个谜。编译库(获取模块Lib和从Main.hs生成的可执行文件,假设这个名为test)。

stack exec ghci,给出正确的结果(24):

Prelude> :l src/Main.hs 
[1 of 1] Compiling Main             ( src/Main.hs, interpreted )
Ok, one module loaded.
*Main> main
24

运行可执行文件,结果错误(总是0):

$ .stack-work/install/x86_64-linux/lts-11.4/8.2.2/bin/test 
0

test可执行文件是已编译的Main.hs。但是,它无法正确读取array.c中数组的条目,而:l src/Main.hs后的stack exec ghci会提供正确的结果。

不是很奇怪吗?有没有人有解释?

解决方案

我还不知道为什么stack exec ghci和可执行文件之间的行为有所不同,但现在有一个解决方案,感谢@ Alec的评论:它足以用{替换头文件中的int array[2][3] {1}}。看起来可执行文件将extern int array[2][3]视为int array[2][3]的定义,其条目初始化为array,而0将其视为stack exec ghci的声明。