[推荐4]GObject FAQ 定义新类快速入门[包括继承,初始化,销毁等]

引用自:

http://irrepupavel.com/documents/gtk/gobject-faq.html

 

GObject FAQ

    * Preamble
    * 1. How do I implement a class?
    * 2. How do I define, initialize, finalize and access public instance variables?
    * 3. How do I define, initialize, finalize and access private instance variables?
    * 4. How do I define public and private methods?
    * 5. How do I define an abstract class? And abstract methods?
    * 6. How do I extend a class and override a method?
    * 7. How do I define an Interface?
    * 8. How do I implement an interface in a class?
    * 9. How do I override an interface's method already implemented in the superclass of my class?
    * 10. What are properties and how do I implement them?
    * 11. How do I use properties?
    * 12. How to define a class contructor?
    * ChangeLog

Version: 0.1

Last Updated: 26-08-04

Preamble

This is document is intended for the reader which is already familiar with Object Oriented Programming but needs to find a way on how things work on GObject. It is not intended to readers who want to know why things are done the way they are. Think of it as a quick reference or a cookbook. If you find any errors be so kind as to point them out and send me an email.

IMPORTANT: This code only works with GLib >= 2.4

1. How do I implement a class?

Considering that you are going to name your class name Human and want it to belong to Nature namespace, in Java it's the equivalent of package and in Python of module, you have to add this boiler code in the header:

#ifndef __NATURE_HUMAN_H__
#define __NATURE_HUMAN_H__
G_BEGIN_DECLS
#define NATURE_TYPE_HUMAN            (nature_human_get_type (void))
#define NATURE_HUMAN(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), NATURE_TYPE_HUMAN, NatureHuman)
#define NATURE_HUMAN_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), NATURE_TYPE_HUMAN, NatureHumanClass))
#define NATURE_IS_HUMAN(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NATURE_TYPE_HUMAN))
#define NATURE_IS_HUMAN_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NATURE_TYPE_HUMAN))
#define NATURE_GET_HUMAN_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), NATURE_TYPE_HUMAN, NatureHumanClass))
typedef struct _NatureHuman NatureHuman;
typedef struct _NatureHumanClass NatureHumanClass;
struct _NatureHuman {
    GObject parent;
    /* define public instance variables here */
};
struct _NatureHumanClass {
    GObjectClass parent_class;
    /* define vtable methods and signals here */
};
GType nature_human_get_type (void);
NatureHuman* nature_human_new (void);
G_END_DECLS
#endif /* __NATURE_HUMAN_H__ */

The first macros and functions are the standard convention for GObject objects and you should keep on following it, if you want your classes to be easely usuable by other Gtk+ or simply GObject users. Now in the source file you just need to define the following:

#include "nature_human.h"
G_DEFINE_TYPE (NatureHuman, nature_human, G_TYPE_OBJECT)
/* we need to define these two functions */
static void nature_human_init (NatureHuman *self) {}
static void nature_human_class_init (NatureHumanClass *klass) {}
NatureHuman* nature_human_new (void)
{
    return NATURE_HUMAN (g_object_new (NATURE_TYPE_HUMAN, NULL));
}

2. How do I define, initialize, finalize and access public instance variables?

Just add a variable definition in the instance struct definition as follows in the header:

struct _NatureHuman {
    GObject parent;
    gint age;
};

To initialize and finalize the variables you have to implement the following functions in the source file:

static void nature_human_finalize (NatureHuman *self);
static void nature_class_init (NatureHumanClass *klass)
{
    /* bind finalizing function */
    G_OBJECT_CLASS (klass)->finalize = (GObjectFinalizeFunc) nature_human_finalize;
}
static void nature_human_init (NatureHuman *self)
{
    self->age = 0;
}
static void nature_human_finalize (NatureHuman *self)
{
    /* in finalization we'll just reset the age */
    self->age = 0;
}

3. How do I define, initialize, finalize and access private instance variables?

First you have to define your private structure, add the necessary code to access it and then it's the same as with public variables:

/* macro for simplifying getting the private structure */
#define NATURE_HUMAN_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), NATURE_TYPE_HUMAN, NatureHumanPrivate))
typedef struct _NatureHumanPrivate NatureHumanPrivate;
struct _NatureHumanPrivate {
    gchar *dna;
};
static void nature_human_class_init (NatureHumanClass *klass)
{
    /* register private structure */
    g_type_class_add_private(klass, sizeof(NatureHumanPrivate));
    /* bind finalizing function */
    G_OBJECT_CLASS (klass)->finalize = (GObjectFinalizeFunc) nature_human_finalize;
}
static void nature_human_init (NatureHuman *self)
{
    NATURE_HUMAN_GET_PRIVATE (self)->dna = NULL;
}
static void nature_human_finalize (NatureHuman *self)
{
    NatureHumanPrivate *p;
    p = NATURE_HUMAN_GET_PRIVATE (self);
    /* free alloc'ed data */
    if (p->dna)
        g_free (dna);
}

4. How do I define public and private methods?

Public methods are very simple to implement. Following the standard conventions you must name as usual the methods with $namespace_$class_$methodname. So define the prototype in the header and the definition in the source file.

Header file:

void nature_human_eat (NatureHuman *self, NatureFood *food);

Source file:

void nature_human_eat (NatureHuman *self, NatureFood *food)
{
    /* do something */
}

Privates methods follow the same convetion but have the static modifier and the prototype is defined in the source instead of the header file.

5. How do I define an abstract class? And abstract methods?

Abstract classes are as easy as changing the G_DEFINE_TYPE macro to G_DEFINE_ABSTRACT_TYPE macro:

G_DEFINE_ABSTRACT_TYPE (NatureHuman, nature_human, G_TYPE_OBJECT)

To create abstract methods first you need to create extendable methods. Extendable methods are defined in the class structure and therefore can be binded and altered in child classes. You need to change the header's class structure as follows:

struct _NatureHumanClass {
    GObjectClass parent_class;
    void (*eat) (NatureHuman *self, NatureFood *food);
};

Now you have to make call the structure's function whenever the user calls your `nature_human_eat`, making it as a simple wrapper function to the real, implemented one. Therefore you have to change your source file's nature_human_eat implementation:

void fiction_super_human_eat (NatureHuman *self, NatureFood *food)
{
    /* call super */
    NATURE_HUMAN_CLAS (fiction_super_human_parent_class)->eat (self, food);
    /* do something else */
}

6. How do I extend a class and override a method?

In the source file you have to redefine the G_DEFINE_TYPE macro's third argument with the type of the class you intend to extend. Then you have to bind, in class initialization function, the functions you wish to override. Finally, in each function you wish to override, you can chain up and call the super class method:

#include <nature_human.h>
G_DEFINE_TYPE (FictionSuperHuman, fiction_super_human, NATURE_TYPE_HUMAN)
static void fiction_super_human_eat (NatureHuman *self, NatureFood *food);
static void fiction_super_human_class_init (FictionHumanClass *klass)
{
    NATURE_HUMAN_CLASS (fiction_super_human_parent_class)->eat = fiction_super_human_eat;
}
void fiction_super_human_eat (NatureHuman *self, NatureFood *food)
{
    /* do something else */
}

If our super class Human didn't have the eat method abstract we coul chain up and call the super method by doing the following:

void fiction_super_human_eat (NatureHuman *self, NatureFood *food)
{
    /* call super's method */
    NATURE_HUMAN_CLAS (fiction_super_human_parent_class)->eat (self, food);
    /* do something else */
}

7. How do I define an Interface?

Intefaces have all the same boiler code in the header file as normal classes yet all methods must be extendable. Also the class struct derives from GTypeInterface instead of GObjectClass. The instance structure is just a typedef to a pointer, because there are not interface instances.

#ifndef __MOTION_MOVEABLE_H__
#define __MOTION_MOVEABLE_H__
G_BEGIN_DECLS
#define MOTION_TYPE_MOVEABLE            (motion_moveable_get_type (void))
#define MOTION_MOVEABLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), MOTION_TYPE_MOVEABLE, MotionMoveable)
#define MOTION_MOVEABLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), MOTION_TYPE_MOVEABLE, MotionMoveableClass))
#define MOTION_IS_MOVEABLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MOTION_TYPE_MOVEABLE))
#define MOTION_IS_MOVEABLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MOTION_TYPE_MOVEABLE))
#define MOTION_MOVEABLE_GET_IFACE(obj)  (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MOTION_TYPE_MOVEABLE, MotionMoveableClass))
typedef struct _MotionMoveable MotionMoveable;
typedef struct _MotionMoveableIface MotionMoveableIface;
struct _MotionMoveableIface {
    GTypeInterface parent_iface;
    void (*move) (MotionMoveableIface *iface);
};
GType motion_moveable_get_type (void);
void motion_moveable_move (MotionMoveable *self);
G_END_DECLS
#endif /* __MOTION_MOVEABLE_H__ */

On the source file we have to do the follwing:

#include "motion_moveable.h"
/* we need to define the get_type function */
GType motion_moveable_get_type()
{
    static GType object_type = 0;
    if (!object_type) {
        static const GTypeInfo object_info = {
            sizeof(MotionMoveableIface),
            NULL,    /* base init */
            NULL,    /* base finalize */
        };
        object_type =
            g_type_register_static(G_TYPE_INTERFACE,
                       "MotionMoveable",
                       &object_info, 0);
    }
    return object_type;
}
void motion_moveable_move (MotionMoveable *self)
{
    MOTION_MOVEABLE_GET_IFACE (self)->move (self);
}

As you can see, unfortunately, we can't use the G_DEFINE_TYPE macro to define interface's type.

8. How do I implement an interface in a class?

Using the handy macro G_DEFINE_TYPE_WITH_CODE just add the macro G_IMPLEMENT_INTERFACE as the code. Here is the full example:

G_DEFINE_TYPE_WITH_CODE (NatureHuman, nature_human, G_TYPE_OBJECT,
                          G_IMPLEMENT_INTERFACE (MOTION_MOVEABLE,
                                                 nature_human_movable_init))

In this example we have an object Human of namespace Nature which implements the Moveable interface, which has Motion namespace. nature_human_moveable_init is the initialization function where you bind your implemented class methods.

Let's imagine the class Moveable has only one method named move. In this example we should implement nature_moveable_init like so:

static void nature_human_move (MotionMoveable *self);
static void nature_human_movable_init(MotionMoveableIface *iface, gpointer user_data)
{
    iface->move = nature_human_move;
}

9. How do I override an interface's method already implemented in the superclass of my class?

Call G_IMPLEMENT_INTERFACE macro again and only bind the methods you intend to implement.

Example:

static gpointer moveable_parent_class = NULL;
G_DEFINE_TYPE_WITH_CODE (FictionSuperHuman, fiction_super_human, NATURE_TYPE_HUMAN,
                          G_IMPLEMENT_INTERFACE (MOTION_MOVEABLE,
                                                 fiction_super_human_movable_init))
static void fiction_super_human_move (MotionMoveable *self);
static void fiction_super_human_movable_init(MotionMoveableIface *iface, gpointer user_data)
{
    moveable_parent_class = g_type_class_peek_parent (iface);
    iface->move = nature_human_move;
}
static void fiction_super_human_move (MotionMoveable *self)
{
    /* let's chain up and call super first */
    MOTION_MOVEABLE_CLASS (moveable_parent_class)->move (self);
    /* do something */
}

In this example we have a class SuperHuman which extends class Human and implements method move from interface Moveable which was already implemented. Also we have defined in the interface initialization the parent class so that we can call super's methods. We then call the super method move by casting the pointer to the moveable_parent_class.

10. What are properties and how do I implement them?

Properties are kind of variable wrappers which adds a mechanist for controlling it's access. Properties also add introspection to your class, therefore you should turn every variable (wrapped by get and set) into properties. For simplicity's sake we'll implement a property that wraps a public variable.

Still in the NauteHuman example we'll wrap age as a property. Changes are all done in the source file.

static void nature_human_set_property (GObject * object, guint prop_id,
                     const GValue * value,
                     GParamSpec * pspec);
static void nature_human_get_property (GObject * object, guint prop_id,
                     GValue * value,
                     GParamSpec * pspec);
enum {
    PROP_0,
    PROP_AGE
};
static void nature_human_class_init (NatureHumanClass *klass)
{
    GObjectClass *g_klass = G_OBJECT_CLAS (klass);
    /* bind handlers which wrap properties access */
    g_klass->set_property = nature_human_set_property;
    g_klass->get_property = nature_human_get_property;
    g_object_class_install_property (g_klass, PROP_AGE,
            /* this is the special contructor for
             * integer type properties, you can
             * control the range of valid values.
             */
            g_param_spec_int ("age",
                "Human's age",                         /* a small description of the property */
                "The human age can determine the "
                "life he has remaining."
                " Usually it will not go over 120.",   /* the description of the property */
                0,                                     /* the lower value accepted */
                G_MAXINT,                              /* the higher value accepted */
                0,                                     /* the default value */
                G_PARAM_READWRITE));                   /* we can get and set values */
}
static void nature_human_set_property(GObject * object, guint prop_id,
                     const GValue * value,
                     GParamSpec * pspec)
{
    NatureHuman *self;
    self = NATURE_HUMAN(object);
    switch (prop_id) {
    case PROP_AGE:
        self->age = g_value_get_int (value);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}
static void nature_human_get_property(GObject * object, guint prop_id,
                     GValue * value,
                     GParamSpec * pspec)
{
    NatureHuman *self;
    self = NATURE_HUMAN(object);
    switch (prop_id) {
    case PROP_AGE:
        g_value_set_int(value, self->age);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

11. How do I use properties?

Properties can be used in two different ways, you can get a property value and set a property value.

To set you have to:

g_object_set (G_OBJECT(an_object), "age", 12, NULL);

The NULL is there because we can actually get and set more then one properties at once. Because the arguments provided are pairs of key/value adding a key with NULL makes the function know we've finished setting (or getting) properties.

g_object_set (G_OBJECT(an_object), "age", 12, "name", "John", NULL);

To get values of a property you just have to pass a pointer to the type you wish to fill.

gchar *name;
gint age;
g_object_get (G_OBJECT(an_object), "age", &age, "name", &name, NULL);

12. How to define a class contructor?

A GObject constructor is called after you issue the g_object_new function. When you call this function you can pass a number of key/value pairs for initializing properties as you would with the g_object_set method.

GObject *john = g_object_new (NATURE_TYPE_HUMAN, "name", "John", "age", 15, NULL);

However there are certain cases where you want to explicitly tell the user that certain properties must be initialized with a certain value. To do this we need to set a special flag uppon properties contructions:

g_object_class_install_property (g_klass, PROP_AGE,
        g_param_spec_int ("age",
            "Human's age",
            "The human age can determine the life he has remaining."
            " Usually it will not go over 120.",
            0,
            G_MAXINT,
            0,
            G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

However there are certain cases where you need to specify a constructor argument which is only needed for constructing the argument, in this case we should use:

g_object_class_install_property (g_klass, PROP_AGE,
        g_param_spec_int ("age",
            "Human's age",
            "The human age can determine the life he has remaining."
            " Usually it will not go over 120.",
            0,
            G_MAXINT,
            0,
            G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

This covers the way to send arguments through object creation, the constructor. But to actually implement a constructor we need to bind one more function:

static GObject *nature_human_constructor (GType                  type,
                                 guint                  n_construct_properties,
                                 GObjectConstructParam *construct_properties);
static void nature_human_class_init (NatureHumanClass *klass)
{
    G_OBJECT_CLASS (klass)->contructor = nature_human_constructor;
}
static GObject *nature_human_constructor (GType                  type,
                                 guint                  n_construct_properties,
                                 GObjectConstructParam *construct_properties)
{
    /*
     * in this part of the code we have control over:
     * - the type of object the user intends to create
     * - the number of arguments the user has passed to g_object_new
     * - the spec and the value each argument has
     */
    gint i;
    for (i = 0; i < n_construct_properties; i++) {
        /* we will print the the properties' names and if it's
         * an integer we'll print it's value
         */
        g_print ("%s", construct_properties[i].pspec->name);
        if (G_VALUE_HOLDS_INT (construct_properties[i].value))
            g_print(": %d", g_value_get_int (construct_properties[i].value));
        g_print ("\n");
    }
}

The n_construct_properties value represents the number of properties registred with G_PARAM_CONSTRUCT or G_PARAM_CONSTRUCT_ONLY, only this type of properties will be passed to the constructor, normal properties will not appear in the constructor's construct_properties.

On last important note, the _init method is called after the _constructor. Therefore the _contructor is used to control the arguments (type and number) sent through object construction whereas _init is used to control the values which assumes all properties/arguments the object needs toare correctly initialized.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值