mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-28 16:36:14 +01:00
1289 lines
41 KiB
Markdown
1289 lines
41 KiB
Markdown
|
Title: GObject Tutorial
|
|||
|
|
|||
|
# GObject Tutorial
|
|||
|
|
|||
|
## How to define and implement a new GObject
|
|||
|
|
|||
|
This document focuses on the implementation of a subtype of GObject, for
|
|||
|
example to create a custom class hierarchy, or to subclass a GTK widget.
|
|||
|
|
|||
|
Throughout the chapter, a running example of a file viewer program is used,
|
|||
|
which has a `ViewerFile` class to represent a single file being viewed, and
|
|||
|
various derived classes for different types of files with special
|
|||
|
functionality, such as audio files. The example application also supports
|
|||
|
editing files (for example, to tweak a photo being viewed), using a
|
|||
|
`ViewerEditable` interface.
|
|||
|
|
|||
|
### Boilerplate header code
|
|||
|
|
|||
|
The first step before writing the code for your GObject is to write the
|
|||
|
type's header which contains the needed type, function and macro
|
|||
|
definitions. Each of these elements is nothing but a convention which is
|
|||
|
followed by almost all users of GObject, and has been refined over multiple
|
|||
|
years of experience developing GObject-based code. If you are writing a
|
|||
|
library, it is particularly important for you to adhere closely to these
|
|||
|
conventions; users of your library will assume that you have. Even if not
|
|||
|
writing a library, it will help other people who want to work on your
|
|||
|
project.
|
|||
|
|
|||
|
Pick a name convention for your headers and source code and stick to it:
|
|||
|
|
|||
|
- use a dash to separate the prefix from the typename: `viewer-file.h` and
|
|||
|
`viewer-file.c` (this is the convention used by most GNOME libraries and
|
|||
|
applications)
|
|||
|
- use an underscore to separate the prefix from the typename:
|
|||
|
`viewer_file.h` and `viewer_file.c`
|
|||
|
- do not separate the prefix from the typename: `viewerfile.h` and
|
|||
|
`viewerfile.c` (this is the convention used by GTK)
|
|||
|
|
|||
|
Some people like the first two solutions better: it makes reading file names
|
|||
|
easier for those with poor eyesight.
|
|||
|
|
|||
|
The basic conventions for any header which exposes a GType are described in
|
|||
|
the section of the Type system introduction called
|
|||
|
["Conventions"](concepts.html#conventions).
|
|||
|
|
|||
|
If you want to declare a type named "file" in the namespace "viewer", name
|
|||
|
the type instance `ViewerFile` and its class `ViewerFileClass` (names are
|
|||
|
case sensitive). The recommended method of declaring a type differs based on
|
|||
|
whether the type is final or derivable.
|
|||
|
|
|||
|
Final types cannot be subclassed further, and should be the default choice
|
|||
|
for new types—changing a final type to be derivable is always a change that
|
|||
|
will be compatible with existing uses of the code, but the converse will
|
|||
|
often cause problems. Final types are declared using the
|
|||
|
`G_DECLARE_FINAL_TYPE` macro, and require a structure to hold the instance
|
|||
|
data to be declared in the source code (not the header file).
|
|||
|
|
|||
|
```c
|
|||
|
/*
|
|||
|
* Copyright/Licensing information.
|
|||
|
*/
|
|||
|
|
|||
|
/* inclusion guard */
|
|||
|
#pragma once
|
|||
|
|
|||
|
#include <glib-object.h>
|
|||
|
|
|||
|
/*
|
|||
|
* Potentially, include other headers on which this header depends.
|
|||
|
*/
|
|||
|
|
|||
|
G_BEGIN_DECLS
|
|||
|
|
|||
|
/*
|
|||
|
* Type declaration.
|
|||
|
*/
|
|||
|
#define VIEWER_TYPE_FILE viewer_file_get_type()
|
|||
|
G_DECLARE_FINAL_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
|
|||
|
|
|||
|
/*
|
|||
|
* Method definitions.
|
|||
|
*/
|
|||
|
ViewerFile *viewer_file_new (void);
|
|||
|
|
|||
|
G_END_DECLS
|
|||
|
```
|
|||
|
|
|||
|
Derivable types can be subclassed further, and their class and instance
|
|||
|
structures form part of the public API which must not be changed if API
|
|||
|
stability is cared about. They are declared using the
|
|||
|
`G_DECLARE_DERIVABLE_TYPE` macro:
|
|||
|
|
|||
|
```c
|
|||
|
/*
|
|||
|
* Copyright/Licensing information.
|
|||
|
*/
|
|||
|
|
|||
|
/* inclusion guard */
|
|||
|
#pragma once
|
|||
|
|
|||
|
#include <glib-object.h>
|
|||
|
|
|||
|
/*
|
|||
|
* Potentially, include other headers on which this header depends.
|
|||
|
*/
|
|||
|
|
|||
|
G_BEGIN_DECLS
|
|||
|
|
|||
|
/*
|
|||
|
* Type declaration.
|
|||
|
*/
|
|||
|
#define VIEWER_TYPE_FILE viewer_file_get_type()
|
|||
|
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
|
|||
|
|
|||
|
struct _ViewerFileClass
|
|||
|
{
|
|||
|
GObjectClass parent_class;
|
|||
|
|
|||
|
/* Class virtual function fields. */
|
|||
|
void (* open) (ViewerFile *file,
|
|||
|
GError **error);
|
|||
|
|
|||
|
/* Padding to allow adding up to 12 new virtual functions without
|
|||
|
* breaking ABI. */
|
|||
|
gpointer padding[12];
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Method definitions.
|
|||
|
*/
|
|||
|
ViewerFile *viewer_file_new (void);
|
|||
|
|
|||
|
G_END_DECLS
|
|||
|
```
|
|||
|
|
|||
|
The convention for header includes is to add the minimum number of
|
|||
|
`#include` directives to the top of your headers needed to compile that
|
|||
|
header. This allows client code to simply `#include "viewer-file.h"`,
|
|||
|
without needing to know the prerequisites for `viewer-file.h`.
|
|||
|
|
|||
|
### Boilerplate code
|
|||
|
|
|||
|
In your code, the first step is to `#include` the needed headers:
|
|||
|
|
|||
|
```c
|
|||
|
/*
|
|||
|
* Copyright/Licensing information
|
|||
|
*/
|
|||
|
|
|||
|
#include "viewer-file.h"
|
|||
|
|
|||
|
/* Private structure definition. */
|
|||
|
typedef struct {
|
|||
|
char *filename;
|
|||
|
|
|||
|
/* other private fields */
|
|||
|
} ViewerFilePrivate;
|
|||
|
|
|||
|
/*
|
|||
|
* forward definitions
|
|||
|
*/
|
|||
|
```
|
|||
|
|
|||
|
If the class is being declared as final using `G_DECLARE_FINAL_TYPE`, its instance structure should be defined in the C file:
|
|||
|
|
|||
|
```c
|
|||
|
struct _ViewerFile
|
|||
|
{
|
|||
|
GObject parent_instance;
|
|||
|
|
|||
|
/* Other members, including private data. */
|
|||
|
};
|
|||
|
```
|
|||
|
|
|||
|
Call the `G_DEFINE_TYPE` macro (or `G_DEFINE_TYPE_WITH_PRIVATE` if your
|
|||
|
class needs private data—final types do not need private data) using the
|
|||
|
name of the type, the prefix of the functions and the parent GType to reduce
|
|||
|
the amount of boilerplate needed. This macro will:
|
|||
|
|
|||
|
- implement the `viewer_file_get_type` function
|
|||
|
- define a parent class pointer accessible from the whole `.c` file
|
|||
|
- add private instance data to the type (if using `G_DEFINE_TYPE_WITH_PRIVATE`)
|
|||
|
|
|||
|
If the class has been declared as final using `G_DECLARE_FINAL_TYPE` private
|
|||
|
data should be placed in the instance structure, `ViewerFile`, and
|
|||
|
`G_DEFINE_TYPE` should be used instead of `G_DEFINE_TYPE_WITH_PRIVATE`. The
|
|||
|
instance structure for a final class is not exposed publicly, and is not
|
|||
|
embedded in the instance structures of any derived classes (because the
|
|||
|
class is final); so its size can vary without causing incompatibilities for
|
|||
|
code which uses the class. Conversely, private data for derivable classes
|
|||
|
must be included in a private structure, and `G_DEFINE_TYPE_WITH_PRIVATE`
|
|||
|
must be used.
|
|||
|
|
|||
|
```c
|
|||
|
G_DEFINE_TYPE (ViewerFile, viewer_file, G_TYPE_OBJECT)
|
|||
|
```
|
|||
|
|
|||
|
or
|
|||
|
|
|||
|
```c
|
|||
|
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)
|
|||
|
```
|
|||
|
|
|||
|
It is also possible to use the `G_DEFINE_TYPE_WITH_CODE` macro to control
|
|||
|
the `get_type` function implementation — for instance, to add a call to the
|
|||
|
`G_IMPLEMENT_INTERFACE` macro to implement an interface.
|
|||
|
|
|||
|
### Object construction
|
|||
|
|
|||
|
People often get confused when trying to construct their GObjects because of
|
|||
|
the sheer number of different ways to hook into the objects's construction
|
|||
|
process: it is difficult to figure which is the correct, recommended way.
|
|||
|
|
|||
|
The [documentation on object
|
|||
|
instantiation](concepts.html#object-instantiation) shows what user-provided
|
|||
|
functions are invoked during object instantiation and in which order they
|
|||
|
are invoked. A user looking for the equivalent of the simple C++ constructor
|
|||
|
function should use the `instance_init` method. It will be invoked after all
|
|||
|
the parents’ `instance_init` functions have been invoked. It cannot take
|
|||
|
arbitrary construction parameters (as in C++) but if your object needs
|
|||
|
arbitrary parameters to complete initialization, you can use construction
|
|||
|
properties.
|
|||
|
|
|||
|
Construction properties will be set only after all `instance_init` functions have run. No object reference will be returned to the client of `g_object_new()` until all the construction properties have been set.
|
|||
|
|
|||
|
It is important to note that object construction cannot ever fail. If you
|
|||
|
require a fallible GObject construction, you can use the `GInitable` and
|
|||
|
`GAsyncInitable` interfaces provided by the GIO library.
|
|||
|
|
|||
|
You should write the following code first:
|
|||
|
|
|||
|
```c
|
|||
|
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_init (ViewerFile *self)
|
|||
|
{
|
|||
|
ViewerFilePrivate *priv = viewer_file_get_instance_private (self);
|
|||
|
|
|||
|
/* initialize all public and private members to reasonable default values.
|
|||
|
* They are all automatically initialized to 0 to begin with. */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If you need special construction properties (with `G_PARAM_CONSTRUCT_ONLY`
|
|||
|
set), install the properties in the `class_init()` function, override the
|
|||
|
`set_property()` and `get_property()` methods of the GObject class, and
|
|||
|
implement them as described by the section called ["Object
|
|||
|
properties"](concepts.html#object-properties).
|
|||
|
|
|||
|
Property identifiers must start from 1, as 0 is reserved for internal use by GObject.
|
|||
|
|
|||
|
```c
|
|||
|
enum
|
|||
|
{
|
|||
|
PROP_FILENAME = 1,
|
|||
|
PROP_ZOOM_LEVEL,
|
|||
|
N_PROPERTIES
|
|||
|
};
|
|||
|
|
|||
|
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|||
|
|
|||
|
object_class->set_property = viewer_file_set_property;
|
|||
|
object_class->get_property = viewer_file_get_property;
|
|||
|
|
|||
|
obj_properties[PROP_FILENAME] =
|
|||
|
g_param_spec_string ("filename",
|
|||
|
"Filename",
|
|||
|
"Name of the file to load and display from.",
|
|||
|
NULL /* default value */,
|
|||
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
|
|||
|
|
|||
|
obj_properties[PROP_ZOOM_LEVEL] =
|
|||
|
g_param_spec_uint ("zoom-level",
|
|||
|
"Zoom level",
|
|||
|
"Zoom level to view the file at.",
|
|||
|
0 /* minimum value */,
|
|||
|
10 /* maximum value */,
|
|||
|
2 /* default value */,
|
|||
|
G_PARAM_READWRITE);
|
|||
|
|
|||
|
g_object_class_install_properties (object_class,
|
|||
|
N_PROPERTIES,
|
|||
|
obj_properties);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If you need this, make sure you can build and run code similar to the code
|
|||
|
shown above. Also, make sure your construct properties can be set without
|
|||
|
side effects during construction.
|
|||
|
|
|||
|
Some people sometimes need to complete the initialization of an instance of
|
|||
|
a type only after the properties passed to the constructors have been set.
|
|||
|
This is possible through the use of the `constructor()` class method as
|
|||
|
described in the section called "Object instantiation" or, more simply,
|
|||
|
using the `constructed()` class method. Note that the `constructed()` virtual
|
|||
|
function will only be invoked after the properties marked as
|
|||
|
`G_PARAM_CONSTRUCT_ONLY` or `G_PARAM_CONSTRUCT` have been consumed, but before
|
|||
|
the regular properties passed to `g_object_new()` have been set.
|
|||
|
|
|||
|
### Object destruction
|
|||
|
|
|||
|
Again, it is often difficult to figure out which mechanism to use to hook
|
|||
|
into the object's destruction process: when the last `g_object_unref()` function
|
|||
|
call is made, a lot of things happen as described in [the "Object memory
|
|||
|
management" section](concepts.html#object-memory-management) of the
|
|||
|
documentation.
|
|||
|
|
|||
|
The destruction process of your object is in two phases: dispose and
|
|||
|
finalize. This split is necessary to handle potential cycles due to the
|
|||
|
nature of the reference counting mechanism used by GObject, as well as
|
|||
|
dealing with temporary revival of instances in case of signal emission
|
|||
|
during the destruction sequence.
|
|||
|
|
|||
|
```c
|
|||
|
struct _ViewerFilePrivate
|
|||
|
{
|
|||
|
gchar *filename;
|
|||
|
guint zoom_level;
|
|||
|
|
|||
|
GInputStream *input_stream;
|
|||
|
};
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_PRIVATE (ViewerFile, viewer_file, G_TYPE_OBJECT)
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_dispose (GObject *gobject)
|
|||
|
{
|
|||
|
ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (gobject));
|
|||
|
|
|||
|
/* In dispose(), you are supposed to free all types referenced from this
|
|||
|
* object which might themselves hold a reference to self. Generally,
|
|||
|
* the most simple solution is to unref all members on which you own a
|
|||
|
* reference.
|
|||
|
*/
|
|||
|
|
|||
|
/* dispose() might be called multiple times, so we must guard against
|
|||
|
* calling g_object_unref() on an invalid GObject by setting the member
|
|||
|
* NULL; g_clear_object() does this for us.
|
|||
|
*/
|
|||
|
g_clear_object (&priv->input_stream);
|
|||
|
|
|||
|
/* Always chain up to the parent class; there is no need to check if
|
|||
|
* the parent class implements the dispose() virtual function: it is
|
|||
|
* always guaranteed to do so
|
|||
|
*/
|
|||
|
G_OBJECT_CLASS (viewer_file_parent_class)->dispose (gobject);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_finalize (GObject *gobject)
|
|||
|
{
|
|||
|
ViewerFilePrivate *priv = viewer_file_get_instance_private (VIEWER_FILE (gobject));
|
|||
|
|
|||
|
g_free (priv->filename);
|
|||
|
|
|||
|
/* Always chain up to the parent class; as with dispose(), finalize()
|
|||
|
* is guaranteed to exist on the parent's class virtual function table
|
|||
|
*/
|
|||
|
G_OBJECT_CLASS (viewer_file_parent_class)->finalize (gobject);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|||
|
|
|||
|
object_class->dispose = viewer_file_dispose;
|
|||
|
object_class->finalize = viewer_file_finalize;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_init (ViewerFile *self);
|
|||
|
{
|
|||
|
ViewerFilePrivate *priv = viewer_file_get_instance_private (self);
|
|||
|
|
|||
|
priv->input_stream = g_object_new (VIEWER_TYPE_INPUT_STREAM, NULL);
|
|||
|
priv->filename = /* would be set as a property */;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
It is possible that object methods might be invoked after dispose is run and
|
|||
|
before finalize runs. GObject does not consider this to be a program error:
|
|||
|
you must gracefully detect this and neither crash nor warn the user, by
|
|||
|
having a disposed instance revert to an inert state.
|
|||
|
|
|||
|
### Object methods
|
|||
|
|
|||
|
Just as with C++, there are many different ways to define object methods and
|
|||
|
extend them: the following list and sections draw on C++ vocabulary.
|
|||
|
(Readers are expected to know basic C++ concepts. Those who have not had to
|
|||
|
write C++ code recently can refer to a [C++
|
|||
|
tutorial](http://www.cplusplus.com/doc/tutorial/) to refresh their
|
|||
|
memories.)
|
|||
|
|
|||
|
- non-virtual public methods,
|
|||
|
- virtual public methods and
|
|||
|
- virtual private methods
|
|||
|
- non-virtual private methods
|
|||
|
|
|||
|
#### Non-Virtual Methods
|
|||
|
|
|||
|
These are the simplest, providing a simple method which acts on the object.
|
|||
|
Provide a function prototype in the header and an implementation of that
|
|||
|
prototype in the source file.
|
|||
|
|
|||
|
```c
|
|||
|
/* declaration in the header. */
|
|||
|
void viewer_file_open (ViewerFile *self,
|
|||
|
GError **error);
|
|||
|
```
|
|||
|
|
|||
|
```c
|
|||
|
/* implementation in the source file */
|
|||
|
void
|
|||
|
viewer_file_open (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
/* do stuff here. */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### Virtual public methods
|
|||
|
|
|||
|
This is the preferred way to create GObjects with overridable methods:
|
|||
|
|
|||
|
- define the common method and its virtual function in the class structure
|
|||
|
in the public header
|
|||
|
- define the common method in the header file and implement it in the source
|
|||
|
file
|
|||
|
- implement a base version of the virtual function in the source file and
|
|||
|
initialize the virtual function pointer to this implementation in the
|
|||
|
object’s `class_init` function; or leave it as `NULL` for a ‘pure virtual’
|
|||
|
method which must be overridden by derived classes
|
|||
|
- re-implement the virtual function in each derived class which needs to
|
|||
|
override it
|
|||
|
|
|||
|
Note that virtual functions can only be defined if the class is derivable,
|
|||
|
declared using `G_DECLARE_DERIVABLE_TYPE` so the class structure can be
|
|||
|
defined.
|
|||
|
|
|||
|
```c
|
|||
|
/* declaration in viewer-file.h. */
|
|||
|
#define VIEWER_TYPE_FILE viewer_file_get_type ()
|
|||
|
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
|
|||
|
|
|||
|
struct _ViewerFileClass
|
|||
|
{
|
|||
|
GObjectClass parent_class;
|
|||
|
|
|||
|
/* stuff */
|
|||
|
void (*open) (ViewerFile *self,
|
|||
|
GError **error);
|
|||
|
|
|||
|
/* Padding to allow adding up to 12 new virtual functions without
|
|||
|
* breaking ABI. */
|
|||
|
gpointer padding[12];
|
|||
|
};
|
|||
|
|
|||
|
void viewer_file_open (ViewerFile *self,
|
|||
|
GError **error);
|
|||
|
```
|
|||
|
|
|||
|
```c
|
|||
|
/* implementation in viewer-file.c */
|
|||
|
void
|
|||
|
viewer_file_open (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerFileClass *klass;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
klass = VIEWER_FILE_GET_CLASS (self);
|
|||
|
g_return_if_fail (klass->open != NULL);
|
|||
|
|
|||
|
klass->open (self, error);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The code above simply redirects the open call to the relevant virtual
|
|||
|
function.
|
|||
|
|
|||
|
It is possible to provide a default implementation for this class method in
|
|||
|
the object's `class_init` function: initialize the `klass->open` field to a
|
|||
|
pointer to the actual implementation. By default, class methods that are not
|
|||
|
inherited are initialized to `NULL`, and thus are to be considered "pure
|
|||
|
virtual".
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_file_real_close (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
/* Default implementation for the virtual method. */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
/* this is not necessary, except for demonstration purposes.
|
|||
|
*
|
|||
|
* pure virtual method: mandates implementation in children.
|
|||
|
*/
|
|||
|
klass->open = NULL;
|
|||
|
|
|||
|
/* merely virtual method. */
|
|||
|
klass->close = viewer_file_real_close;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_file_open (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerFileClass *klass;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
klass = VIEWER_FILE_GET_CLASS (self);
|
|||
|
|
|||
|
/* if the method is purely virtual, then it is a good idea to
|
|||
|
* check that it has been overridden before calling it, and,
|
|||
|
* depending on the intent of the class, either ignore it silently
|
|||
|
* or warn the user.
|
|||
|
*/
|
|||
|
g_return_if_fail (klass->open != NULL);
|
|||
|
klass->open (self, error);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_file_close (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerFileClass *klass;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
klass = VIEWER_FILE_GET_CLASS (self);
|
|||
|
if (klass->close != NULL)
|
|||
|
klass->close (self, error);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### Virtual private Methods
|
|||
|
|
|||
|
These are very similar to virtual public methods. They just don't have a
|
|||
|
public function to call directly. The header file contains only a
|
|||
|
declaration of the virtual function:
|
|||
|
|
|||
|
```c
|
|||
|
/* declaration in viewer-file.h. */
|
|||
|
struct _ViewerFileClass
|
|||
|
{
|
|||
|
GObjectClass parent;
|
|||
|
|
|||
|
/* Public virtual method as before. */
|
|||
|
void (*open) (ViewerFile *self,
|
|||
|
GError **error);
|
|||
|
|
|||
|
/* Private helper function to work out whether the file can be loaded via
|
|||
|
* memory mapped I/O, or whether it has to be read as a stream. */
|
|||
|
gboolean (*can_memory_map) (ViewerFile *self);
|
|||
|
|
|||
|
/* Padding to allow adding up to 12 new virtual functions without
|
|||
|
* breaking ABI. */
|
|||
|
gpointer padding[12];
|
|||
|
};
|
|||
|
|
|||
|
void viewer_file_open (ViewerFile *self, GError **error);
|
|||
|
```
|
|||
|
|
|||
|
These virtual functions are often used to delegate part of the job to child classes:
|
|||
|
|
|||
|
```c
|
|||
|
/* this accessor function is static: it is not exported outside of this file. */
|
|||
|
static gboolean
|
|||
|
viewer_file_can_memory_map (ViewerFile *self)
|
|||
|
{
|
|||
|
return VIEWER_FILE_GET_CLASS (self)->can_memory_map (self);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_file_open (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
/*
|
|||
|
* Try to load the file using memory mapped I/O, if the implementation of the
|
|||
|
* class determines that is possible using its private virtual method.
|
|||
|
*/
|
|||
|
if (viewer_file_can_memory_map (self))
|
|||
|
{
|
|||
|
/* Load the file using memory mapped I/O. */
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Fall back to trying to load the file using streaming I/O… */
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Again, it is possible to provide a default implementation for this private virtual function:
|
|||
|
|
|||
|
```c
|
|||
|
static gboolean
|
|||
|
viewer_file_real_can_memory_map (ViewerFile *self)
|
|||
|
{
|
|||
|
/* As an example, always return false. Or, potentially return true if the
|
|||
|
* file is local. */
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
/* non-pure virtual method; does not have to be implemented in children. */
|
|||
|
klass->can_memory_map = viewer_file_real_can_memory_map;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Derived classes can then override the method with code such as:
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_audio_file_class_init (ViewerAudioFileClass *klass)
|
|||
|
{
|
|||
|
ViewerFileClass *file_class = VIEWER_FILE_CLASS (klass);
|
|||
|
|
|||
|
/* implement pure virtual function. */
|
|||
|
file_class->can_memory_map = viewer_audio_file_can_memory_map;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Chaining up
|
|||
|
|
|||
|
Chaining up is often loosely defined by the following set of conditions:
|
|||
|
|
|||
|
- parent class A defines a public virtual method named `foo` and provides a
|
|||
|
default implementation
|
|||
|
- child class B re-implements method `foo`
|
|||
|
- B’s implementation of `foo` calls (‘chains up to’) its parent class A’s
|
|||
|
implementation of `foo`
|
|||
|
|
|||
|
There are various uses of this idiom:
|
|||
|
|
|||
|
- you need to extend the behaviour of a class without modifying its code.
|
|||
|
You create a subclass to inherit its implementation, re-implement a public
|
|||
|
virtual method to modify the behaviour and chain up to ensure that the
|
|||
|
previous behaviour is not really modified, just extended
|
|||
|
- you need to implement the
|
|||
|
[Chain of Responsibility pattern](https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern):
|
|||
|
each object of the inheritance tree chains up to its parent (typically, at the
|
|||
|
beginning or the end of the method) to ensure that each handler is run in turn
|
|||
|
|
|||
|
To explicitly chain up to the implementation of the virtual method in the
|
|||
|
parent class, you first need a handle to the original parent class
|
|||
|
structure. This pointer can then be used to access the original virtual
|
|||
|
function pointer and invoke it directly
|
|||
|
|
|||
|
The "original" adjective used in the sentence above is not innocuous. To
|
|||
|
fully understand its meaning, recall how class structures are initialized:
|
|||
|
for each object type, the class structure associated with this object is
|
|||
|
created by first copying the class structure of its parent type (a simple
|
|||
|
memcpy) and then by invoking the `class_init` callback on the resulting class
|
|||
|
structure. Since the `class_init` callback is responsible for overwriting the
|
|||
|
class structure with the user re-implementations of the class methods, the
|
|||
|
modified copy of the parent class structure stored in the derived instance
|
|||
|
cannot be used. A copy of the class structure of an instance of the parent
|
|||
|
class is needed.
|
|||
|
|
|||
|
To chain up, you can use the `parent_class` pointer created and initialized
|
|||
|
by the `G_DEFINE_TYPE` family of macros, for instance:
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
b_method_to_call (B *obj, int some_param)
|
|||
|
{
|
|||
|
/* do stuff before chain up */
|
|||
|
|
|||
|
/* call the method_to_call() virtual function on the
|
|||
|
* parent of BClass, AClass.
|
|||
|
*
|
|||
|
* remember the explicit cast to AClass*
|
|||
|
*/
|
|||
|
A_CLASS (b_parent_class)->method_to_call (obj, some_param);
|
|||
|
|
|||
|
/* do stuff after chain up */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## How to define and implement interfaces
|
|||
|
|
|||
|
### Defining interfaces
|
|||
|
|
|||
|
The theory behind how GObject interfaces work is given in the section called
|
|||
|
["Non-instantiatable classed types:
|
|||
|
interfaces"](concepts.html#non-instantiatable-classed-types-interfaces);
|
|||
|
this section covers how to define and implement an interface.
|
|||
|
|
|||
|
The first step is to get the header right. This interface defines three
|
|||
|
methods:
|
|||
|
|
|||
|
```c
|
|||
|
/*
|
|||
|
* Copyright/Licensing information.
|
|||
|
*/
|
|||
|
|
|||
|
#pragma once
|
|||
|
|
|||
|
#include <glib-object.h>
|
|||
|
|
|||
|
G_BEGIN_DECLS
|
|||
|
|
|||
|
#define VIEWER_TYPE_EDITABLE viewer_editable_get_type()
|
|||
|
G_DECLARE_INTERFACE (ViewerEditable, viewer_editable, VIEWER, EDITABLE, GObject)
|
|||
|
|
|||
|
struct _ViewerEditableInterface
|
|||
|
{
|
|||
|
GTypeInterface parent_iface;
|
|||
|
|
|||
|
void (*save) (ViewerEditable *self,
|
|||
|
GError **error);
|
|||
|
void (*undo) (ViewerEditable *self,
|
|||
|
guint n_steps);
|
|||
|
void (*redo) (ViewerEditable *self,
|
|||
|
guint n_steps);
|
|||
|
};
|
|||
|
|
|||
|
void viewer_editable_save (ViewerEditable *self,
|
|||
|
GError **error);
|
|||
|
void viewer_editable_undo (ViewerEditable *self,
|
|||
|
guint n_steps);
|
|||
|
void viewer_editable_redo (ViewerEditable *self,
|
|||
|
guint n_steps);
|
|||
|
|
|||
|
G_END_DECLS
|
|||
|
```
|
|||
|
|
|||
|
This code is the same as the code for a normal GType which derives from a
|
|||
|
GObject except for a few details:
|
|||
|
|
|||
|
- the `_GET_CLASS` function is called `_GET_IFACE` (and is defined by `G_DECLARE_INTERFACE`)
|
|||
|
- the instance type, `ViewerEditable`, is not fully defined: it is used merely as an abstract type which represents an instance of whatever object which implements the interface
|
|||
|
- the parent of the `ViewerEditableInterface` is `GTypeInterface`, not `GObjectClass`
|
|||
|
|
|||
|
The implementation of the `ViewerEditable` type itself is trivial:
|
|||
|
|
|||
|
- `G_DEFINE_INTERFACE` creates a `viewer_editable_get_type` function which registers the type in the type system. The third argument is used to define a prerequisite interface (which we'll talk about more later). Just pass 0 for this argument when an interface has no prerequisite
|
|||
|
- `viewer_editable_default_init` is expected to register the interface's signals if there are any (we will see a bit later how to use them)
|
|||
|
- the interface methods `viewer_editable_save`, `viewer_editable_undo` and `viewer_editable_redo` dereference the interface structure to access its associated interface function and call it
|
|||
|
|
|||
|
```c
|
|||
|
G_DEFINE_INTERFACE (ViewerEditable, viewer_editable, G_TYPE_OBJECT)
|
|||
|
|
|||
|
static void
|
|||
|
viewer_editable_default_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
/* add properties and signals to the interface here */
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_editable_save (ViewerEditable *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerEditableInterface *iface;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_EDITABLE (self));
|
|||
|
g_return_if_fail (error == NULL || *error == NULL);
|
|||
|
|
|||
|
iface = VIEWER_EDITABLE_GET_IFACE (self);
|
|||
|
g_return_if_fail (iface->save != NULL);
|
|||
|
iface->save (self, error);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_editable_undo (ViewerEditable *self,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
ViewerEditableInterface *iface;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_EDITABLE (self));
|
|||
|
|
|||
|
iface = VIEWER_EDITABLE_GET_IFACE (self);
|
|||
|
g_return_if_fail (iface->undo != NULL);
|
|||
|
iface->undo (self, n_steps);
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
viewer_editable_redo (ViewerEditable *self,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
ViewerEditableInterface *iface;
|
|||
|
|
|||
|
g_return_if_fail (VIEWER_IS_EDITABLE (self));
|
|||
|
|
|||
|
iface = VIEWER_EDITABLE_GET_IFACE (self);
|
|||
|
g_return_if_fail (iface->redo != NULL);
|
|||
|
iface->redo (self, n_steps);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Implementing interfaces
|
|||
|
|
|||
|
Once the interface is defined, implementing it is rather trivial.
|
|||
|
|
|||
|
The first step is to define a normal final GObject class exactly as usual.
|
|||
|
|
|||
|
The second step is to implement `ViewerFile` by defining it using
|
|||
|
`G_DEFINE_TYPE_WITH_CODE` and `G_IMPLEMENT_INTERFACE` instead of
|
|||
|
`G_DEFINE_TYPE`:
|
|||
|
|
|||
|
```c
|
|||
|
static void viewer_file_editable_interface_init (ViewerEditableInterface *iface);
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_CODE (ViewerFile, viewer_file, G_TYPE_OBJECT,
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
|
|||
|
viewer_file_editable_interface_init))
|
|||
|
```
|
|||
|
|
|||
|
This definition is very much like all the similar functions seen previously.
|
|||
|
The only interface-specific code present here is the use of
|
|||
|
`G_IMPLEMENT_INTERFACE`.
|
|||
|
|
|||
|
Classes can implement multiple interfaces by using multiple calls to
|
|||
|
`G_IMPLEMENT_INTERFACE` inside the call to `G_DEFINE_TYPE_WITH_CODE`.
|
|||
|
|
|||
|
`viewer_file_editable_interface_init` is the interface initialization
|
|||
|
function: inside it, every virtual method of the interface must be assigned
|
|||
|
to its implementation:
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_file_editable_save (ViewerFile *self,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
g_print ("File implementation of editable interface save method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_undo (ViewerFile *self,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
g_print ("File implementation of editable interface undo method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_redo (ViewerFile *self,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
g_print ("File implementation of editable interface redo method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_interface_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
iface->save = viewer_file_editable_save;
|
|||
|
iface->undo = viewer_file_editable_undo;
|
|||
|
iface->redo = viewer_file_editable_redo;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_init (ViewerFile *self)
|
|||
|
{
|
|||
|
/* Instance variable initialisation code. */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If the object is not of final type, e.g. was declared using
|
|||
|
`G_DECLARE_DERIVABLE_TYPE` then `G_ADD_PRIVATE` macro should be added. The
|
|||
|
private structure should be declared exactly as for a normal derivable
|
|||
|
object.
|
|||
|
|
|||
|
```c
|
|||
|
G_DEFINE_TYPE_WITH_CODE (ViewerFile, viewer_file, G_TYPE_OBJECT,
|
|||
|
G_ADD_PRIVATE (ViewerFile)
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
|
|||
|
viewer_file_editable_interface_init))
|
|||
|
```
|
|||
|
|
|||
|
### Interface definition prerequisites
|
|||
|
|
|||
|
To specify that an interface requires the presence of other interfaces when
|
|||
|
implemented, GObject introduces the concept of prerequisites: it is possible
|
|||
|
to associate a list of prerequisite types to an interface. For example, if
|
|||
|
object A wishes to implement interface I1, and if interface I1 has a
|
|||
|
prerequisite on interface I2, A has to implement both I1 and I2.
|
|||
|
|
|||
|
The mechanism described above is, in practice, very similar to Java's
|
|||
|
interface I1 extends interface I2. The example below shows the GObject
|
|||
|
equivalent:
|
|||
|
|
|||
|
```
|
|||
|
/* Make the ViewerEditableLossy interface require ViewerEditable interface. */
|
|||
|
G_DEFINE_INTERFACE (ViewerEditableLossy, viewer_editable_lossy, VIEWER_TYPE_EDITABLE)
|
|||
|
```
|
|||
|
|
|||
|
In the `G_DEFINE_INTERFACE` call above, the third parameter defines the
|
|||
|
prerequisite type. This is the GType of either an interface or a class. In
|
|||
|
this case the `ViewerEditable` interface is a prerequisite of
|
|||
|
`ViewerEditableLossy`. The code below shows how an implementation can
|
|||
|
implement both interfaces and register their implementations:
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_file_editable_lossy_compress (ViewerEditableLossy *editable)
|
|||
|
{
|
|||
|
ViewerFile *self = VIEWER_FILE (editable);
|
|||
|
|
|||
|
g_print ("File implementation of lossy editable interface compress method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_lossy_interface_init (ViewerEditableLossyInterface *iface)
|
|||
|
{
|
|||
|
iface->compress = viewer_file_editable_lossy_compress;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_save (ViewerEditable *editable,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerFile *self = VIEWER_FILE (editable);
|
|||
|
|
|||
|
g_print ("File implementation of editable interface save method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_undo (ViewerEditable *editable,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
ViewerFile *self = VIEWER_FILE (editable);
|
|||
|
|
|||
|
g_print ("File implementation of editable interface undo method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_redo (ViewerEditable *editable,
|
|||
|
guint n_steps)
|
|||
|
{
|
|||
|
ViewerFile *self = VIEWER_FILE (editable);
|
|||
|
|
|||
|
g_print ("File implementation of editable interface redo method: %s.\n",
|
|||
|
self->filename);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_editable_interface_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
iface->save = viewer_file_editable_save;
|
|||
|
iface->undo = viewer_file_editable_undo;
|
|||
|
iface->redo = viewer_file_editable_redo;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
/* Nothing here. */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_init (ViewerFile *self)
|
|||
|
{
|
|||
|
/* Instance variable initialisation code. */
|
|||
|
}
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_CODE (ViewerFile, viewer_file, G_TYPE_OBJECT,
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
|
|||
|
viewer_file_editable_interface_init)
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE_LOSSY,
|
|||
|
viewer_file_editable_lossy_interface_init))
|
|||
|
```
|
|||
|
|
|||
|
It is very important to notice that the order in which interface
|
|||
|
implementations are added to the main object is not random:
|
|||
|
`g_type_add_interface_static()`, which is called by `G_IMPLEMENT_INTERFACE`, must
|
|||
|
be invoked first on the interfaces which have no prerequisites and then on
|
|||
|
the others.
|
|||
|
|
|||
|
### Interface properties
|
|||
|
|
|||
|
GObject interfaces can also have properties. Declaration of the interface
|
|||
|
properties is similar to declaring the properties of ordinary GObject types
|
|||
|
as explained in the section called ["Object
|
|||
|
properties"](concepts.html#object-properties), except that
|
|||
|
`g_object_interface_install_property()` is used to declare the properties
|
|||
|
instead of `g_object_class_install_property()`.
|
|||
|
|
|||
|
To include a property named 'autosave-frequency' of type gdouble in the
|
|||
|
`ViewerEditable` interface example code above, we only need to add one call in
|
|||
|
`viewer_editable_default_init()` as shown below:
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_editable_default_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
g_object_interface_install_property (iface,
|
|||
|
g_param_spec_double ("autosave-frequency",
|
|||
|
"Autosave frequency",
|
|||
|
"Frequency (in per-seconds) to autosave backups of the editable content at. "
|
|||
|
"Or zero to disable autosaves.",
|
|||
|
0.0, /* minimum */
|
|||
|
G_MAXDOUBLE, /* maximum */
|
|||
|
0.0, /* default */
|
|||
|
G_PARAM_READWRITE));
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
One point worth noting is that the declared property wasn't assigned an
|
|||
|
integer ID. The reason being that integer IDs of properties are used only
|
|||
|
inside the `get_property` and `set_property` virtual methods. Since interfaces
|
|||
|
declare but do not implement properties, there is no need to assign integer
|
|||
|
IDs to them.
|
|||
|
|
|||
|
An implementation declares and defines its properties in the usual way as
|
|||
|
explained in the section called “Object properties”, except for one small
|
|||
|
change: it can declare the properties of the interface it implements using
|
|||
|
`g_object_class_override_property()` instead of `g_object_class_install_property()`.
|
|||
|
The following code snippet shows the modifications needed in the `ViewerFile`
|
|||
|
declaration and implementation above:
|
|||
|
|
|||
|
```c
|
|||
|
struct _ViewerFile
|
|||
|
{
|
|||
|
GObject parent_instance;
|
|||
|
|
|||
|
double autosave_frequency;
|
|||
|
};
|
|||
|
|
|||
|
enum
|
|||
|
{
|
|||
|
PROP_AUTOSAVE_FREQUENCY = 1,
|
|||
|
N_PROPERTIES
|
|||
|
};
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_set_property (GObject *object,
|
|||
|
guint prop_id,
|
|||
|
const GValue *value,
|
|||
|
GParamSpec *pspec)
|
|||
|
{
|
|||
|
ViewerFile *file = VIEWER_FILE (object);
|
|||
|
|
|||
|
switch (prop_id)
|
|||
|
{
|
|||
|
case PROP_AUTOSAVE_FREQUENCY:
|
|||
|
file->autosave_frequency = g_value_get_double (value);
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_get_property (GObject *object,
|
|||
|
guint prop_id,
|
|||
|
GValue *value,
|
|||
|
GParamSpec *pspec)
|
|||
|
{
|
|||
|
ViewerFile *file = VIEWER_FILE (object);
|
|||
|
|
|||
|
switch (prop_id)
|
|||
|
{
|
|||
|
case PROP_AUTOSAVE_FREQUENCY:
|
|||
|
g_value_set_double (value, file->autosave_frequency);
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_file_class_init (ViewerFileClass *klass)
|
|||
|
{
|
|||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|||
|
|
|||
|
object_class->set_property = viewer_file_set_property;
|
|||
|
object_class->get_property = viewer_file_get_property;
|
|||
|
|
|||
|
g_object_class_override_property (object_class, PROP_AUTOSAVE_FREQUENCY, "autosave-frequency");
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Overriding interface methods
|
|||
|
|
|||
|
If a base class already implements an interface and a derived class needs to
|
|||
|
implement the same interface but needs to override certain methods, you must
|
|||
|
reimplement the interface and set only the interface methods which need
|
|||
|
overriding.
|
|||
|
|
|||
|
In this example, `ViewerAudioFile` is derived from `ViewerFile`. Both implement
|
|||
|
the `ViewerEditable` interface. `ViewerAudioFile` only implements one method of
|
|||
|
the `ViewerEditable` interface and uses the base class implementation of the
|
|||
|
other.
|
|||
|
|
|||
|
```c
|
|||
|
static void
|
|||
|
viewer_audio_file_editable_save (ViewerEditable *editable,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerAudioFile *self = VIEWER_AUDIO_FILE (editable);
|
|||
|
|
|||
|
g_print ("Audio file implementation of editable interface save method.\n");
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_editable_interface_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
/* Override the implementation of save(). */
|
|||
|
iface->save = viewer_audio_file_editable_save;
|
|||
|
|
|||
|
/*
|
|||
|
* Leave iface->undo and ->redo alone, they are already set to the
|
|||
|
* base class implementation.
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_CODE (ViewerAudioFile, viewer_audio_file, VIEWER_TYPE_FILE,
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
|
|||
|
viewer_audio_file_editable_interface_init))
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_class_init (ViewerAudioFileClass *klass)
|
|||
|
{
|
|||
|
/* Nothing here. */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_init (ViewerAudioFile *self)
|
|||
|
{
|
|||
|
/* Nothing here. */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
To access the base class interface implementation use
|
|||
|
`g_type_interface_peek_parent()` from within an interface's `default_init`
|
|||
|
function.
|
|||
|
|
|||
|
To call the base class implementation of an interface method from a derived
|
|||
|
class where than interface method has been overridden, stash away the
|
|||
|
pointer returned from `g_type_interface_peek_parent()` in a global variable.
|
|||
|
|
|||
|
In this example `ViewerAudioFile` overrides the save interface method. In
|
|||
|
its overridden method it calls the base class implementation of the same
|
|||
|
interface method.
|
|||
|
|
|||
|
```c
|
|||
|
static ViewerEditableInterface *viewer_editable_parent_interface = NULL;
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_editable_save (ViewerEditable *editable,
|
|||
|
GError **error)
|
|||
|
{
|
|||
|
ViewerAudioFile *self = VIEWER_AUDIO_FILE (editable);
|
|||
|
|
|||
|
g_print ("Audio file implementation of editable interface save method.\n");
|
|||
|
|
|||
|
/* Now call the base implementation */
|
|||
|
viewer_editable_parent_interface->save (editable, error);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_editable_interface_init (ViewerEditableInterface *iface)
|
|||
|
{
|
|||
|
viewer_editable_parent_interface = g_type_interface_peek_parent (iface);
|
|||
|
|
|||
|
iface->save = viewer_audio_file_editable_save;
|
|||
|
}
|
|||
|
|
|||
|
G_DEFINE_TYPE_WITH_CODE (ViewerAudioFile, viewer_audio_file, VIEWER_TYPE_FILE,
|
|||
|
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
|
|||
|
viewer_audio_file_editable_interface_init))
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_class_init (ViewerAudioFileClass *klass)
|
|||
|
{
|
|||
|
/* Nothing here. */
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
viewer_audio_file_init (ViewerAudioFile *self)
|
|||
|
{
|
|||
|
/* Nothing here. */
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
## How to create and use signals
|
|||
|
|
|||
|
The signal system in GType is pretty complex and flexible: it is possible
|
|||
|
for its users to connect at runtime any number of callbacks (implemented in
|
|||
|
any language for which a binding exists) to any signal and to stop the
|
|||
|
emission of any signal at any state of the signal emission process. This
|
|||
|
flexibility makes it possible to use GSignal for much more than just
|
|||
|
emitting signals to multiple clients.
|
|||
|
|
|||
|
### Simple use of signals
|
|||
|
|
|||
|
The most basic use of signals is to implement event notification. For
|
|||
|
example, given a `ViewerFile` object with a write method, a signal could be
|
|||
|
emitted whenever the file is changed using that method. The code below shows
|
|||
|
how the user can connect a callback to the "changed" signal.
|
|||
|
|
|||
|
```c
|
|||
|
file = g_object_new (VIEWER_FILE_TYPE, NULL);
|
|||
|
|
|||
|
g_signal_connect (file, "changed", (GCallback) changed_event, NULL);
|
|||
|
|
|||
|
viewer_file_write (file, buffer, strlen (buffer));
|
|||
|
```
|
|||
|
|
|||
|
The ViewerFile signal is registered in the `class_init` function:
|
|||
|
|
|||
|
```c
|
|||
|
file_signals[CHANGED] =
|
|||
|
g_signal_newv ("changed",
|
|||
|
G_TYPE_FROM_CLASS (object_class),
|
|||
|
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
|
|||
|
NULL /* closure */,
|
|||
|
NULL /* accumulator */,
|
|||
|
NULL /* accumulator data */,
|
|||
|
NULL /* C marshaller */,
|
|||
|
G_TYPE_NONE /* return_type */,
|
|||
|
0 /* n_params */,
|
|||
|
NULL /* param_types */);
|
|||
|
```
|
|||
|
|
|||
|
and the signal is emitted in `viewer_file_write`:
|
|||
|
|
|||
|
```c
|
|||
|
void
|
|||
|
viewer_file_write (ViewerFile *self,
|
|||
|
const guint8 *buffer,
|
|||
|
gsize size)
|
|||
|
{
|
|||
|
g_return_if_fail (VIEWER_IS_FILE (self));
|
|||
|
g_return_if_fail (buffer != NULL || size == 0);
|
|||
|
|
|||
|
/* First write data. */
|
|||
|
|
|||
|
/* Then, notify user of data written. */
|
|||
|
g_signal_emit (self, file_signals[CHANGED], 0 /* details */);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
As shown above, the details parameter can safely be set to zero if no detail
|
|||
|
needs to be conveyed. For a discussion of what it can be used for, see the
|
|||
|
section called [“The detail argument”](concepts.html#the-detail-argument).
|
|||
|
|
|||
|
The C signal marshaller should always be `NULL`, in which case the best
|
|||
|
marshaller for the given closure type will be chosen by GLib. This may be an
|
|||
|
internal marshaller specific to the closure type, or
|
|||
|
`g_cclosure_marshal_generic()`, which implements generic conversion of arrays of
|
|||
|
parameters to C callback invocations. GLib used to require the user to write
|
|||
|
or generate a type-specific marshaller and pass that, but that has been
|
|||
|
deprecated in favour of automatic selection of marshallers.
|
|||
|
|
|||
|
Note that `g_cclosure_marshal_generic()` is slower than non-generic
|
|||
|
marshallers, so should be avoided for performance critical code. However,
|
|||
|
performance critical code should rarely be using signals anyway, as signals
|
|||
|
are synchronous, and the emission blocks until all listeners are invoked,
|
|||
|
which has potentially unbounded cost.
|