简体   繁体   English

Haskell FFI:在C程序中使用数据类型

[英]Haskell FFI : Using data types in a C program

I wrote a library with Haskell and I would like it to be used in C programs. 我与Haskell一起编写了一个库,希望将其用于C程序。 I have read some documentation about using the foreign export ccall command and the Foreign module. 我已经阅读了一些有关使用foreign export ccall命令和Foreign模块的文档。

I have seen some example such as this one but these examples use common C types like Int or Double . 我已经看到了一些例子,如这一个 ,但这些实施例使用常用的C类型,如IntDouble

In my library, I created some data types like : 在我的库中,我创建了一些数据类型,例如:

data OrdSymb = SEQ
             | SLT
             | SGT

or recursives with a supplied type : 或提供类型的递归:

data MyType a =
        TypeDouble Double
      | TypeInt Int
      | TypeVar a 
      | TypeAdd (MyType a) (MyType a) 

But I didn't find how to use/export these types with the FFI. 但是我没有找到如何在FFI中使用/导出这些类型。

How can I export my own data types to C and use them in the foreign declarations to export my functions ? 如何将自己的数据类型导出到C并在foreign声明中使用它们导出函数?

The short answer to your question is: 您问题的简短答案是:

  • As has been suggested here and in the comments to your related question, start by fully designing your C API, including the C data representations and C functions you expect to be able to use from C code. 如此处所建议的,以及在对相关问题的评论中所建议的,首先要完全设计C API,包括希望从C代码中使用的C数据表示形式和C函数。 This is not a trivial step. 这不是一个微不足道的步骤。 The design decisions you make here will influence the approach you take to exporting (AKA marshaling) these Haskell types out to C and back. 您在此处做出的设计决策将影响您将这些Haskell类型导出(再称为C)并返回到C的方法。
  • Use a "C-to-Haskell" helper to automate the nuts and bolts of sizing, alignment, and structure member access. 使用“ C-to-Haskell”助手来自动执行尺寸,对齐和结构成员访问的螺母和螺栓。 Either hsc2hs or c2hs or both may be helpful. hsc2hsc2hs或两者都可能会有所帮助。 These tools aren't design for exporting Haskell functions to C, but they are still useful. 这些工具不是为 Haskell函数导出到C而设计的,但它们仍然有用。
  • Expect to spend a lot of time reading up on the whole FFI subsystem, studying the generated output from the aforementioned tools, and writing lots of C glue. 期望花费大量时间阅读整个FFI子系统,研究上述工具产生的输出,并编写大量C胶。 Any non-trivial language binding is complex, and the fact that you're trying to expose a library written in a "high level" language through bindings in a "low level" language makes it all the more challenging. 任何非平凡的语言绑定都是复杂的,而且您试图通过以“低级”语言进行绑定来公开以“高级”语言编写的库这一事实使其更具挑战性。

Now, here's a (very, very) long answer to your question that will actually give you something to work with. 现在,这是对您的问题的(非常非常长的)答案,实际上可以为您提供一些帮助。 First some needed imports: 首先需要一些进口:

-- file: MyLib1.hs
module MyLib1 where

import Control.Exception.Base
import Foreign
import Foreign.C

Binding for an Enum 绑定一个枚举

Let's start with your first data type, a simple sum type with 0-ary constructors: 让我们从第一个数据类型开始,这是一个带有0元构造函数的简单求和类型:

data OrdSymb = SEQ | SLT | SGT deriving (Show, Eq)

To be concrete, let's assume we also have some symbols: 具体来说,假设我们也有一些符号:

newtype Symbol = Symbol String

and we'd like to expose Haskell functions with the following signatures: 并且我们希望使用以下签名公开Haskell函数:

compareSymb :: Symbol -> Symbol -> OrdSymb
compareSymb (Symbol x) (Symbol y) =
  case compare x y of { EQ -> SEQ; LT -> SLT; GT -> SGT }

checkSymb :: Symbol -> OrdSymb -> Symbol -> Bool
checkSymb x ord y = compareSymb x y == ord

Admittedly, checkSymb is stupid, but I wanted to show examples of functions that both produce OrdSymb results and accept OrdSymb arguments. 诚然, checkSymb是愚蠢的,但是我想展示既产生OrdSymb结果又接受OrdSymb参数的函数示例。

Here's the C interface we'd like to have for these data types and functions. 这是我们想要用于这些数据类型和函数的C接口。 A natural C representation for a sum type with 0-ary constructors is an enum, so we get something like the following: 带有0元构造函数的sum类型的自然C表示形式是一个枚举,因此我们得到如下所示:

enum ord_symb {
        SLT = -1,
        SEQ = 0,
        SGT = 1
};

Symbols can just be represented by pointers to NUL-terminated C strings: 符号只能由指向NUL终止的C字符串的指针表示:

typedef char* symbol;

and the signatures for the exported functions will look something like: 导出函数的签名将类似于:

enum ord_symb compare_symb(symbol x, symbol y);
bool check_symb(symbol x, enum ord_symb ord, symbol y);

Here's how to create the C language binding completely manually, with no C-to-Haskell helper. 这是完全没有C-to-Haskell帮助器的完全手动创建C语言绑定的方法。 It's a little tedious, but seeing it will help you understand what's going on under the hood. 这有点乏味,但是看到它可以帮助您了解引擎盖下发生的事情。

We'll need an explicit mapping, for the OrdSymb type, between the Haskell constructor representation ( SLT , SEQ , and SGT ) and the C representation as an integer (-1, 0, or 1). 对于OrdSymb类型,我们需要在Haskell构造函数表示形式( SLTSEQSGT )与C表示形式(整数,-1、0或1)之间进行显式映射。 You could do this with a couple of plain functions (eg, toOrdSymb and fromOrdSymb ), though Haskell provides an Enum class with some functions that fit this description: 您可以使用几个简单的函数(例如toOrdSymbfromOrdSymb )来完成此操作,尽管Haskell提供了一个Enum类,其中包含一些适合此描述的函数:

instance Enum OrdSymb where

  toEnum (-1) = SLT
  toEnum 0    = SEQ
  toEnum 1    = SGT

  fromEnum SLT = -1
  fromEnum SEQ = 0
  fromEnum SGT = 1

For documentation purposes, it's also helpful to define a type to represent the C-side type enum ord_symb . 出于文档目的,定义一个表示C端类型enum ord_symb的类型也很有帮助。 The C standard says that enums have the same representation as int s, so we'll write: C标准说,枚举与int具有相同的表示形式,因此我们将编写:

type C_OrdSymb = CInt

Now, because OrdSymb is a simple type, it might make sense to create a Storable instance that can marshal its values to and from a C enum ord_symb in preallocated memory. 现在,由于OrdSymb是一种简单的类型,因此创建一个Storable实例可以合理地将其值与预分配内存中的C enum ord_symb进行编组可能是有意义的。 That would look like this: 看起来像这样:

instance Storable OrdSymb where
  sizeOf _ = sizeOf (undefined :: C_OrdSymb)
  alignment _ = alignment (undefined :: C_OrdSymb)
  peek ptr = genToEnum <$> peek (castPtr ptr :: Ptr C_OrdSymb)
  poke ptr val = poke (castPtr ptr :: Ptr C_OrdSymb) (genFromEnum val)

where we've used the helper functions: 我们在其中使用了辅助函数的位置:

genToEnum :: (Integral a, Enum b) => a -> b
genToEnum = toEnum . fromIntegral

genFromEnum :: (Integral a, Enum b) => b -> a
genFromEnum = fromIntegral . fromEnum

The peek and poke here just wrap the corresponding methods for plain CInt s, and they use the toEnum and fromEnum methods defined above to perform the actual transformation. 这里的peekpoke只是包装了普通CInt的相应方法,它们使用上面定义的toEnumfromEnum方法执行实际的转换。

Note that this Storable instance isn't technically required. 请注意,此Storable实例在技术上不是必需的。 We can marshal OrdSymb s in and out of C enum ord_symb s without such an instance, and in fact in the examples below, that's what we'll do. 我们可以元帅OrdSymb并输出C第enum ord_symb •不用这样的实例,而事实上在下面的例子中,这就是我们要做的。 However, the Storable could come in handy if we later have to work with a C structure that contains an enum ord_symb member, or if we find we're marshaling arrays of enum ord_symb s or something. 但是,如果以后需要使用包含enum ord_symb成员的C结构,或者如果发现要对enum ord_symb数组进行enum ord_symb ,则Storable可能会派上用场。

However, it's worth bearing in mind that -- generally speaking -- objects that are marshaled to and from C don't need to be Storable , and making something Storable doesn't magically take care of all the details of marshaling. 但是,值得注意的是,通常来说,与C进行封送处理的对象不需要Storable ,并且使Storable做某些事情并不能神奇地处理封送处理的所有细节。 In particular, if we tried to write a Storable instance for Symbol , we'd run into trouble. 特别是,如果我们尝试为Symbol编写一个Storable实例,则会遇到麻烦。 Storable s are supposed to be of predetermined length, so sizeOf isn't supposed to inspect it's argument. Storable s应该具有预定的长度,因此sizeOf不应检查其参数。 However, a Symbol 's size depends on the underlying string, so unless we decide to implement a maximum string length and store all Symbol s that way, we shouldn't use a Storable instance here. 但是, Symbol的大小取决于基础字符串,因此,除非我们决定实现最大字符串长度并以这种方式存储所有Symbol ,否则我们不应在此处使用Storable实例。 Instead, let's write some marshaling functions for Symbol s without the benefit of the Storable class: 相反,让我们为Symbol编写一些封送处理功能,而无需利用Storable类:

peekSymbol :: Ptr Symbol -> IO Symbol
peekSymbol ptr = Symbol <$> peekCString (castPtr ptr)

newSymbol :: Symbol -> IO (Ptr Symbol)
newSymbol (Symbol str) = castPtr <$> newCString str

freeSymbol :: Ptr Symbol -> IO ()
freeSymbol = free

Note that we don't "poke" symbols, because we don't normally have a pre-allocated buffer of the correct size into which we're writing the symbol. 请注意,我们不会“戳”符号,因为通常我们没有将符号写入其中的大小正确的预分配缓冲区。 Instead, when we want to marshal a Symbol out to C, we'll need to allocate a new C string for it, and that's what newSymbol does. 相反,当我们想将一个Symbol编组到C时,我们需要为其分配一个新的C字符串,这就是newSymbol所做的。 To avoid a memory leak, we'll need to call freeSymbol (or just free ) on symbols once we're done with them (or let the user of our C bindings know that they are responsible for calling the C function free on the pointer). 为了避免内存泄漏,我们需要调用freeSymbol (或只是free的符号)一次,我们正在与他们进行(或让我们的C绑定的用户知道他们是负责调用C函数free上的指针)。 This also means it may be helpful to write a helper that can be used to wrap a computation that uses a marshalled symbol without leaking the memory. 这也意味着编写一个帮助程序来包装使用编组符号的计算而不会泄漏内存的帮助程序可能会有所帮助。 Again, this is something we won't actually use in this example, but it's a helpful sort of thing to define: 同样,这是我们在本示例中实际上不会使用的东西,但这是定义的一种有用的东西:

withSymbol :: Symbol -> (Ptr Symbol -> IO a) -> IO a
withSymbol sym = bracket (newSymbol sym) freeSymbol

Now, we can export our Haskell functions by writing wrappers that perform the marshaling: 现在,我们可以通过编写执行封送处理的包装器来导出Haskell函数:

mylib_compare_symb :: Ptr Symbol -> Ptr Symbol -> IO C_OrdSymb
mylib_compare_symb px py = do
  x <- peekSymbol px
  y <- peekSymbol py
  return $ genFromEnum (compareSymb x y)

mylib_check_symb :: Ptr Symbol -> C_OrdSymb -> Ptr Symbol -> IO CInt
mylib_check_symb px ord py = do
  x <- peekSymbol px
  y <- peekSymbol py
  return $ genFromEnum (checkSymb x (genToEnum ord) y)

Note that the genFromEnum in the last line is for the Enum instance for Haskell's Bool type, to turn false/true into 0/1. 请注意,最后一行的genFromEnum是Haskell的Bool类型的Enum实例,将false / true转换为0/1。

Also, it's maybe worth noting that, for these wrappers, we didn't use any Storable instances at all! 另外,值得注意的是,对于这些包装器,我们根本没有使用任何Storable实例!

Finally, we can export the wrapper functions to C. 最后,我们可以将包装函数导出到C。

foreign export ccall mylib_compare_symb
  :: Ptr Symbol -> Ptr Symbol -> IO C_OrdSymb
foreign export ccall mylib_check_symb
  :: Ptr Symbol -> C_OrdSymb -> Ptr Symbol -> IO CInt

If you put all of the above Haskell code into MyLib1.hs , create mylib.h , example1.c , and ffitypes.cabal with contents as follows: 如果将上述所有Haskell代码放入MyLib1.hs ,请创建mylib.hexample1.cffitypes.cabal ,其内容如下:

// file: mylib.h
#ifndef MYLIB_H
#define MYLIB_H

enum ord_symb {
        SLT = -1,
        SEQ = 0,
        SGT = 1
};
typedef char* symbol;   // NUL-terminated string

// don't need these signatures -- they'll be autogenerated into
// MyLib1_stub.h
//enum ord_symb compare_symb(symbol x, symbol y);
//bool check_symb(symbol x, enum ord_symb ord, symbol y);

#endif

and: 和:

// file: example1.c
#include <HsFFI.h>
#include "MyLib1_stub.h"
#include <stdio.h>

#include "mylib.h"

int main(int argc, char *argv[])
{
    hs_init(&argc, &argv);

    symbol foo = "foo";
    symbol bar = "bar";

    printf("%s\n", mylib_compare_symb(foo, bar) == SGT ? "pass" : "fail");
    printf("%s\n", mylib_check_symb(foo, SGT, bar) ? "pass" : "fail");
    printf("%s\n", mylib_check_symb(foo, SEQ, bar) ? "fail" : "pass");

    hs_exit();
    return 0;
}

and: 和:

-- file: ffitypes.cabal
name:                 ffitypes
version:              0.1.0.0
cabal-version:        >= 1.22
build-type:           Simple

executable example1
  main-is:            example1.c
  other-modules:      MyLib1
  include-dirs:       .
  includes:           mylib.h
  build-depends:      base
  default-language:   Haskell2010
  cc-options:         -Wall -O
  ghc-options:        -Wall -Wno-incomplete-patterns -O

and put everything in a fresh ffitypes directory. 并将所有内容放入新的ffitypes目录中。 Then, from that directory: 然后,从该目录:

$ stack init
$ stack build
$ stack exec example1

should work to run the example. 应该可以运行示例。

Marshaling a Parameterized, Recursive Type 封送参数化递归类型

Now, let's turn to your more complicated MyType . 现在,让我们转到更复杂的MyType I've changed the Int to an Int32 so it'll match a CInt on typical platforms. 我已经将Int更改为Int32因此它将与典型平台上的CInt匹配。

data MyType a =
        TypeDouble Double
      | TypeInt Int32
      | TypeVar a 
      | TypeAdd (MyType a) (MyType a)

This is a sum type with unary and binary constructors, an arbitrary type parameter a , and recursive structure, so pretty complicated. 这是一个具有一元和二进制构造函数,任意类型参数a和递归结构的求和类型,因此非常复杂。 Again, it's important to begin by specifying a concrete C implementation. 同样,从指定具体的C实现开始也很重要。 AC union can be used to store a complicated sum type, but we'll also want to "tag" the union with a enum to indicate which constructor the union is representing, so the C type will look something like this: AC联合可以用于存储复杂的sum类型,但是我们还想用枚举“标记”联合以指示联合所代表的构造函数,因此C类型将如下所示:

typedef struct mytype_s {
        enum mytype_cons_e {
                TYPEDOUBLE,
                TYPEINT,
                TYPEVAR,
                TYPEADD
        } mytype_cons;
        union {
                double type_double;
                int type_int;
                void* type_var;
                struct {
                        struct mytype_s *left;
                        struct mytype_s *right;
                } type_add;
        } mytype_value;
} mytype;

Note that, to allow the C bindings to work with MyType a s with multiple possible parameters a , we need to use a void* for the type_var union member. 请注意,要允许C绑定与具有多个可能参数a MyType a一起使用,我们需要对type_var联合成员使用void*

Writing the marshalling functions for MyType entirely manually is very painful and error prone. 完全手动为MyType编写编组函数非常痛苦且容易出错。 There are a lot of details about the exact size, alignment, and layout of C structures that you'd need to get right. 您需要弄清很多有关C结构的确切大小,对齐方式和布局的细节。 Instead, we'll use the c2hs helper package. 相反,我们将使用c2hs帮助程序包。 We'll start with a little preamble at the top of a new MyLib2.chs : 我们将从新的MyLib2.chs顶部的一些前言开始:

-- file: MyLib2.chs
module MyLib2 where

import Foreign
import Foreign.C

#include "mylib.h"

The c2hs package is great for working with enums. c2hs软件包非常适合使用枚举。 For example, creating marshalling infrastructure for the enum mytype_cons_e tag with this package looks like this: 例如,使用此程序包为enum mytype_cons_e标签创建编组基础结构看起来像这样:

-- file: MyLib2.chs
{#enum mytype_cons_e as MyTypeCons {}#}

Note that this automatically retrieves the definition from the C header mylib.h , creates a Haskell definition equivalent to: 请注意,这会自动从C标头mylib.h检索定义,并创建一个等效于以下内容的Haskell定义:

-- data MyTypeCons = TYPEDOUBLE | TYPEINT | etc.

and defines the needed Enum instance to map the Haskell constructors to and from the integer values on the C side. 并定义所需的Enum实例,以将Haskell构造函数与C端的整数值进行映射。 It'll be useful to have our generalized toEnum and fromEnum helpers here, too: 在这里将我们的泛化toEnumfromEnum帮助器也将很有用:

genToEnum :: (Integral a, Enum b) => a -> b
genToEnum = toEnum . fromIntegral

genFromEnum :: (Integral a, Enum b) => b -> a
genFromEnum = fromIntegral . fromEnum

Now, let's look at marshalling your data type: 现在,让我们看一下如何整理数据类型:

data MyType a =
  TypeDouble Double
  | TypeInt Int32
  | TypeVar a 
  | TypeAdd (MyType a) (MyType a)

to and from a struct mytype_s . 往返于struct mytype_s One warning: these implementations assume that the recursive constructor TypeAdd and its C analogue type_add are never used to create "cycles" on the C or Haskell side. 一个警告:这些实现假定递归构造函数TypeAdd及其C模拟type_add从未用于在C或Haskell端创建“循环”。 Handling data structures that are recursive in the sense that let x = 0:x is recursive would require a different approach. let x = 0:x是递归的意义上处理递归的数据结构将需要不同的方法。

Because struct mytype_s is a fixed-length structure, you might think it would be a good candidate for a Storable instance, but that turns out not to be the case. 由于struct mytype_s是固定长度的结构,因此您可能会认为它是Storable实例的不错选择,但事实并非如此。 Because of the embedded pointer in the type_var union member and the recursive pointers in the type_add member, it's not possible to write a reasonable Storable instance for MyType . 由于type_var联合成员中的嵌入式指针和type_add成员中的递归指针,因此无法为MyType编写合理的Storable实例。 We could write one for: 我们可以这样写:

data C_MyType a =
  C_TypeDouble Double
  | C_TypeInt Int32
  | C_TypeVar (Ptr a)
  | C_TypeAdd (Ptr (MyType a)) (Ptr (MyType a))

where the pointers have been made explicit. 指针已经明确的地方。 When we marshall this, we'll assume we've already marshalled the "child" nodes and have pointers to them that we can marshall out to the structure. 当我们进行编组时,我们假设我们已经对“子”节点进行了编组,并且有指向它们的指针,可以将它们编组到结构中。 For the C_TypeAdd constructor, I could have written this instead: 对于C_TypeAdd构造函数,我可以这样编写:

  -- C_TypeAdd (Ptr (C_MyType a)) (Ptr (C_MyType a))

It doesn't really matter, since we'll be freely casting Ptr s back and forth between MyType s and C_MyType s . 没关系,因为我们将在MyTypeC_MyType之间自由地来回转换Ptr I decided to use my definition because it got rid of two castPtr calls. 我决定使用我的定义,因为它摆脱了两个castPtr调用。

The Storable instance for C_MyType looks like this. C_MyTypeStorable实例如下所示。 Note how c2hs allows us to look up sizes, alignments, and offsets automatically. 请注意, c2hs如何使我们能够自动查找尺寸,对齐方式和偏移量。 We'd have to calculate these all manually otherwise. 否则,我们必须手动计算所有这些。

instance Storable (C_MyType a) where
  sizeOf _ = {#sizeof mytype_s#}
  alignment _ = {#alignof mytype_s#}
  peek p = do
    typ <- genToEnum <$> {#get struct mytype_s->mytype_cons#} p
    case typ of
      TYPEDOUBLE ->
        C_TypeDouble . (\(CDouble x) -> x)
        <$> {#get struct mytype_s->mytype_value.type_double#} p
      TYPEINT    ->
        C_TypeInt    . (\(CInt    x) -> x)
        <$> {#get struct mytype_s->mytype_value.type_int   #} p
      TYPEVAR    ->
        C_TypeVar . castPtr <$> {#get struct mytype_s->mytype_value.type_var#} p
      TYPEADD    -> do
        q1 <- {#get struct mytype_s->mytype_value.type_add.left#} p
        q2 <- {#get struct mytype_s->mytype_value.type_add.right#} p
        return $ C_TypeAdd (castPtr q1) (castPtr q2)
  poke p t = case t of
    C_TypeDouble x -> do
      tag TYPEDOUBLE
      {#set struct mytype_s->mytype_value.type_double#} p (CDouble x)
    C_TypeInt x    -> do
      tag TYPEINT
      {#set struct mytype_s->mytype_value.type_int   #} p (CInt    x)
    C_TypeVar q    -> do
      tag TYPEVAR
      {#set struct mytype_s->mytype_value.type_var   #} p (castPtr q)
    C_TypeAdd q1 q2 -> do
      tag TYPEADD
      {#set struct mytype_s->mytype_value.type_add.left #} p (castPtr q1)
      {#set struct mytype_s->mytype_value.type_add.right#} p (castPtr q2)

    where
      tag = {#set struct mytype_s->mytype_cons#} p . genFromEnum

With the Storable instance for C_MyType out of the way, the marshalling functions for a real MyType look pretty clean: 有了C_MyTypeStorable实例, 真正的 MyType的编组函数看起来就很干净了:

peekMyType :: (Ptr a -> IO a) -> Ptr (MyType a) -> IO (MyType a)
peekMyType peekA p = do
  ct <- peek (castPtr p)
  case ct of
    C_TypeDouble x -> return $ TypeDouble x
    C_TypeInt    x -> return $ TypeInt    x
    C_TypeVar    q -> TypeVar <$> peekA q
    C_TypeAdd q1 q2 -> do
      t1 <- peekMyType peekA q1
      t2 <- peekMyType peekA q2
      return $ TypeAdd t1 t2

newMyType :: (a -> IO (Ptr a)) -> MyType a -> IO (Ptr (MyType a))
newMyType newA t = do
  p <- malloc
  case t of
    TypeDouble x  -> poke p (C_TypeDouble x)
    TypeInt    x  -> poke p (C_TypeInt    x)
    TypeVar    v  -> poke p . C_TypeVar =<< newA v
    TypeAdd t1 t2 -> do
      q1 <- newMyType newA t1
      q2 <- newMyType newA t2
      poke p (C_TypeAdd q1 q2)
  return (castPtr p)  -- case from Ptr C_MyType to Ptr MyType

freeMyType :: (Ptr a -> IO ()) -> Ptr (MyType a) -> IO ()
freeMyType freeA p = do
  ct <- peek (castPtr p)
  case ct of
    C_TypeVar q -> freeA q
    C_TypeAdd q1 q2 -> do
      freeMyType freeA q1
      freeMyType freeA q2
    _ -> return ()  -- no children to free
  free p

Note how we need to use helpers for the a type. 请注意我们需要如何为a类型使用辅助函数。 Whenever we want to make a newMyType for a MyType a , we'll need to provide a tailored newA for the a type. 每当我们想为MyType a创建newMyType ,我们都需要为a类型提供量身定制的newA It would be possible to make this into a typeclass and even create an instance for all Storable a , but I haven't done that here. 可以将其设置为类型类,甚至为所有Storable a创建一个实例,但是我在这里没有做到这一点。

Now, suppose we have a Haskell function that uses all these data types that we'd like to export to C: 现在,假设我们有一个Haskell函数,该函数使用我们要导出到C的所有这些数据类型:

replaceSymbols :: OrdSymb -> Symbol -> Symbol -> MyType Symbol -> MyType Symbol
replaceSymbols ord sym1 sym2 = go
  where
    go (TypeVar s) | checkSymb s ord sym1 = TypeVar sym2
    go (TypeAdd t1 t2) = TypeAdd (go t1) (go t2)
    go rest = rest

with the helper functions previously defined: 使用先前定义的帮助程序功能:

compareSymb :: Symbol -> Symbol -> OrdSymb
compareSymb (Symbol x) (Symbol y) =
  case compare x y of { EQ -> SEQ; LT -> SLT; GT -> SGT }

checkSymb :: Symbol -> OrdSymb -> Symbol -> Bool
checkSymb x ord y = compareSymb x y == ord

We'll need a few other things in MyLib2.chs . 我们还需要MyLib2.chs其他一些MyLib2.chs First, we'll use c2hs to define the OrdSymb type (again, this automatically generates the associated data OrdSymb ): 首先,我们将使用c2hs定义OrdSymb类型(同样,这会自动生成关联的data OrdSymb ):

{#enum ord_symb as OrdSymb {} deriving (Show, Eq)#}
type C_OrdSymb = CInt

and the symbol-marshalling code copied from MyLib1.hs : 以及从MyLib1.hs复制的符号编组代码:

newtype Symbol = Symbol String

peekSymbol :: Ptr Symbol -> IO Symbol
peekSymbol ptr = Symbol <$> peekCString (castPtr ptr)

newSymbol :: Symbol -> IO (Ptr Symbol)
newSymbol (Symbol str) = castPtr <$> newCString str

freeSymbol :: Ptr Symbol -> IO ()
freeSymbol = free

Then, we can write the following C wrapper: 然后,我们可以编写以下C包装器:

mylib_replace_symbols :: C_OrdSymb -> Ptr Symbol -> Ptr Symbol
    -> Ptr (MyType Symbol) -> IO (Ptr (MyType Symbol))
mylib_replace_symbols ord psym1 psym2 pt = do
  sym1 <- peekSymbol psym1
  sym2 <- peekSymbol psym2
  t <- peekMyType peekSymbol pt
  let t' = replaceSymbols (genToEnum ord) sym1 sym2 t
  newMyType newSymbol t'

Given that this returns a malloced data structure, it's helpful to also provide an exported function to free it: 鉴于此操作返回了已分配的数据结构,因此还提供导出函数以释放它是有帮助的:

mylib_free_mytype_symbol :: Ptr (MyType Symbol) -> IO ()
mylib_free_mytype_symbol = freeMyType freeSymbol

And let's export them: 然后导出它们:

foreign export ccall mylib_replace_symbols
  :: C_OrdSymb -> Ptr Symbol -> Ptr Symbol
       -> Ptr (MyType Symbol) -> IO (Ptr (MyType Symbol))
foreign export ccall mylib_free_mytype_symbol
  :: Ptr (MyType Symbol) -> IO ()

If you take all the Haskell code in this section, starting with the module MyLib2 line and put it in MyLib2.chs , then create/modify the following files: 如果您采用本节中的所有Haskell代码, module MyLib2行开始并将其放入MyLib2.chs ,然后创建/修改以下文件:

// file: mylib.h
#ifndef MYLIB_H
#define MYLIB_H

enum ord_symb {
    SLT = -1,
    SEQ = 0,
    SGT = 1
};
typedef char* symbol;   // NUL-terminated string

typedef struct mytype_s {
    enum mytype_cons_e {
        TYPEDOUBLE,
        TYPEINT,
        TYPEVAR,
        TYPEADD
    } mytype_cons;
    union {
        double type_double;
        int type_int;
        void* type_var;
        struct {
            struct mytype_s *left;
            struct mytype_s *right;
        } type_add;
    } mytype_value;
} mytype;

#endif

and: 和:

// file: example2.c
#include <HsFFI.h>
#include "MyLib2_stub.h"
#include <stdio.h>

#include "mylib.h"

// AST for:   1.0 + foo
mytype node1 = { TYPEDOUBLE, {type_double: 1.0} };
mytype node2 = { TYPEVAR,    {type_var: "foo"} };
mytype root  = { TYPEADD,    {type_add: {&node1, &node2} } };

int main(int argc, char *argv[])
{
    hs_init(&argc, &argv);

    mytype *p1 = mylib_replace_symbols(SEQ, "foo", "bar", &root);
    printf("%s\n",  // should print "bar"
       (char*) p1->mytype_value.type_add.right->mytype_value.type_var);
    mytype *p2 = mylib_replace_symbols(SEQ, "quux", "bar", &root);
    printf("%s\n",  // unchanged -- should still be "foo"
       (char*) p2->mytype_value.type_add.right->mytype_value.type_var);

    mylib_free_mytype_symbol(p1);
    mylib_free_mytype_symbol(p2);

    hs_exit();
    return 0;
}

and add the executable example2 clause to your Cabal file: 并将executable example2子句添加到Cabal文件中:

-- file: ffitypes.cabal
name:                 ffitypes
version:              0.1.0.0
cabal-version:        >= 1.22
build-type:           Simple

executable example1
  main-is:            example1.c
  other-modules:      MyLib1
  include-dirs:       .
  includes:           mylib.h
  build-depends:      base
  default-language:   Haskell2010
  cc-options:         -Wall -O
  ghc-options:        -Wall -Wno-incomplete-patterns -O

executable example2
  main-is:            example2.c
  other-modules:      MyLib2
  include-dirs:       .
  includes:           mylib.h
  build-depends:      base
  build-tools:        c2hs
  default-language:   Haskell2010
  cc-options:         -Wall -O
  ghc-options:        -Wall -Wno-incomplete-patterns -O

and stick them all in the ffitypes directory, then you should be able to stack build and stack exec example2 . 并将它们全部粘贴在ffitypes目录中,那么您应该能够stack buildstack exec example2

Language Bindings are Hard! 语言绑定很难!

As you can perhaps tell from the code above, it takes a lot of work to create even simple C bindings for Haskell libraries. 从上面的代码中可以看出,为Haskell库创建甚至简单的C绑定也需要大量的工作。 If it's any consolation, creating Haskell bindings for C libraries is only a little bit easier. 如果可以的话,为C库创建Haskell绑定只会稍微容易一些。 Best of luck! 祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM