Haskell FFI - mallocForeignPtr用法

时间:2012-08-15 18:10:55

标签: haskell ffi

我是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。怎么办呢?我的上述逻辑是否正确? 任何形式的帮助都会非常棒。感谢。

2 个答案:

答案 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")

问题是:

  1. 我忽视了Haskell的懒惰评价:只有在访问'res'时才会执行外部调用。所以,我必须使用if / else块来调用ccall
  2. 我应该使用withForeignPtr而不是unsafeForeignPtrtoPtr