2

Haskellプログラミング、Foreign Function Interface、Stackoverflowは初めてです。Cベースのライブラリ用のHaskellFFIバインディングを構築しようとしています。私の現在の問題に非常に似ている架空の例を以下に見つけてください。

私が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が折り目が付けられます。

FFI呼び出しでのmallocForeignPtrとForeignPtrの使用法についてはよくわかりません。長期参照の場合は、ForeignPtr + mallocForeignPtrを使用する必要がありますが、ccallでForeignPtrを渡すことはできません。それではどうすればいいですか?上記のロジックは正しいですか?どんな種類の助けも本当に素晴らしいでしょう。ありがとう。

4

2 に答える 2

4

私は典型的な使用例を示すことができる例を考え出そうとしました。関数型言語の例として階乗を使用するという長い伝統があるので、私はそれを壊さないことに決めました。

以下の2つのファイル(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にあります。最初にポインタをmallocします(ここで、Storableのサイズと配置情報が使用されます。その呼び出しのタイプを記述しましたが、これは必須ではありません)。次に、ファイナライザーを追加します。ファイナライザーは、ポインターメモリが解放される直前に実行されます。そのポインタを整数の関数(factorialFunction)内にカプセル化し、常にwithForeignPtrを使用して、それを返します。

関数には重要な副作用がないことがわかっているので、最後の2行は、作成したものを純粋関数にします。それをテストしてみましょう:

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」にアクセスした場合にのみ、外部呼び出しが実行されます。したがって、ccallを呼び出すためにif/elseブロックを使用する必要がありました
  2. unsafeForeignPtrtoPtrの代わりにwithForeignPtrを使用する必要があります
于 2012-08-17T09:19:59.213 に答える