Показаны сообщения с ярлыком GLib. Показать все сообщения
Показаны сообщения с ярлыком GLib. Показать все сообщения

В позапрошлом посте рассказывалось о создании экземпляров объектов GObject по имени класса, где, кроме всего прочего, было сказано:
Получается: либо типы всех потенциально загружаемых из файла объектов должны быть заранее зарегистрированы, т.е. все (обязательно все) и явно (в каком-то виде) типы должны быть прописаны в коде разработчиком, либо же нужен неизящный и непортабельный механизм вроде поиска в таблице символов исполняемого файла для имени каждого подгружаемого из файла класса соответствующего метода foo_bar_get_type... и вызова этого метода.
Все почти так и есть, но в коде GtkBuilder случайно был найден довольно изящный и портабельный механизм поиска foo_bar_get_type. Идея простая: нужно создать объект GModule не для динамически загружаемого модуля, а для самой программы. Для чего в метод g_module_open(const gchar *file_name, GModuleFlags flags) вместо имени файла нужно передать NULL:
file_name — the name of the file containing the module, or NULL to obtain a GModule representing the main program itself.
Пример на Vala на основе идей из GtkBuilder (механизм получения типа по имени класса иллюстрирует функция type_from_name_lazy):
public abstract class Animal : Object
{
  public abstract string to_string();
}

public class Cow : Animal
{
  public override string to_string()
  {
    return "I'm a cow!";
  }
}

public class Dog : Animal
{
  public override string to_string()
  {
    return "I'm a dog!";
  }
}

int main()
{
  Animal a;
  Type type;

  a = new Dog(); //< Явно создаем Dog, имя типа зарегистрировано в системе типов.
  print("1) %s\n", a.to_string());

  if((type = Type.from_name("Dog")) != Type.INVALID)
  {
    a = Object.new(type) as Animal;
    print("2) %s\n", a.to_string());
  }
  else
    print("2) Failed to get type!\n");

  if((type = Type.from_name("Cow")) != Type.INVALID)
  {
    a = Object.new(type) as Animal;
    print("3) %s\n", a.to_string());
  }
  else
  {
    print("3) Failed to get type! Trying type_from_name_lazy...\n");

    if((type = type_from_name_lazy("Cow")) != Type.INVALID)
    {
      a = Object.new(type) as Animal;
      print("3) %s\n", a.to_string());
    }
    else
      print("3) Failed to get type!\n");
  }

  return 0;
}

[CCode (has_target = false)]
delegate Type TypeGetFunc();

Type type_from_name_lazy(string name)
{
  void *func = null;

  if(Module.open(null, 0).symbol(ascii_camel_to_snake(name) + "_get_type", out func))
    return ((TypeGetFunc)func)();
  else
    return Type.INVALID;
}

string ascii_camel_to_snake(string input)
{
  var sb = new StringBuilder();

  for(int i = 0; input[i] != '\0'; i++)
  {
    if(
      ( input[i].isupper() && i > 0 && !input[i - 1].isupper() ) ||
      ( (i > 2 && input[i].isupper()) && input[i - 1].isupper() && input[i - 2].isupper() )
      )
      sb.append_c('_');

    sb.append_c(input[i].tolower());
  }

  return sb.str;
}


Вывод программы:
1) I'm a dog!
2) I'm a dog!
3) Failed to get type! Trying type_from_name_lazy...
3) I'm a cow!
И немного про Windows

Лично у меня данный пример как есть в Windows не заработал (собирал компилятором mingw). Не удавалось найти указатель на функцию cow_get_type. Опытным путем выяснилось, что работает такой механизм при соблюдении двух условий:
  1. Объект должен быть реализован в отдельной dll;
  2. До попытки поиска указателя должна быть вызвана хотя бы одна функция из этой dll.

В предыдущем посте «Клонирование GObject» я рассказывал про идею некоего обобщенного кода, не зависимого от конкретной задачи, который бы сохранял объекты Gobject в файлы (типы объектов и значения свойств) и потом воссоздавал последовательность объектов по этим файлам. Предполагалось сохранять в текстовый файл имена классов и порядок объектов, а также имена свойств объектов и их значения.

Идея красивая, но здесь мы сталкиваемся с одним ограничением системы типов GObject (ограничением вполне естественным, т.к. написан GObjecct на Си и вся поддержка ООП реализована на уровне библиотеки): чтобы создать экземпляр класса по имени класса нужно, чтобы этот класс уже был зарегистрирован в системе типов GObject. Т.е. для типа FooBar должен быть заранее вызван метод foo_bar_get_type непосредственно или опосредованно (например, через метод создания вроде foo_bar_new).

Получается: либо типы всех потенциально загружаемых из файла объектов должны быть заранее зарегистрированы, т.е. все (обязательно все) и явно (в каком-то виде) типы должны быть прописаны в коде разработчиком, либо же нужен неизящный и непортабельный механизм вроде поиска в таблице символов исполняемого файла для имени каждого подгружаемого из файла класса соответствующего метода foo_bar_get_type (см. тему на stack overflow) и вызова этого метода.

UPDATE: Найден довольно изящный и портабельный способ поиска метода foo_bar_get_type, см. Создание объектов GObject по имени типа. Часть 2.

Пример на Vala ниже иллюстрирует описанную проблему. Мы можем создать объект типа Dog по имени, т.к. выше уже создан объект типа Dog и класс зарегистрирован в системе типов. Создание объекта типа Cow же завершается неудачей (см. вывод программы после исходного кода).

public abstract class Animal : Object
{
  public abstract string to_string();
}

public class Cow : Animal
{
  public override string to_string()
  {
    return "I'm a cow!";
  }
}

public class Dog : Animal
{
  public override string to_string()
  {
    return "I'm a dog!";
  }
}

int main()
{
  Animal a;
  Type type;

  a = new Dog(); //< Явно создаем Dog, здесь класс и регистрируется в системе типов.
  print("1) %s\n", a.to_string());

  if((type = Type.from_name("Dog")) != 0)
  {
    a = Object.new(type) as Animal;
    print("2) %s\n", a.to_string());
  }
  else
    print("2) Failed to get type!\n");

  if((type = Type.from_name("Cow")) != 0)
  {
    a = Object.new(type) as Animal;
    print("3) %s\n", a.to_string());
  }
  else
    print("3) Failed to get type!\n");

  return 0;
}
Вывод программы:
1) I'm a dog!
2) I'm a dog!
3) Failed to get type!

В одной из программ на работе используется ряд самописных Gtk-виджетов для настройки фильтров изображения. Виджеты лежат в Box'е, добавляются, удаляются и настраиваются пользователем. У каждого типа виджетов свой набор настроек. Все настройки виджеты хранят в своих свойствах.

Чтобы сохранять после перезапуска программы список фильтров и их параметры, была идея сделать некий обобщенны код, не зависимый от конкретной задачи, который бы сохранял объекты Gobject в файлы (типы объектов и значения свойств) и потом воссоздавал последовательность объектов по этим файлам.

В качестве "proof of concept" был написан код, клонирующий объект Gobject. Функция g_object_clone возвращает копию объекта, переданного ей в качестве параметра src. Код основан на примере со StackOverflow. Обращаю внимание, что полноценно копируются только простые свойства, для boxed-типов копируется указатель с инкрементированием счетчика ссылок.

Код gobject_clone.c:
// gcc `pkg-config --libs --cflags glib-2.0 gobject-2.0` gobject_clone.c point.c -o gobject_clone
#include <glib-object.h>
#include "point.h"
#include <stdio.h>
#include <string.h> //< For strcmp.

// See http://stackoverflow.com/questions/3003655/is-there-a-good-way-to-copy-a-gtk-widget?answertab=votes#tab-top
static GObject *g_object_clone(GObject *src)
{
  GObject *dst;
  GParameter *params;
  GParamSpec **specs;
  guint n, n_specs, n_params;

  specs = g_object_class_list_properties(G_OBJECT_GET_CLASS(src), &n_specs);
  params = g_new0(GParameter, n_specs);
  n_params = 0;

  for (n = 0; n < n_specs; ++n)
    if((specs[n]->flags & G_PARAM_READWRITE) == G_PARAM_READWRITE)
    {
      params[n_params].name = g_intern_string(specs[n]->name);
      g_value_init(&params[n_params].value, specs[n]->value_type);
      g_object_get_property(src, specs[n]->name, &params[n_params].value);
      ++n_params;
    }

  dst = g_object_newv(G_TYPE_FROM_INSTANCE(src), n_params, params);
  g_free(specs);
  g_free(params);

  return dst;
}

// Print, clear, print again.
static void psp (Point *p)
{
  g_print("OBJECT %p\n", p);
  g_print("  Before clearing:\n");
  point_print(p);
  g_signal_emit_by_name(p, "clear");
  g_print("  After clearing:\n");
  point_print(p);
  g_print("  ...\n");
}

int main(int argc, char **argv)
{
  g_type_init();

  GArray *array = g_array_new(FALSE, FALSE, sizeof(gint));
  gint val1 = 33, val2 = 44;
  g_array_append_val(array, val1);
  g_array_append_val(array, val2);
  g_array_append_val(array, val1);

  GObject *p1 = g_object_new(TYPE_POINT, "x", 5, "y", 10, "a", array, NULL);
  GObject *p2 = g_object_clone(p1); //< Note: garray uses ref/unref for copy/free.

  psp(POINT(p1));

  g_print("Appending value '%d' to array.\n", val2);
  g_print("  ...\n");
  g_array_append_val(array, val2);

  psp(POINT(p2));

  g_clear_object(&p1);
  g_clear_object(&p2);

  return 0;
}
В качестве объектов в примере взят простенький класс Point с гитхаба за авторством Jacob Gelbman. Код point.h:
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance
#ifndef __POINT_H__
#define __POINT_H__
#include <glib.h>
#include <glib-object.h>

#define TYPE_POINT           (point_get_type             ())
#define POINT(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), TYPE_POINT, Point))
#define POINT_CLASS(cls)     (G_TYPE_CHECK_CLASS_CAST    ((cls), TYPE_POINT, PointClass))
#define IS_POINT(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_POINT))
#define IS_POINT_CLASS(cls)  (G_TYPE_CHECK_CLASS_TYPE    ((cls), TYPE_POINT))
#define POINT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS  ((obj), TYPE_POINT, PointClass))

typedef struct _PointClass PointClass;
typedef struct _Point      Point;

struct _PointClass
{
  GObjectClass parent;
  void (*clear) (Point *self);
};

struct _Point
{
  GObject parent;
  gint    x;
  gint    y;
  GArray *array;
};

GType point_get_type(void);
void  point_print(Point *self);

#endif /*__POINT_H__*/

Код point.c:
// https://github.com/zorgnax/gobject-examples/tree/master/02-point-inheritance
#include <glib-object.h>
#include "point.h"

G_DEFINE_TYPE(Point, point, G_TYPE_OBJECT);

enum
{
  PROP_0,
  PROP_ARRAY,
  PROP_X,
  PROP_Y
};

enum
{
  CLEAR,
  LAST_SIGNAL
};

static guint point_signals[LAST_SIGNAL] = {0};


void point_print (Point *self)
{
  g_print("    x: %d, y: %d\n", self->x, self->y);

  g_print("    ar:");
    if(self->array)
    {
      guint i;
      for(i = 0; i < self->array->len; i++)
        g_print(" %d", g_array_index(self->array, gint, i));
    }
    else
      g_print("null");
  g_print("\n");
}

static void point_clear (Point *self)
{
  g_object_set(self, "x", 0, "y", 0, "a", NULL, NULL);
}

static void point_get_property(GObject *obj, guint prop_id, GValue *value, GParamSpec *pspec)
{
  Point *point = POINT(obj);

  switch (prop_id)
  {
    case PROP_ARRAY:
      g_value_set_boxed(value, point->array);
    break;

    case PROP_X:
      g_value_set_int(value, point->x);
    break;

    case PROP_Y:
      g_value_set_int(value, point->y);
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
    break;
  }
}

static void point_set_property(GObject *obj, guint prop_id, const GValue *value, GParamSpec *pspec)
{
  Point *point = POINT(obj);

  switch (prop_id)
  {
    case PROP_ARRAY:
      if(point->array) g_array_unref(point->array);
      point->array = g_value_dup_boxed(value);
    break;

    case PROP_X:
      point->x = g_value_get_int(value);
    break;

    case PROP_Y:
      point->y = g_value_get_int(value);
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, pspec);
    break;
  }
}

static void point_finalize(GObject *point)
{
  if(POINT(point)->array) g_array_unref(POINT(point)->array);

  G_OBJECT_CLASS(point_parent_class)->finalize(point);
}

static void point_class_init (PointClass *cls)
{
  GObjectClass *g_object_class = G_OBJECT_CLASS(cls);
  GParamSpec *array_param;
  GParamSpec *x_param;
  GParamSpec *y_param;

  g_object_class->get_property = point_get_property;
  g_object_class->set_property = point_set_property;
  g_object_class->finalize = point_finalize;

  cls->clear = point_clear;

  array_param = g_param_spec_boxed(
    "a", "a", "some int array",
    G_TYPE_ARRAY,
    G_PARAM_READWRITE);

  x_param = g_param_spec_int(
    "x", "x", "x loc of point",
    INT_MIN, /* => */ INT_MAX,
    0,
    G_PARAM_READWRITE);

  y_param = g_param_spec_int(
    "y", "y", "y loc of point",
    INT_MIN, /* => */ INT_MAX,
    0,
    G_PARAM_READWRITE);

  g_object_class_install_property(
    g_object_class,
    PROP_ARRAY,
    array_param);

  g_object_class_install_property(
    g_object_class,
    PROP_X,
    x_param);

  g_object_class_install_property(
    g_object_class,
    PROP_Y,
    y_param);

  point_signals[CLEAR] = g_signal_new(
    "clear",                               /* signal_name */
    TYPE_POINT,                            /* itype */
    G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, /* signal_flags */
    G_STRUCT_OFFSET(PointClass, clear),    /* class_offset */
    NULL,                                  /* accumulator */
    NULL,                                  /* accu_data */
    g_cclosure_marshal_VOID__VOID,         /* c_marshaller */
    G_TYPE_NONE,                           /* return_type */
    0);                                    /* n_params */
}

static void point_init(Point *point)
{
  point->array = NULL;
}

"Хозяйке на заметку": прочитать данные (переменные, целые, с плавающей точкой и т.п.) из бинарного файла на Vala можно и нужно с помощью GVariant, например так:


{
    uint8[] buf = new uint8[sizeof(uint32) + sizeof(uint32) +
        sizeof(double) + sizeof(double)];

    {
        var data_stream = new DataInputStream(file.read());
        data_stream.read(buf);
    }

    Variant variant = Variant.new_from_data<uint8>(
        new VariantType("(uudd)"), buf, true);

    uint32 int1 = variant.get_child_value(0).get_uint32();
    uint32 int2 = variant.get_child_value(1).get_uint32();
    double double1 = variant.get_child_value(2).get_double();
    double double2 = variant.get_child_value(3).get_double();

    stdout.printf("int 1 = %u\n", int1);
    stdout.printf("int 2 = %u\n", int2);
    stdout.printf("double 1 = %f\n", double1);
    stdout.printf("double 2 = %f\n", double2);
}



 

Copyright © 2007 DamnSmallBlog. Content is licensed under Creative Commons Attribution-Noncommercial.

Design: GeckoandFly and Blogcrowds.