Question

I have a structure described:

#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;

To represent inchi_Atom I made the data structure below:

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

Then, I try to implement a Storable instance for this structure (using 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'

I have several question. I have no way how to realise the Storable instance for ConnGraph. And second, I want to put a NULL pointer to bondStereo, but if I decomment (#poke inchi_Atom, bond_stereo) $ nullPtr I get compilation error. More, is it correct alignment (undefined :: CInt) for my data structure?

Was it helpful?

Solution

You can make a Storable instance for ConnGraph, but it's a bit dodgy. The usual pattern is to malloc space for an array and marshal to that. However, since you know the max size, and space is allocated in the struct, you can take advantage of this and write:

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!"

I'm not entirely happy with this, it seems fragile. If you ever want to marshal a ConnGraph outside of the inchi_Atom struct, it may cause problems. If you do go this route, I think it's important to make ConnGraph a newtype because this definition won't interfere with any other values of type [CShort].

Instead of creating this Storable instance, you could use hsc2hs's #offset macro to determine the starting position, then use pokeArray on the incremented pointer. This actually looks easier:

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

you should probably check that the length doesn't exceed maxval here.

The reason for the error on the nullPtr line is because you left out the ptr argument. However, this code isn't strictly correct. If your pointer type is larger than short, you'll write the nullPtr value into several fields. Better would be to explicitly clear the whole array. The final instance will be

-- 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'

I also changed the alignment to match CDouble. Structs are aligned to be at least as strict as the strictest member, which in this case is double. This may or may not be the same alignment as int, depending on your system.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top