2

我是 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)

在我的实际代码中,上述类似的实现似乎有效。但是当我传递不透明的结构引用时,我会得到奇怪的输出,有时还会出现 ghci 崩溃。

我不确定 mallocForeignPtr 和 ForeignPtr 在 FFI 调用中的用法。对于长期引用,我们应该使用 ForeignPtr + mallocForeignPtr,但我们不能在 ccall 中传递 ForeignPtr。那怎么办呢?我上面的逻辑正确吗?任何形式的帮助都会非常棒。谢谢。

4

2 回答 2

4

我试图提出一个可以显示典型用例的示例。由于使用阶乘作为函数式语言示例的传统由来已久,因此我决定不打破它。

下面的这两个文件(factorial.h 和 factorial.c)使用表格来帮助计算整数的阶乘。他们首先建立并用阶乘填充一个表;那么这个表用来求阶乘;然后在不再需要时将其释放。我们还将消息打印到标准输出,以便能够知道我们的表何时初始化和释放。

阶乘.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);

阶乘.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 中。它首先 malloc 一个指针(这里是使用 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.

我希望这很有用。当然,这是一种完全人为的计算阶乘的方法,但这就是上帝创造阶乘的目的。

于 2013-08-12T00:28:57.700 回答
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
于 2012-08-17T09:19:59.213 回答