Страницы

суббота, 13 июля 2019 г.

Проба создания привязки к коду на C++

Для примера я взял открытую C++ библиотеку Open Babel для работы с моделями молекул. API библиотеки необъятно, поэтому решено было сделать только ту привязку, которая необходима для тестовой задачи — чтения молекулы из файла и вывод координат её атомов. Так как для ввода Open Babel использует стандартные классы С++, то в привязку было добавлены функции открытия и закрытия std::ifstream

Для удобства использования я создал структуру каталогов для привязки:

$ mkdir -p openbabel/singularity/definition openbabel/implementation
В таком виде библиотеку проще подключать к транслятору ключом -infr openbabel

Где создал интерфейсный модуль на Обероне openbabel/singularity/definition/OpenBabel.mod:

MODULE OpenBabel;

  TYPE
    Atom      * = POINTER TO RECORD END;
    Mol       * =            RECORD END;
    Conversion* =            RECORD END;
    Ifstream  * = POINTER TO RECORD END;

  PROCEDURE Open*(name: ARRAY OF CHAR): Ifstream;
  BEGIN
    ASSERT(name # "");
  RETURN
    NIL
  END Open;

  PROCEDURE Close*(VAR s: Ifstream);
  BEGIN
    s := NIL
  END Close;

  PROCEDURE SetInFormat*(VAR c: Conversion; fmt: ARRAY OF CHAR): BOOLEAN;
  RETURN FALSE
  END SetInFormat;

  PROCEDURE Read*(VAR m: Mol; VAR c: Conversion; in: Ifstream): BOOLEAN;
  RETURN
    FALSE
  END Read;

  PROCEDURE GetAtom*(m: Mol; idx: INTEGER): Atom;
  RETURN
    NIL
  END GetAtom;

  PROCEDURE GetVector*(a: Atom; VAR v: ARRAY OF REAL);
  BEGIN
    ASSERT(LEN(v) >= 3);
    ASSERT(FALSE)
  END GetVector;

END OpenBabel.

Транслировал его в Си-код

$ ost to-c OpenBabel openbabel/singularity/implementation -m openbabel/singularity/definition
Получив такие два файла-болванки для вставки кода:
  1. openbabel/singularity/implementation/OpenBabel.h
    #if !defined HEADER_GUARD_OpenBabel
    #    define  HEADER_GUARD_OpenBabel 1
    
    typedef struct OpenBabel_Atom__s { char nothing; } *OpenBabel_Atom;
    #define OpenBabel_Atom__s_tag o7_base_tag
    
    extern void OpenBabel_Atom__s_undef(struct OpenBabel_Atom__s *r);
    typedef struct OpenBabel_Mol { char nothing; } OpenBabel_Mol;
    #define OpenBabel_Mol_tag o7_base_tag
    
    extern void OpenBabel_Mol_undef(struct OpenBabel_Mol *r);
    typedef struct OpenBabel_Conversion { char nothing; } OpenBabel_Conversion;
    #define OpenBabel_Conversion_tag o7_base_tag
    
    extern void OpenBabel_Conversion_undef(struct OpenBabel_Conversion *r);
    typedef struct OpenBabel_Ifstream__s { char nothing; } *OpenBabel_Ifstream;
    #define OpenBabel_Ifstream__s_tag o7_base_tag
    
    extern void OpenBabel_Ifstream__s_undef(struct OpenBabel_Ifstream__s *r);
    
    extern struct OpenBabel_Ifstream__s *OpenBabel_Open(o7_int_t name_len0, o7_char name[/*len0*/]);
    
    extern void OpenBabel_Close(struct OpenBabel_Ifstream__s **s);
    
    extern o7_bool OpenBabel_SetInFormat(struct OpenBabel_Conversion *c, o7_int_t fmt_len0, o7_char fmt[/*len0*/]);
    
    extern o7_bool OpenBabel_Read(struct OpenBabel_Mol *m, struct OpenBabel_Conversion *c, struct OpenBabel_Ifstream__s *in_);
    
    extern struct OpenBabel_Atom__s *OpenBabel_GetAtom(struct OpenBabel_Mol *m, o7_int_t idx);
    
    extern void OpenBabel_GetVector(struct OpenBabel_Atom__s *a, o7_int_t v_len0, double v[/*len0*/]);
    
    extern void OpenBabel_init(void);
    #endif
  2. openbabel/singularity/implementation/OpenBabel.c
    #include <o7.h>
    
    #include "OpenBabel.h"
    
    #define OpenBabel_Atom__s_tag o7_base_tag
    extern void OpenBabel_Atom__s_undef(struct OpenBabel_Atom__s *r) {
    }
    #define OpenBabel_Mol_tag o7_base_tag
    extern void OpenBabel_Mol_undef(struct OpenBabel_Mol *r) {
    }
    #define OpenBabel_Conversion_tag o7_base_tag
    extern void OpenBabel_Conversion_undef(struct OpenBabel_Conversion *r) {
    }
    #define OpenBabel_Ifstream__s_tag o7_base_tag
    extern void OpenBabel_Ifstream__s_undef(struct OpenBabel_Ifstream__s *r) {
    }
    
    extern struct OpenBabel_Ifstream__s *OpenBabel_Open(o7_int_t name_len0, o7_char name[/*len0*/]) {
     O7_ASSERT(o7_strcmp(name_len0, name, 0, (o7_char *)"") != 0);
     return NULL;
    }
    
    extern void OpenBabel_Close(struct OpenBabel_Ifstream__s **s) {
     (*s) = NULL;
    }
    
    extern o7_bool OpenBabel_SetInFormat(struct OpenBabel_Conversion *c, o7_int_t fmt_len0, o7_char fmt[/*len0*/]) {
     return (0 > 1);
    }
    
    extern o7_bool OpenBabel_Read(struct OpenBabel_Mol *m, struct OpenBabel_Conversion *c, struct OpenBabel_Ifstream__s *in_) {
     return (0 > 1);
    }
    
    extern struct OpenBabel_Atom__s *OpenBabel_GetAtom(struct OpenBabel_Mol *m, o7_int_t idx) {
     return NULL;
    }
    
    extern void OpenBabel_GetVector(struct OpenBabel_Atom__s *a, o7_int_t v_len0, double v[/*len0*/]) {
     O7_ASSERT(v_len0 >= 3);
     O7_ASSERT((0 > 1));
    }
    
    extern void OpenBabel_init(void) {
     static unsigned initialized = 0;
     if (0 == initialized) {
    
     }
     ++initialized;
    }

Для уменьшения накладных расходов на привязку я избавился от .c файла, оставив только заголовочный файл, пометив его функции как встраиваемые и наполнив нужным кодом

#if !defined HEADER_GUARD_OpenBabel
#    define  HEADER_GUARD_OpenBabel 1

#include <iostream>
#include <fstream>

#include <openbabel/mol.h>
#include <openbabel/obconversion.h>

typedef struct OpenBabel_Atom__s { OpenBabel::OBAtom a; } *OpenBabel_Atom;
#define OpenBabel_Atom__s_tag o7_base_tag
O7_ALWAYS_INLINE void OpenBabel_Atom__s_undef(OpenBabel_Atom *r) {}

typedef struct OpenBabel_Mol { OpenBabel::OBMol m; } OpenBabel_Mol;
#define OpenBabel_Mol_tag o7_base_tag
O7_ALWAYS_INLINE void OpenBabel_Mol_undef(OpenBabel_Mol *r) {}

typedef struct OpenBabel_Conversion { OpenBabel::OBConversion c; } OpenBabel_Conversion;
#define OpenBabel_Conversion_tag o7_base_tag
O7_ALWAYS_INLINE void OpenBabel_Conversion_undef(OpenBabel_Conversion *r) {}

typedef struct OpenBabel_Ifstream__s { std::ifstream s; } *OpenBabel_Ifstream;
#define OpenBabel_Ifstream__s_tag o7_base_tag
O7_ALWAYS_INLINE void OpenBabel_Ifstream__s_undef(OpenBabel_Ifstream *r) {}

O7_ALWAYS_INLINE OpenBabel_Ifstream OpenBabel_Open(o7_int_t len, o7_char name[/*len*/]) {
    OpenBabel_Ifstream f;

    O7_ASSERT(name[0] != 0);

    f = (OpenBabel_Ifstream)new std::ifstream((char *)name);
    return f;
}

O7_ALWAYS_INLINE void OpenBabel_Close(OpenBabel_Ifstream *s) {
    O7_ASSERT(*s != NULL);
    ((std::ifstream *)(*s))->close();
    delete (std::ifstream *)*s;
    *s = NULL;
}

O7_ALWAYS_INLINE o7_bool 
OpenBabel_SetInFormat(OpenBabel_Conversion *c, o7_int_t len, o7_char fmt[/*len*/]) {
    return c->c.SetInAndOutFormats((char *)fmt, (char *)fmt);
}

O7_ALWAYS_INLINE o7_bool 
OpenBabel_Read(OpenBabel_Mol *m, OpenBabel_Conversion *c, OpenBabel_Ifstream in) {
    return c->c.Read(&m->m, (std::ifstream *)in);
}

O7_ALWAYS_INLINE OpenBabel_Atom OpenBabel_GetAtom(OpenBabel_Mol *m, o7_int_t idx) {
    return (OpenBabel_Atom)m->m.GetAtom(idx);
}

O7_ALWAYS_INLINE void OpenBabel_GetVector(OpenBabel_Atom a, o7_int_t len, double out[/*len*/]) {
    O7_ASSERT(len >= 3);
    OpenBabel::vector3 v;
    v = a->a.GetVector();
    v.Get(out);
}

O7_ALWAYS_INLINE void OpenBabel_init(void) {}
#endif

Для тестирования привязки был написан модуль Mol.mod для выполнения задачи в иходной постановке:

MODULE Mol;

  IMPORT Out, Ob := OpenBabel;

  PROCEDURE Read*(VAR m: Ob.Mol; name: ARRAY OF CHAR);
  VAR conv: Ob.Conversion; in: Ob.Ifstream; ok: BOOLEAN;
  BEGIN
    IF Ob.SetInFormat(conv, "xyz") THEN
      in := Ob.Open(name);
      ok := Ob.Read(m, conv, in);
      Ob.Close(in)
    END
  END Read;

  PROCEDURE OutAtom(a: Ob.Atom);
  VAR r: ARRAY 3 OF REAL; i: INTEGER;
  BEGIN
    Ob.GetVector(a, r);
    FOR i := 0 TO LEN(r) - 1 DO
      Out.Real(r[i], 0); Out.String(" ")
    END
  END OutAtom;

  PROCEDURE Log*(n: ARRAY OF CHAR);
  VAR mol: Ob.Mol; atom: Ob.Atom; i: INTEGER;
  BEGIN
    Read(mol, n);
    i := 1;
    Out.String("Molecule: "); Out.Ln;
    atom := Ob.GetAtom(mol, i);
    WHILE atom # NIL DO
      Out.Int(i, 0); Out.String(") "); OutAtom(atom); Out.Ln;
      INC(i);
      atom := Ob.GetAtom(mol, i)
    END
  END Log;

END Mol.

Такой модуль можно было бы, к примеру, запустить командой:

$ ost run 'Mol.Log("water.xyz")' -infr openbabel -m . \
-cc "g++ -xc++ -I/usr/include/openbabel-2.0 -lopenbabel"
water.xyz
3

O         -0.00000       -0.35107       -0.00000
H         -0.81100        0.17553        0.00000
H          0.81100        0.17553        0.0000

Но здесь проявилась особенность компилятора g++ - опция компоновщика -lopenbabel игнорируется, если указана до имён файлов с исходным кодом. Для возможности разделения опций компилятора Си необходимо доработать транслятор ost. Пока же нужно отдельно транслировать код на Обероне в Си, затем запускать компилятор C++, после чего можно запускать выходной файл.

Вторая проблема проявилась из-за особенности C++ - неявного вызова конструкторов локальных переменных структурного типа в месте их объявления. Транслятор ost по умолчанию генерирует очистку локальных переменных после их объявления, приводя их в негодность. Поэтому нужно добавлять к команде трансляции в Си ключ -init noinit. Это тоже предмет будущей доработки транслятора.

Обновление: обе проблемы были решены и программу можно запустить с помощью команды:

$ ost run 'Mol.Log("water.xyz")' -infr openbabel -m . \
-cc "g++ -xc++ -I/usr/include/openbabel-2.0" ... "-lopenbabel"
Здесь опция "-lopenbabel" отделена от основной команды троеточием и добавляется к команде компилятора после указания исходный кодов на C, что позволяет успешно скомпоновать программу.

2 комментария:

  1. Отлично. Что-то про Андроид будет?)

    ОтветитьУдалить
    Ответы
    1. Что-то ещё кроме этого - https://vostok-space.blogspot.com/2018/09/android.html ?
      Пока не планирую

      Удалить

Обработка ошибок

Тема корректной обработки ошибок в программе является довольно сложным вопросом в программировании. Отчасти от того, что и она сама являет...