我是Haskell编程,外部函数接口和Stackoverflow的新手。我正在尝试为基于C的库构建Haskell FFI绑定。请在下面找到一个与我当前问题非常相似的假设示例:
考虑我有一个C结构和这样的函数:
typedef struct {
int someInt;
void *someInternalData;
} opaque_t;
int bar (opaque_t *aPtr, int anArg);
不透明的C结构是这里的out参数。我应该将其传递给其他API。调用者不需要取消引用不透明的结构。
使用FFI导入查找以下myFFI.hsc文件:
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
module MyFFI where
import Foreign
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C.Types
import Foreign.C
import System.IO.Unsafe
import Foreign.Marshal
import qualified Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import qualified System.IO (putStrLn)
#include "myclib.h"
newtype OpaquePtr = OpaquePtr (ForeignPtr OpaquePtr)
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
instance Storable OpaquePtr where
sizeOf _ = #{size opaque_t}
alignment _ = #{alignment opaque_t}
peek _ = error "Cant peek"
foreign import ccall unsafe "myclib.h bar"
c_bar :: Ptr OpaquePtr
-> CInt
-> CInt
barWrapper :: Int -> (Int, ForeignPtr OpaquePtr)
barWrapper anArg = System.IO.Unsafe.unsafePerformIO $ do
o <- mallocForeignPtr
let res = c_bar (fromIntegral anArg) (Foreign.ForeignPtr.Unsafe.unsafeForeignPtrToPtr o)
return ((fromIntegral res), o)
在我的实际代码中,上面的类似实现似乎有效。但是,当我传递不透明的struct引用时,我会得到奇怪的输出,有时候会出现ghci。
我不确定在FFI调用中使用mallocForeignPtr和ForeignPtr。对于长期生存参考,我们应该使用ForeignPtr + mallocForeignPtr,但是我们不能在ccall中传递ForeignPtr。怎么办呢?我的上述逻辑是否正确? 任何形式的帮助都会非常棒。感谢。
答案 0 :(得分:4)
我试图想出一个可以显示典型用法的示例 案件。因为使用阶乘的长期存在传统 函数式语言的例子,我决定不打破它。
下面的这两个文件(factorial.h和factorial.c)使用表格 帮助计算整数的阶乘。他们首先建立和 用阶乘填表;然后这个表用于请求 阶乘;然后在不再需要它时取消分配。 我们还向stdout打印消息,以便能够知道何时 我们的表被初始化并释放。
factorial.h:
/* A table of factorials. table[i] is the factorial of i. The
* max field is calculated so that its factorial would not be an
* integer overflow.
*/
typedef struct {
unsigned max;
unsigned *table;
} factorial_table;
int factorial_table_init(factorial_table *t);
int factorial_get(factorial_table *t, int n);
void factorial_table_free(factorial_table *t);
factorial.c:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <factorial.h>
/* Calculates max and allocate table. Returns !0 if
* memory could not be allocated.
*/
int factorial_table_init(factorial_table *t)
{
unsigned i, factorial;
t->max = factorial = 1;
while (INT_MAX / factorial > t->max + 1)
factorial *= ++t->max;
t->table = malloc((t->max + 1)*sizeof(unsigned));
if (!t->table) return !0;
t->table[0] = 1;
for (i = 1; i <= t->max; i++)
t->table[i] = i * t->table[i-1];
fprintf(stdout,"A factorial table was just allocated.\n");
return 0;
}
/* Uses a table to get the factorial of an integer number n. Returns
* (-1) if n is negative and (-2) if n is too big.
*/
int factorial_get(factorial_table *t, int n)
{
if (n < 0) return (-1);
if (n > t->max) return (-2);
return t->table[n];
}
/* Frees the table we used. */
void factorial_table_free(factorial_table *t)
{
free(t->table);
fprintf(stdout,"A factorial table was just freed.\n");
}
现在,我们的Haskell代码。
{-# LANGUAGE CPP, ForeignFunctionInterface, EmptyDataDecls #-}
#include <factorial.h>
#let alignment t = "%lu", (unsigned long)offsetof(struct {char x__; t (y__); }, y__)
module Factorial (factorial) where
import Control.Monad
import Foreign.Ptr
import Foreign.ForeignPtr
import Foreign.C
import Foreign.Storable
import System.IO.Unsafe
import Foreign.Marshal
data Factorial_table
instance Storable Factorial_table where
sizeOf _ = #{size factorial_table}
alignment _ = #{alignment factorial_table}
peek _ = error "Cant peek"
foreign import ccall factorial_table_init :: Ptr Factorial_table -> IO CInt
foreign import ccall factorial_get :: Ptr Factorial_table -> CInt -> IO CInt
foreign import ccall "&factorial_table_free" funptr_factorial_table_free
:: FunPtr (Ptr Factorial_table -> IO ())
factorialIO :: IO (CInt -> IO CInt)
factorialIO = do
tableFgnPtr <- mallocForeignPtr :: IO (ForeignPtr Factorial_table)
withForeignPtr tableFgnPtr $ \ptr -> do
status <- factorial_table_init ptr
when (status /= 0) $ fail "No memory for factorial table"
addForeignPtrFinalizer funptr_factorial_table_free tableFgnPtr
let factorialFunction n = do
r <- withForeignPtr tableFgnPtr $ \ptr -> factorial_get ptr n
when (r == (-1)) $ fail
"Factorial was requested for a negative number"
when (r == (-2)) $ fail
"Factorial was requested for a number that is too big"
return r
return factorialFunction
factorial :: CInt -> CInt
factorial = unsafePerformIO . unsafePerformIO factorialIO
首先,注意Factorial_table实例如何可存储。也 请注意,所有函数绑定都返回IO。
所有相关代码都在factorialIO中。它首先mallocs一个指针(和 这里是使用Storable的大小和对齐信息的地方。一世 写了那个电话的类型,但这不是必要的)。然后它添加了 终结器,将在释放指针内存之前运行。我们 将该指针封装在整数函数(factorialFunction)中,始终使用 withForeignPtr,并将其返回。
因为我们知道我们的功能没有重要的副作用, 最后两行只是将我们刚创建的内容转换为纯函数。 我们来测试一下:
ghci
Prelude> :m + Factorial
Prelude Factorial> factorial 5
A factorial table was just allocated.
120
Prelude Factorial> factorial 10
3628800
Prelude Factorial> factorial 13
*** Exception: user error (Factorial was requested for a number that is too big)
Prelude Factorial> factorial 12
479001600
Prelude Factorial> :q
Leaving GHCi.
A factorial table was just freed.
我希望这很有用。当然,这是一种完全人为的方式 计算阶乘,但这就是上帝创造的阶乘。
答案 1 :(得分:0)
好吧,我想我找到了一个正确使用ForeignPtr和mallocForeignPtr的解决方案:
barWrapper :: Int -> (Int, Either error (ForeignPtr OpaquePtr))
barWrapper anArg = System.IO.Unsafe.unsafePerformIO $ do
o <- mallocForeignPtr
withForeignPtr o $ \opaque_ptr -> do
let res = c_bar (fromIntegral anArg) opaque_ptr
if res /= (-1)
then return ((fromIntegral res), Right o)
else
return ((fromIntegral res), Left $ error "some problem")
问题是: