[英]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类型,如
Int
或Double
。
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: 您问题的简短答案是:
hsc2hs
or c2hs
or both may be helpful. hsc2hs
或c2hs
或两者都可能会有所帮助。 These tools aren't design for exporting Haskell functions to C, but they are still useful. 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
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构造函数表示形式( SLT
, SEQ
和SGT
)与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: 您可以使用几个简单的函数(例如
toOrdSymb
和fromOrdSymb
)来完成此操作,尽管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. 这里的
peek
和poke
只是包装了普通CInt
的相应方法,它们使用上面定义的toEnum
和fromEnum
方法执行实际的转换。
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.h
, example1.c
和ffitypes.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. 应该可以运行示例。
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: 在这里将我们的泛化
toEnum
和fromEnum
帮助器也将很有用:
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 . 没关系,因为我们将在
MyType
和C_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_MyType
的Storable
实例如下所示。 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_MyType
的Storable
实例, 真正的 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 build
和stack exec example2
。
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.