воскресенье, 1 мая 2011 г.

perl binding for C++ objects

а вот например нас периодически спрашивают, как можно прикрутить к perlу большие и развесистые объекты C++, из которых биндинг осуществляется к весьма немногим методам (как почти всегда и бывает).
Например для автоматической генерации биндингов дико хорош swig. Но и у него периодически рвет крышу при парзинге развесистых заголовков. В качестве prooflink см. например как сделан биндинг писона к ida pro в ida sdk - для этого большинство заголовочных файлов были жестоко отпатчены на предмет вставки где попало макроса SWIG
Но есть например и другой способ, про который я не помню где вычитал и потому буду нагло считать, что придумал его сам.
Допустим что у нас есть некий C++ объект и мы хотим прикрутить его к perl:

class SomeUsefullClass
{
 public:
  SomeUsefullClass();
  ~SomeUsefullClass();
  // this is our methods for binding
  int MakeMeIncredibleRich(int howMuch);
  int StoreResults(const char *fname);

  // тут еще гора методов, которые мы не хотим биндить
 protected:
  SomeShittyClass m_Worker;
  SomeOtherShittyClass m_Serializer;
  // и тут еще гора методов, не имеющих к биндингу никакого отношения
};

C весьма высокой вероятностью если такое описание реального класса подсунуть swig, то его стошнит (заметим в скобках что нам еще придется указать всю кучу использованных header files). А если немного помедитировать над приведенным выше описанием, то можно заметить, что нам не нужны все кишки и потроха реального класса - например swigу можно было бы подсунуть ровно то что нам нужно


class SomeUsefullClass
{
 public:
  SomeUsefullClass();
  ~SomeUsefullClass();
  int MakeMeIncredibleRich(int howMuch);
  int StoreResults(const char *fname);
}

Такой заголовок компилится без проблем. И даже иногда линкуется с библиотекой, содержащей реализацию SomeUsefullClass. Но чаще не линкуется и не работает. Потому что мы хотим вызвать конструктор/деструктор реального объекта.И тут нам может помочь директива %exception (или даже пара %newobject & %delobject):


%module SomeUsefullClass
%{
  extern SomeUsefullClass *get_new_SomeUsefullClass();
  extern void delete_SomeUsefullClass(SomeUsefullClass *);
%}

%exception SomeUsefullClass::SomeUsefullClass() %{
   result = get_new_SomeUsefullClass();
%}

%exception SomeUsefullClass::~SomeUsefullClass() %{
  delete_SomeUsefullClass(arg1);
%}

// тут фальшивое описание класса SomeUsefullClass


Остается написать пару методов get_new_SomeUsefullClass & delete_SomeUsefullClass и все прекрасно собирается и работает.
Однако поскольку я чудовищно ленив, то и этот метод на мой вкус требует слишком много ненужной писанины (и хуже того - дублирования кода в разных файлах !). Можно поступить еще проще - в заголовках реального класса SomeUsefullClass проставить комменты с нужными атрибутами для нужных методов, и потом например банальным perl скриптом автоматически сгенерировать файл интерфейса для swig и по паре методов для заменяемых конструктора/деструктора. Итого наш сорец может выглядеть примерно так:


//@bind_class
class SomeUsefullClass
{
 public:
//@fake_constructor
  SomeUsefullClass();
//@fake_destructor
  ~SomeUsefullClass();
//@bind_method
  int MakeMeIncredibleRich(int howMuch);
//@bind_method
  int StoreResults(const char *fname);
 protected:
  SomeShittyClass m_worker;
  SomeOtherShittyClass m_Serializer;
  ...
};
//@end_bind_class

Написание простого парзера, распознающего нужные атрибуты, оставляется в качестве тривиального упражнения для читателя (потому что я ленив например)

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

  1. SWIG c STL-контейнерами работает через какую-то анальную жопу: я так и не смог добиться что бы, к примеру, возвращаемый моим методом std::map прозрачно и без лишних костылей транслировался в пайтоновский dictionary.
    Хотя, возможно, просто неасилел.

    ОтветитьУдалить
  2. да, с stl есть такая проблема у swig
    но скажи честно - часто ли тебе нужно прямо целиком stl объекты прокидывать в binding, если и в perl и в писоне есть свои собственные типы данных для словарей и можно написать тривиальный метод для конвертации ?

    ОтветитьУдалить
  3. > можно написать тривиальный метод для конвертации
    Так и делаю обычно, но вообще хотелось бы обходиться без лишних промежуточных сущностей, тем более, что они достаточно шаблонны и однотипны.
    Конкретно под пайтон в бусте какие-то биндинги есть, всё хочу глянуть, как руки дойдут - может там дела получше обстоят.

    ОтветитьУдалить
  4. а в perlе есть например typemap файлы - тоже достаточно один раз написать
    Но по моему это bad design - возвращать в script engine списки объектов. В крайнем случае можно сделать для своего объекта tied hash например для эмуляции коллекций

    ОтветитьУдалить