FFI,制作可存储实例的麻烦

时间:2013-10-08 08:14:52

标签: haskell ffi

我有一个描述的结构:

#define MAXVAL                   20 
#define ATOM_EL_LEN               6 
#define NUM_H_ISOTOPES            3 
typedef signed char   S_CHAR;
typedef unsigned char U_CHAR;
typedef signed short   S_SHORT;
typedef unsigned short U_SHORT;

typedef  S_SHORT AT_NUM;  

typedef struct tagInchiAtom {
    /* atom coordinates */
    double x;
    double y;
    double z;
    /* connectivity */
    AT_NUM  neighbor[MAXVAL];     /* adjacency list: ordering numbers of */
                                  /*            the adjacent atoms, >= 0 */
    S_CHAR  bond_type[MAXVAL];    /* inchi_BondType */
    /* 2D stereo */
    S_CHAR  bond_stereo[MAXVAL];  /* inchi_BondStereo2D; negative if the */
                                  /* sharp end points to opposite atom */
    /* other atom properties */
    char    elname[ATOM_EL_LEN];  /* zero-terminated chemical element name:*/
                                  /* "H", "Si", etc. */
    AT_NUM  num_bonds;            /* number of neighbors, bond types and bond*/
                                  /* stereo in the adjacency list */
    S_CHAR  num_iso_H[NUM_H_ISOTOPES+1]; /* implicit hydrogen atoms */
                                  /* [0]: number of implicit non-isotopic H
                                       (exception: num_iso_H[0]=-1 means INCHI
                                       adds implicit H automatically),
                                     [1]: number of implicit isotopic 1H (protium),
                                     [2]: number of implicit 2H (deuterium),
                                     [3]: number of implicit 3H (tritium) */
    AT_NUM  isotopic_mass;        /* 0 => non-isotopic; isotopic mass or  */
                                  /* ISOTOPIC_SHIFT_FLAG + mass - (average atomic mass) */
    S_CHAR  radical;              /* inchi_Radical */
    S_CHAR  charge;               /* positive or negative; 0 => no charge */
}inchi_Atom;

要表示inchi_Atom我在下面创建了数据结构:

type ConnGraph = [CShort]
data INCHIAtom = INCHIAtom {atoms :: ConnGraph,
                            label :: CString,
                            bondTypes :: ConnGraph,
                            charge :: CSChar}

然后,我尝试为此结构实现Storable实例(使用hsc2hs):

instance Storable INCHIAtom where
    sizeOf    _ = (#size inchi_Atom)
    alignment _ = alignment (undefined :: CInt)
    peek _ = error "peek is not implemented"
    poke ptr (INCHIAtom atoms' label' bondType' charge') = do 
                                                          (#poke inchi_Atom, x) ptr $ (0 ::CDouble)
                                                          (#poke inchi_Atom, y) ptr $ (0 ::CDouble)
                                                          (#poke inchi_Atom, z) ptr $ (0 ::CDouble)
                                                          (#poke inchi_Atom, neighbor) ptr $ atoms'
                                                          (#poke inchi_Atom, bond_type) ptr $ bondType'
                                                          --(#poke inchi_Atom, bond_stereo) $ nullPtr
                                                          (#poke inchi_Atom, elname) ptr $ label'
                                                          (#poke inchi_Atom, num_bonds) ptr $ (length atoms')
                                                          (#poke inchi_Atom, num_iso_H) ptr $ (0 :: CSChar)
                                                          (#poke inchi_Atom, isotopic_mass) ptr $ (0 :: CShort)
                                                          (#poke inchi_Atom, radical) ptr $ (0 :: CSChar)
                                                          (#poke inchi_Atom, charge) ptr $ charge'

我有几个问题。我无法如何实现Storable的{​​{1}}实例。 第二,我想把一个NULL指针放到ConnGraph,但如果我解除bondStereo,我会得到编译错误。更多,我的数据结构是否正确(#poke inchi_Atom, bond_stereo) $ nullPtr

1 个答案:

答案 0 :(得分:1)

你可以为ConnGraph制作一个可存储的实例,但它有点狡猾。通常的模式是为数组提供malloc空间并为其编组。但是,既然您知道最大大小,并且在结构中分配了空间,您可以利用这一点并写下:

newtype ConnGraph = ConnGraph {unConnGraph :: [CShort]}

instance Storable ConnGraph where
    sizeOf _ = maxval*sizeOf (undefined :: CShort)
    alignment _ = alignment (undefined :: CShort)
    poke ptr (ConnGraph lst) = if length lst <= maxval
        then pokeArray (castPtr ptr) lst
        else error "Can't poke ConnGraph, too big!"

我对此并不完全满意,看起来很脆弱。如果您想要在ConnGraph结构之外编组inchi_Atom,则可能会导致问题。如果您选择这条路线,我认为让ConnGraph成为新类型非常重要,因为此定义不会干扰[CShort]类型的任何其他值。

您可以使用hsc2hs的#offset宏来确定起始位置,然后在增量指针上使用pokeArray,而不是创建此Storable实例。这实际上看起来更容易:

pokeArray (ptr `plusPtr` (#offset inchi_Atom, neighbor)) $ unConnGraph atoms'

你应该检查这里的长度是否超过maxval

nullPtr行出错的原因是您遗漏了ptr参数。但是,此代码并不严格正确。如果指针类型大于short,则会将nullPtr值写入多个字段。更好的是明确清除整个数组。最后一个实例将是

-- I don't know how to do CPP in hsc2hs-generated Haskell, but you could create a separate module
-- that defines maxval = MAXVAL
maxval = 20

instance Storable INCHIAtom where
    sizeOf    _ = (#size inchi_Atom)
    alignment _ = alignment (undefined :: CDouble)
    peek _ = error "peek is not implemented"
    poke ptr (INCHIAtom atoms' label' bondType' charge') =
       do
          (#poke inchi_Atom, x) ptr $ (0 ::CDouble)
          (#poke inchi_Atom, y) ptr $ (0 ::CDouble)
          (#poke inchi_Atom, z) ptr $ (0 ::CDouble)
          (#poke inchi_Atom, neighbor) ptr $ atoms'
          (#poke inchi_Atom, bond_type) ptr $ bondType'
          (#poke inchi_Atom, bond_stereo) ptr $ ConnGraph (replicate maxval 0)
          (#poke inchi_Atom, elname) ptr $ label'
          (#poke inchi_Atom, num_bonds) ptr $ (length $ unConnGraph atoms')
          (#poke inchi_Atom, num_iso_H) ptr $ (0 :: CSChar)
          (#poke inchi_Atom, isotopic_mass) ptr $ (0 :: CShort)
          (#poke inchi_Atom, radical) ptr $ (0 :: CSChar)
          (#poke inchi_Atom, charge) ptr $ charge'

我还更改了对齐方式以匹配CDouble。结构对齐至少与最严格的成员一样严格,在这种情况下是double。这可能与int的对齐方式相同,也可能不同,具体取决于您的系统。