使用 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
的声明。
我在 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
的声明。