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

Is there a needed GHC option for an executable using the FFI?

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

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

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

因此我想知道是否需要使用某个选项进行编译。与 FFI 打交道时是否有特定选项可供使用? -O0-fllvm都试过了,不行。也许 stack exec ghci 包含一个可以与 GHC 一起使用的选项?

此外,是否有一个可以设置为 GHC 的调试选项,以便 运行 带有 gdb 的可执行文件?我尝试了 -g 选项,但 gdb 没有找到调试符号。 EDIT 这一点解决了: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

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

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 中的数组条目,而 stack exec ghci 之后的 :l src/Main.hs 给出了正确的结果。

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

解决方案

我还不知道为什么 stack exec ghci 和可执行文件之间的行为不同,但现在有了解决方案,这要归功于@Alec 下面的评论:替换头文件中的 int array[2][3] 就足够了extern int array[2][3]。看起来可执行文件将 int array[2][3] 视为 array 的定义,其条目被初始化为 0,而 stack exec ghci 将其视为 array 的声明。