Archive

Posts Tagged ‘OOP’

OO Impelementation in C#

March 13th, 2011 No comments

Finally, we comes with the C# language. C# supports all OO features mentioned in previous articles in language level. So, our implementation is quite straight forward. Just check the source code in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestCSObject-{date}.zip file.

Lastly, I drew a simple table to compare and summarize the OO supports in the four leading languages:

C/gtk+ C++/Qt Java C#
Basic Features
Encapsulation library good design Y Y
Inheritance library Y Y Y
Polymorphism function pointer Y Y Y
Advanced Features
Property library library reflection feature Y
Meta Info library library annotations attributes
Event Driven library(signals) library(signals/slots) events/listeners delegates/events/handlers
Measurements (Using a test application to demostrate above 5 features)
Compiler mingw32-gcc/3.4 mingw32-g++/3.4 JDK/1.6.20 VC#/2005
Library gtk+/2.16.6 Qt/4.3.5 Java/6.0 .NET/2.0
Source Size 19.09 KB 7.28 KB (+10.17 KB) * 13.18 KB 6.28 KB
Binary Size 32.69 KB 34.00 KB 9.48 KB 5.50 KB
Runtime Size 1.46 MB 2.00 MB 15.54 MB 23.84 MB
* Generated source
Categories: .NET Tags:

OO Impelementation in Java

March 13th, 2011 No comments

Continue with last article, we will try to write an identical application to use OO features including: encapsulation, inheritance, polymorphism, properties, meta info and event-driven mechanism. Java supports the 3 basic features in language level. It uses interfaces to implements event-driven. To implements properties and meta info, we have to write our own code. We want to implements API like someObject.setProperty(prop-name, prop-value). I write my own NewObject class:

package my;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import my.annotation.ClassInfo;
import my.annotation.ClassInfoList;
import my.annotation.Property;
import my.annotation.PropertyAccess;

/**
 * We just set/get values as Object type. End users know the exact type of the
 * property, and they can do the conversion themselves.
 */
public class NewObject {

    private static String makeGetPropertyName(Field field) {
        String fieldName = field.getName();
        if (fieldName == null || fieldName.equals("")) {
            return null;
        }
        return "get" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
    }

    private static String makeSetPropertyName(Field field) {
        String fieldName = field.getName();
        if (fieldName == null || fieldName.equals("")) {
            return null;
        }
        return "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
    }

    /**
     * Get property
     * @param name
     * @return
     */
    public Object getProperty(String name) {
        Class klass = this.getClass();
        Field field = null;
        while (!klass.getName().equals(NewObject.class.getName())) {
            try {
                field = klass.getDeclaredField(name);
                break;  // found
            } catch (NoSuchFieldException e) {
                // noop
            }
            klass = klass.getSuperclass();
        }
        if (field != null) {
            Property property = field.getAnnotation(Property.class);
            if (property.value() == PropertyAccess.WRITE
                    || property.value() == PropertyAccess.READWRITE) {
                String methodName = makeGetPropertyName(field);
                try {
                    /**
                     * We can also get the value directly as below, but this
                     * bypass the getter function which is wrong. 
                     * <code>
                     * field.setAccessible(true);
                     * Object value = field.get(this);
                     * field.setAccessible(false);
                     * return value;
                     * </code>
                     */
                    Method getMethod = klass.getMethod(methodName);
                    return getMethod.invoke(this);
                } catch (IllegalAccessException e) {
                    // noop
                } catch (IllegalArgumentException e) {
                    // noop
                } catch (InvocationTargetException e) {
                    // noop
                } catch (NoSuchMethodException e) {
                    // noop
                } catch (SecurityException e) {
                    // noop
                }
            }
        }
        return null;
    }

    /**
     * Set property
     * @param name
     * @param value
     */
    public void setProperty(String name, Object value) {
        Class klass = this.getClass();
        Field field = null;
        while (!klass.getName().equals(NewObject.class.getName())) {
            try {
                field = klass.getDeclaredField(name);
                break;  // found
            } catch (NoSuchFieldException e) {
                // noop
            }
            klass = klass.getSuperclass();
        }
        if (field != null) {
            Property property = field.getAnnotation(Property.class);
            if (property.value() == PropertyAccess.WRITE
                    || property.value() == PropertyAccess.READWRITE) {
                String methodName = makeSetPropertyName(field);
                try {
                    Method setMethod = klass.getMethod(methodName, field.getType());
                    setMethod.invoke(this, value);
                } catch (IllegalAccessException e) {
                    // noop
                } catch (IllegalArgumentException e) {
                    // noop
                } catch (InvocationTargetException e) {
                    // noop
                } catch (NoSuchMethodException e) {
                    // noop
                } catch (SecurityException e) {
                    // noop
                }
            }
        }
    }

    /**
     * Dump class info by given class
     * @param klass
     */
    public static void dumpClassInfo(Class klass) {
        System.out.println(klass.getCanonicalName() + "(");
        ClassInfo[] klassInfos = klass.getAnnotation(ClassInfoList.class).value();
        for (int i = 0; i < klassInfos.length; i++) {
            System.out.println(klassInfos[i].name() + "=" + klassInfos[i].value());
        }
        System.out.println(")");
    }

    /**
     * Dump class info of current object
     */
    public void dumpClassInfo() {
        Class klass = this.getClass();
        System.out.println(klass.getCanonicalName() + "(");
        ClassInfo[] klassInfos = klass.getAnnotation(ClassInfoList.class).value();
        for (int i = 0; i < klassInfos.length; i++) {
            System.out.println(klassInfos[i].name() + "=" + klassInfos[i].value());
        }
        System.out.println(")");
    }

    /**
     * Get class info by given name
     * @param name
     * @return
     */
    public String getClassInfo(String name) {
        Class klass = this.getClass();
        ClassInfo[] klassInfos = klass.getAnnotation(ClassInfoList.class).value();
        for (int i = 0; i < klassInfos.length; i++) {
            if (klassInfos[i].name().equals(name)) {
                return klassInfos[i].value();
            }
        }
        return null;
    }

}

To use our setProperty()/getProperty() method, all classes should derive from the NewObject class. To be consistent with the JavaBean convention, we assume that the getter/setter function to be “get”/”set” + capitalize_first_letter_of(member-variable-name).

Property annotation and PropertyAccess enum are defined to indicate properties:

// PropertyAccess.java
package my.annotation;

public enum PropertyAccess {
    READ, WRITE, READWRITE
}
// Property.java
package my.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Property {
    /* "value" seems to be a magic name */
    public PropertyAccess value();
}

ClassInfo and ClassInfoList annotation are defined to indicate class meta info:

// ClassInfo.java
package my.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassInfo {
    public String name();
    public String value();
}
// ClassInfoList.java
package my.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ClassInfoList {
    ClassInfo[] value();
}

Let’s see how to use them, our Base is defined as:

// Base.java
package fake;

import java.util.LinkedList;
import java.util.List;

import my.NewObject;
import my.annotation.ClassInfo;
import my.annotation.ClassInfoList;
import my.annotation.Property;
import my.annotation.PropertyAccess;

@ClassInfoList({ 
    @ClassInfo(name = "author", value = "gonwan"),
    @ClassInfo(name = "version", value = "1.0.0") 
})
public class Base extends NewObject {
    @Property(PropertyAccess.READWRITE)
    private int id;
    @Property(PropertyAccess.READWRITE)
    private String name;

    private List<IPrintInt> basePrintIntListeners;
    private List<IPrintString> basePrintStringListeners;

    public Base() {
        basePrintIntListeners = new LinkedList<IPrintInt>();
        basePrintStringListeners = new LinkedList<IPrintString>();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void virtualDump() {
        System.out.printf("Base(virtual): id=%d, name=\"%s\"\n", getId(), getName());
    }

    final public void nonvirtualDump() {
        System.out.printf("Base(nonvirtual): id=%d, name=\"%s\"\n", getId(), getName());
    }

    public void addBasePrintIntListener(IPrintInt listener) {
        basePrintIntListeners.add(listener);
    }

    public void addBasePrintStringListener(IPrintString listener) {
        basePrintStringListeners.add(listener);
    }

    public void fireBasePrintIntEvent(int i) {
        for (IPrintInt listener : basePrintIntListeners) {
            listener.printInt(i);
        }
    }

    public void fireBasePrintStringEvent(String str) {
        for (IPrintString listener : basePrintStringListeners) {
            listener.PrintString(str);
        }
    }

}

Since our implementation of properties are simply methods, they can be inherited by subclasses. But the class meta info cannot be retrieved in subclasses. They just get their own.

I do not want to demo events/listeners code here, just find them in source code in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestJavaObject-{date}.zip file.

Categories: Java Tags:

OO Impelementation in C++

March 13th, 2011 No comments

From the last series of GObject library, we know the approach of OOP using C. Now, I just want to have a comparison of OO implementation in all leading programming languages: C, C++, Java and C#. I will use C++/Qt in this article. Apart from basic features like encapsulation, inheritance, polymorphism, I will demonstrate how to use advanced features including properties, meta info and event-driven programming.

Now, let’s start. Since C++ supports inheritance and polymorphism in language level. They are not the problem. When encounter encapsulation, it does not do well. We CAN declare member variables as private to prohibit their directly access. But the internal implementation is still exposed. When adding/removing private member variables, the class structure is changed. This can cause binary compatible issues. According to the guide of Qt, we define a private class to hold all private member variables, and add the pointer of it to our public class. The size of pointer is constant in all platforms, so this will not break the binary compatibility. Here’s the code:

NOTE: To use Qt’s object mechanism, your class should inherit QObject class and include the Q_OBJECT macro.

// qfakebase.h
#ifndef QFAKEBASE_H
#define QFAKEBASE_H

#include <QtCore/QObject>
#include <QtCore/QString>


namespace Fake {

class QBasePrivate;

class QBase : public QObject
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(QBase)
    Q_CLASSINFO("Author", "gonwan")
    Q_CLASSINFO("Version", "1.0.0")
    Q_PROPERTY(int id READ id WRITE setId)
    Q_PROPERTY(QString name READ name WRITE setName)

public:
    explicit QBase(QObject *parent=0);
    virtual ~QBase();
    int id();
    void setId(int id);
    QString name();
    void setName(QString name);
    virtual void virtualDump();
    void nonvirtualDump();
    /*
     * All signals are defined protected, they can be only emit by
     * themselves or their subclasses. So we add these two help function
     * to help to emit our singals.
     */
    void emitBasePrintInt(int i);
    void emitBasePrintString(QString str);

protected:
    QBasePrivate *const d_ptr;

signals:
    void basePrintInt(int i);
    void basePrintString(QString str);

};


}  // namespace Fake


#endif // QFAKEBASE_H
// qfakebase.cpp
#include "qfakebase.h"
#include <stdio.h>


namespace Fake {


/* private class */
class QBasePrivate
{
    //Q_DECLARE_PUBLIC(QBase)
public:
    int id;
    QString name;
};


/* public class */
QBase::QBase(QObject *parent)
    : QObject(parent), d_ptr(new QBasePrivate) {
}

QBase::~QBase() {
    delete d_ptr;
}

int QBase::id() {
    Q_D(QBase);
    return d->id;
}

void QBase::setId(int id) {
    Q_D(QBase);
    d->id = id;
}

QString QBase::name() {
    Q_D(QBase);
    return d->name;
}

void QBase::setName(QString name) {
    Q_D(QBase);
    d->name = name;
}

void QBase::virtualDump() {
    printf("QBase(virtual): id=%d, name=\"%s\"\n", id(), qPrintable(name()));
}

void QBase::nonvirtualDump() {
    printf("QBase(nonvirtual): id=%d, name=\"%s\"\n", id(), qPrintable(name()));
}

void QBase::emitBasePrintInt(int i) {
    emit basePrintInt(i);
}

void QBase::emitBasePrintString(QString str) {
    emit basePrintString(str);
}


}  // namespace Fake

Just note the forward declaration of QBasePrivate private class. It is define in *.c file, and cannot be used by client applications. We defined a d_ptr protected member variable of this type to hold all private data values. Qt library provideds a series of easy-to-use macros to support this scheme to implementation:

// qglobal.h
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(d_ptr); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(d_ptr); } \
    friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class) \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

Qt library supports properties and meta info. properties are defined with Q_PROPERTYmacro, while class meta info are defined with Q_CLASSINFO. Both of them can be inherited by derived classes. Last is Qt’s event-driven mechanism: signals/slots. Since they are also based on QObject, we had to define a test class to include all slots:

// mytestclass.h
#ifndef MYTESTCLASS_H
#define MYTESTCLASS_H

#include <QtCore/QObject>
#include <stdio.h>


/*
 * Qt's signal/slot mechanism only works with
 * QObject-derived classes. So we define a test class here.
 */
class MyTestClass : public QObject {
    Q_OBJECT
public slots:
    void printInt1(int i) {
        QString name = sender()->metaObject()->className();
        printf("Invoking printInt1(): %s.i=%d\n", qPrintable(name), i);
    }
    void printInt2(int i) {
        QString name = sender()->metaObject()->className();
        printf("Invoking printInt2(): %s.i=%d\n", qPrintable(name), i);
    }
    void printString1(QString str) {
        QString name = sender()->metaObject()->className();
        printf("Invoking printString1(): %s.str=%s\n", qPrintable(name), qPrintable(str));
    }
    void printString2(QString str) {
        QString name = sender()->metaObject()->className();
        printf("Invoking printString2(): %s.str=%s\n", qPrintable(name), qPrintable(str));
    }
};


#endif // MYTESTCLASS_H

Test code:

#include <stdio.h>
#include <QtCore/QMetaClassInfo>
#include <QtCore/QObject>
#include <QtCore/QVariant>
#include "qfakebase.h"
#include "qfakederived.h"
#include "mytestclass.h"

using namespace Fake;


void myDumpClass(QMetaObject metaObject) {
    printf("%s (\n", metaObject.className());
    int start = metaObject.superClass()->classInfoCount();
    for (int i = start; i < metaObject.classInfoCount(); i++) {
        printf("%s=\"%s\"\n",
            metaObject.classInfo(i).name(), metaObject.classInfo(i).value());
    }
    printf(")\n");
}

int main() {
    /* Test class */
    myDumpClass(QBase::staticMetaObject);
    myDumpClass(QDerived::staticMetaObject);
    /* Test property */
    QBase base;
    base.setId(111);
    base.setProperty("name", QVariant("aaa"));
    base.nonvirtualDump();
    QDerived derived;
    derived.setId(222);
    derived.setAge(333);
    derived.setProperty("name", QVariant("bbb"));
    derived.setProperty("hash", QVariant("ccc"));
    derived.nonvirtualDump();
    /* Test polymorphism */
    QBase *objs[2] = { &base, &derived };
    for (int i = 0; i < 2; i++) {
        objs[i]->virtualDump();
    }
    /* Test signal/slot */
    /* 1 <-> 1 */
    MyTestClass instance;
    QObject::connect(&base, 
        SIGNAL(basePrintInt(int)), &instance, SLOT(printInt1(int)));
    QObject::connect(&base, 
        SIGNAL(basePrintString(QString)), &instance, SLOT(printString1(QString)));
    base.emitBasePrintInt(12345);
    base.emitBasePrintString("abcde");
    /* 1 <-> 1+ */
    QObject::connect(&base, 
        SIGNAL(basePrintInt(int)), &instance, SLOT(printInt2(int)));
    base.emitBasePrintInt(123456);
    /* signal inheritance */
    QObject::connect(&derived, 
        SIGNAL(basePrintInt(int)), &instance, SLOT(printInt1(int)));
    QObject::connect(&derived, 
        SIGNAL(basePrintString(QString)), &instance, SLOT(printString1(QString)));
    QObject::connect(&derived, 
        SIGNAL(derivedPrintInt(int)), &instance, SLOT(printInt2(int)));
    QObject::connect(&derived, 
        SIGNAL(derivedPrintString(QString)), &instance, SLOT(printString2(QString)));
    derived.emitBasePrintInt(1234567);
    derived.emitBasePrintString("abcdefg");
    derived.emitDerivedPrintInt(1234567);
    derived.emitDerivedPrintString("abcdefg");

}

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestQObject-{date}.zip file.

Categories: C/C++ Tags: ,

OOP Using GObject (9) – A Dynamic Type

March 12th, 2011 No comments

Recall there are 3 types in GObject type system: Fundamental, static and dynamic. A fundamental type is a top-most type which has no parent type. Most of them are pre-defined. Static types never load/unload its class type (say, their class struct) at runtime, since they are static. On the contrary, dynamic types can be dynamically loaded/unloaded at runtime. They are normally used within a module.

We can call g_type_register_dynamic() to register a dynamic type. When used in a module of GObject library (may be a GTypeModule type), We can also call g_type_module_register_type() to create your dynamic types. g_type_register_dynamic() is invoked for you in that function. Let’s go through the code:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

// bartype.h
#ifndef BAR_TYPE_H_
#define BAR_TYPE_H_

#include <glib-object.h>

/* Bar type class struct */
typedef struct _BarTypeClass {
    GObjectClass parent;
} BarTypeClass;

/* Far type object struct */
typedef struct _BarType {
    GObject parent;
} BarType;

/* type function */
GType bar_type_get_type();

/* register type */
void bar_type_register_type(GTypeModule *type_module);


#endif /* BAR_TYPE_H_ */
// bartype.c
#include "bartype.h"

static gpointer parent_klass = NULL;
static GType bar_type_type_id = 0;

static void bar_type_instance_init(BarType *self) {
    g_print("Calling bar_type_instance_init()\n");
}

static void bar_type_instance_finalize(GObject *object) {
    /* do some finalize, maybe release some dynamically allocated memory */
    g_print("Calling bar_type_instance_finalize()\n");
    /* chain to parent's finalize */
    G_OBJECT_CLASS(parent_klass)->finalize(object);
}

static void bar_type_class_init(BarTypeClass *klass) {
    g_print("Calling bar_type_class_init()\n");
    parent_klass = g_type_class_peek_parent(klass);
    G_OBJECT_CLASS(klass)->finalize = bar_type_instance_finalize;
}

static void bar_type_class_finalize(BarTypeClass *klass) {
    g_print("Calling bar_type_class_finalize()\n");
}

GType bar_type_get_type() {
    return bar_type_type_id;
}

void bar_type_register_type(GTypeModule *type_module) {
    const GTypeInfo type_info = {
        sizeof(BarTypeClass), /* class_size */
        NULL,                   /* base_init */
        NULL,                   /* base_finalize */
        (GClassInitFunc)bar_type_class_init, /* class_init */
        (GClassFinalizeFunc)bar_type_class_finalize, /* class_finalize */
        NULL,                   /* class_data */
        sizeof(BarType),        /* instance_size */
        0,                      /* n_preallocs */
        (GInstanceInitFunc)bar_type_instance_init, /* instance_init */
        NULL                    /* value_table */
    };
    bar_type_type_id = g_type_module_register_type(
        type_module, G_TYPE_OBJECT, "BarTypeDynamicClass", &type_info, 0);
}

The implementation structure may be a little different with the stuff when creating a static type. An additional parameter GTypeModule is passed in. It represents the module your dynamic type belongs to. So, when the module is unloaded, all dynamic types in it are unaccessible.

Also note the bar_type_class_finalize() function. We use it to override the finalize() virtual function in GObjectClass. Now you can do un-initialiation in this function. It is like the destructor in a C++ class.

Let’s move on to the module type. This type inherits GTypeModule:

// fakemodule.h
#ifndef FAKE_MODULE_H_
#define FAKE_MODULE_H_

#include <glib-object.h>

/* module object struct */
typedef struct _FakeModule {
    GTypeModule parent;
} FakeModule;

/* module class struct */
typedef struct _FakeModuleClass {
    GTypeModuleClass parent;
} FakeModuleClass;

/* type method */
GType fake_module_get_type();


#endif /* FAKE_MODULE_H_ */
// fakemodule.c
#include "fakemodule.h"

/*
 * If you implement a real shared library module, you
 * can init module variables, assign virtual function here.
 */
gboolean fake_module_load(GTypeModule *module) {
    g_print("Invoking fake_module_load()\n");
    /* successfully loaded */
    return TRUE;
}

/*
 * If you implement a real shared library module, you
 * can uninit module variables, and make all cleanups here.
 */
void fake_module_unload(GTypeModule *module) {
    /* noop */
    g_print("Invoking fake_module_unload()\n");
}

static void fake_module_class_init(FakeModuleClass *klass, gpointer data) {
    g_print("Calling fake_module_class_init()\n");
    GTypeModuleClass *module_class = G_TYPE_MODULE_CLASS(klass);
    module_class->load = fake_module_load;
    module_class->unload = fake_module_unload;
}

static void fake_module_instance_init(FakeModule *instance, gpointer data) {
    g_print("Calling fake_module_instance_init()\n");
}

GType fake_module_get_type() {
    static GType type_id = 0;
    if (type_id == 0) {
        static const GTypeInfo type_info = {
            sizeof(FakeModuleClass), /* class_size */
            NULL,                   /* base_init */
            NULL,                   /* base_finalize */
            (GClassInitFunc)fake_module_class_init, /* class_init */
            NULL,                   /* class_finalize */
            NULL,                   /* class_data */
            sizeof(FakeModule),     /* instance_size */
            0,                      /* n_preallocs */
            (GInstanceInitFunc)fake_module_instance_init, /* instance_init */
            NULL                    /* value_table */
        };
        type_id = g_type_register_static(
            G_TYPE_TYPE_MODULE, "FakeModuleStaticClass", &type_info, 0);
    }
    return type_id;
}

GTypeModule is an abstract type. We should implements its load() and unload() virtual function.

Our test code:

// main.c
#include "footype.h"
#include "bartype.h"
#include "fakemodule.h"
#include <glib-object.h>

/*
 * Module entry point. If you implement a real shared library module,
 * you can use dlopen()/dlsym() or g_module_open()/g_module_symbol() to
 * load this module dynamically.
 */
void module_init(GTypeModule *type_module) {
    foo_type_register_type(type_module);
    bar_type_register_type(type_module);
}

int main() {
    g_type_init();

    FakeModule *module = (FakeModule *)g_object_new(fake_module_get_type(), NULL);
    module_init((GTypeModule *)module);

    /*
     * Add a reference to foo type class here. Otherwise, the fake module
     * will be unloaded right after the free() of foo type object and cause error.
     */
    FooTypeClass *foo_type_class = (FooTypeClass *)g_type_class_ref(foo_type_get_type());

    FooType *foo_type = (FooType *)g_object_new(foo_type_get_type(), NULL);
    BarType *bar_type = (BarType *)g_object_new(bar_type_get_type(), NULL);

    /* Test for override finalize() */
    g_object_unref(foo_type);
    g_object_unref(bar_type);

    g_type_class_unref(foo_type_class);

    return 0;
}

Another dynamic type BarType is defined in addition to FooType to demo the usage. The output maybe:

Calling fake_module_class_init()
Calling fake_module_instance_init()
Invoking fake_module_load()
Calling foo_type_class_init()
Calling foo_type_instance_init()
Calling bar_type_class_init()
Calling bar_type_instance_init()
Calling foo_type_instance_finalize()
Calling bar_type_instance_finalize()
Calling bar_type_class_finalize()
Calling foo_type_class_finalize()
Invoking fake_module_unload()

See the init/finalize process?

At the end of my note, Let me summarize to compare GObject library with C++ implementation:

1. Member Variables:

GObject C++
in class struct class meta info
in object struct class instance member
global variable class static member

2. Function Callbacks:

GObject C++
base_init init class dynamic meta info
base_finalize finalize dynamic class meta info, only dynamic types use it
class_init init class static meta info
class_finalize finalize class static meta info, only dynamic types use it
instance_init init instace, like C++ constructor
override finalize in GObjectClass finalize instance, like C++ destructor

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject7 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (8) – An interface

March 11th, 2011 No comments

Interfaces usage in library is like class usage. We need to define a interface struct, but no object struct is needed:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

typedef struct _FakeIServer FakeIServer; /* dummy object */
typedef struct _FakeIServerInterface {
    GTypeInterface parent;
    void (*response)(FakeIServer *instance);
} FakeIServerInterface;

Then we register the interface using g_type_register_static() with G_TYPE_INTERFACE as first parameter. For interfaces, we only need to assign base_init() and base_finalize()callbacks.

static void fake_iserver_base_init(gpointer g_class) {
    static gboolean is_initialized = FALSE;
    if (!is_initialized) {
        /* add properties and signals to the interface here */
        is_initialized = TRUE;
    }
}

GType fake_iserver_get_type() {
    static GType type_id = 0;
    if (type_id == 0) {
        static const GTypeInfo interface_info = {
            sizeof(FakeIServerInterface),   /* class_size */
            fake_iserver_base_init,         /* base_init */
            NULL,                           /* base_finalize */
        };
        type_id = g_type_register_static(
            G_TYPE_INTERFACE, "FakeIServerInterface", &interface_info, 0);
    }
    return type_id;
}

As described in the official document, we should allocate dynamic memebers of class struct inbase_init(). Otherwise, all copies of the class struct share only one copy of dynamic members. This leads to problems.

Let’s define the type which implements the interface:

// fakedesktop.h
#ifndef FAKE_DESKTOP_H_
#define FAKE_DESKTOP_H_

#include <glib-object.h>

#define FAKE_TYPE_DESKTOP              ( fake_desktop_get_type() )
#define FAKE_DESKTOP(obj)              \
 ( G_TYPE_CHECK_INSTANCE_CAST((obj), FAKE_TYPE_DESKTOP, FakeDesktop) )
#define FAKE_IS_DESKTOP(obj)           \
 ( G_TYPE_CHECK_INSTANCE_TYPE((obj), FAKE_TYPE_DESKTOP) )
#define FAKE_DESKTOP_CLASS(cls)        \
 ( G_TYPE_CHECK_CLASS_CAST((cls), FAKE_TYPE_DESKTOP, FakeDesktopClass) )
#define FAKE_IS_DESKTOP_CLASS(cls)     \
 ( G_TYPE_CHECK_CLASS_TYPE((cls), FAKE_TYPE_DESKTOP) )
#define FAKE_DESKTOP_GET_CLASS(obj)    \
 ( G_TYPE_INSTANCE_GET_CLASS((obj), FAKE_TYPE_DESKTOP, FakeDesktopClass ) )

/* Base object struct */
typedef struct _FakeDesktop {
    /* GObject as the first field */
    GObject parent;
} FakeDesktop;

/* Base class struct */
typedef struct _FakeDesktopClass {
    /* GObjectClass as the first field */
    GObjectClass parent;
} FakeDesktopClass;

/* type method */
GType fake_desktop_get_type();


#endif /* FAKE_DESKTOP_H_ */

Note the naming convention I used here. Our FakeDesktop class will implement the FakeIServer interface and another FakeIClient interface. This time do not use corresponding interface struct as the first members of FakeDesktop and FakeDesktopClass. Interface info will be added dynamically when initialize a real instance of FakeDesktop. Let’s move to the *.c code:

// fakedesktop.c
#include "fakedesktop.h"
#include "fakeiface.h"

void fake_desktop_request(FakeIClient *instance) {
    g_print("Invoking fake_desktop_request()\n");
}

void fake_desktop_response(FakeIServer *instance) {
    g_print("Invoking fake_desktop_response()\n");
}

static void fake_desktop_class_init(FakeDesktopClass *klass, gpointer data) {
}

static void fake_desktop_instance_init(FakeDesktop *instance, gpointer data) {
}

static void fake_desktop_interface_init_iclient(FakeIClientInterface* iface, gpointer iface_data) {
    iface->request = fake_desktop_request;
}

static void fake_desktop_interface_init_iserver(FakeIServerInterface* iface, gpointer iface_data) {
    iface->response = fake_desktop_response;
}

GType fake_desktop_get_type() {
    static GType type_id = 0;
    if (type_id == 0) {
        static const GTypeInfo type_info = {
            sizeof(FakeDesktopClass),  /* class_size */
            NULL,                   /* base_init */
            NULL,                   /* base_finalize */
            (GClassInitFunc)fake_desktop_class_init, /* class_init */
            NULL,                   /* class_finalize */
            NULL,                   /* class_data */
            sizeof(FakeDesktop),    /* instance_size */
            0,                      /* n_preallocs */
            (GInstanceInitFunc)fake_desktop_instance_init, /* instance_init */
            NULL                    /* value_table */
        };
        type_id = g_type_register_static(G_TYPE_OBJECT, "FakeDesktopClass", &type_info, 0);

        /* add interface */
        GInterfaceInfo interface_info_iclient = {
            (GInterfaceInitFunc)fake_desktop_interface_init_iclient, /* interface_init */
            NULL,   /* interface_finalize */
            NULL,   /* interface_data */
        };
        GInterfaceInfo interface_info_iserver = {
            (GInterfaceInitFunc)fake_desktop_interface_init_iserver, /* interface_init */
            NULL,   /* interface_finalize */
            NULL,   /* interface_data */
        };
        g_type_add_interface_static(type_id, FAKE_TYPE_ICLIENT, &interface_info_iclient);
        g_type_add_interface_static(type_id, FAKE_TYPE_ISERVER, &interface_info_iserver);
    }

    return type_id;
}

Note the g_type_add_interface_static() function call to add interface info. The interface info is defined in a GInterfaceInfo struct. We just make use of the interface_init() callback. In it, we assign function pointers of corresponding interface to our implementation function. We can add multiple interface infos to implement them.

Finally, the test code:

// main.c
#include "fakeiface.h"
#include "fakedesktop.h"
#include "fakelaptop.h"
#include <glib-object.h>

void my_dump_type(GType type_id) {
    g_print("Type id: %d\n", type_id);
    g_print("Type name: %s\n", g_type_name(type_id));
    g_print("Is fundamental? %s\n", G_TYPE_IS_FUNDAMENTAL(type_id) ? "yes" : "no");
    g_print("Is derived? %s\n", G_TYPE_IS_DERIVED(type_id) ? "yes" : "no");
    g_print("Is interface? %s\n", G_TYPE_IS_INTERFACE(type_id) ? "yes" : "no");
    g_print("Is classed? %s\n", G_TYPE_IS_CLASSED(type_id) ? "yes" : "no");
    g_print("Is instantiatable? %s\n", G_TYPE_IS_INSTANTIATABLE(type_id) ? "yes" : "no");
    g_print("Is derivable? %s\n", G_TYPE_IS_DERIVABLE(type_id) ? "yes" : "no");
    g_print("Is deep derivable? %s\n", G_TYPE_IS_DEEP_DERIVABLE(type_id) ? "yes" : "no");
    g_print("Is abstract? %s\n", G_TYPE_IS_ABSTRACT(type_id) ? "yes" : "no");
    g_print("Is value abstract? %s\n", G_TYPE_IS_VALUE_ABSTRACT(type_id) ? "yes" : "no");
    g_print("Is value type: %s\n", G_TYPE_IS_VALUE_TYPE(type_id) ? "yes" : "no");
    g_print("Has value table: %s\n", G_TYPE_HAS_VALUE_TABLE(type_id) ? "yes" : "no");
}

int main() {
    g_type_init();
    my_dump_type(FAKE_TYPE_ICLIENT);
    my_dump_type(FAKE_TYPE_ISERVER);
    my_dump_type(FAKE_TYPE_LAPTOP);
    my_dump_type(FAKE_TYPE_DESKTOP);

    FakeLaptop *laptop = (FakeLaptop *)g_object_new(FAKE_TYPE_LAPTOP, NULL);
    FakeDesktop *desktop = (FakeDesktop *)g_object_new(FAKE_TYPE_DESKTOP, NULL);
    g_print("laptop is FakeIServer? %s\n", FAKE_IS_ISERVER(laptop) ? "yes" : "no");
    g_print("laptop is FakeIClient? %s\n", FAKE_IS_ICLIENT(laptop) ? "yes" : "no");
    g_print("desktop is FakeIServer? %s\n", FAKE_IS_ISERVER(desktop) ? "yes" : "no");
    g_print("desktop is FakeIClient? %s\n", FAKE_IS_ICLIENT(desktop) ? "yes" : "no");

    /* Polynophysm */
    int i;
    FakeIServer *servers[2] = { (FakeIServer *)laptop, (FakeIServer *)desktop };
    for (i = 0; i < 2; i++) {
        FakeIServer *inst = servers[i];
        FakeIServerInterface *iface = FAKE_ISERVER_GET_INTERFACE(inst);
        if (iface) {
            iface->response(inst);
        }
    }
    FakeIClient *clients[2] = { (FakeIClient *)laptop, (FakeIClient *)desktop };
    for (i = 0; i < 2; i++) {
        FakeIClient *inst = clients[i];
        FakeIClientInterface *iface = FAKE_ICLIENT_GET_INTERFACE(inst);
        if (iface) {
            iface->request(inst);
        }
    }

    return 0;
}

In runtime, if your classed type implements an interface, it will be considered as the interface type (is-a).

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject6 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (7) – Signals

March 11th, 2011 No comments

Signals in GObject are used to support a event-driven programming. Signals can be connected to callback handlers. When they are emitted, these handlers are invoked. To add signals to a type, notice following lines of code:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

static void fake_base_class_init(FakeBaseClass *klass, gpointer data) {
    // ...
    /* signals */
    g_signal_new("base-signal-int", FAKE_TYPE_BASE, G_SIGNAL_RUN_LAST, 0,
        NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT, NULL);
    g_signal_new("base-signal-string", FAKE_TYPE_BASE, G_SIGNAL_RUN_LAST, 0,
        NULL, NULL, g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING, NULL);
    // ...
}

Like properties, signals can be inherited too. Use signals like:

int main() {
    // ...
    FakeBase *base = (FakeBase *)g_object_new(FAKE_TYPE_BASE, NULL);
    FakeDerived *derived = (FakeDerived *)g_object_new(FAKE_TYPE_DERIVED, NULL);
    /* Test for signals */
    /* 1 <-> 1 */
    g_signal_connect(base, "base-signal-int", G_CALLBACK(print_int1), NULL);
    g_signal_connect(base, "base-signal-string", G_CALLBACK(print_string1), NULL);
    g_signal_emit_by_name(base, "base-signal-int", 12345);
    g_signal_emit_by_name(base, "base-signal-string", "abcde");
    /* 1 <-> 1+ */
    g_signal_connect(base, "base-signal-int", G_CALLBACK(print_int2), NULL);
    g_signal_emit_by_name(base, "base-signal-int", 123456);
    /* signal inheritance */
    g_signal_connect(derived, "base-signal-int", G_CALLBACK(print_int1), NULL);
    g_signal_connect(derived, "base-signal-string", G_CALLBACK(print_string1), NULL);
    g_signal_connect(derived, "derived-signal-int", G_CALLBACK(print_int2), NULL);
    g_signal_connect(derived, "derived-signal-string", G_CALLBACK(print_string2), NULL);
    g_signal_emit_by_name(derived, "base-signal-int", 1234567);
    g_signal_emit_by_name(derived, "base-signal-string", "abcdefg");
    g_signal_emit_by_name(derived, "derived-signal-int", 1234567);
    g_signal_emit_by_name(derived, "derived-signal-string", "abcdefg");
    // ...
}

The callback functions are defined simply like:

void print_int1(GObject *sender, int i, gpointer data) {
    if (FAKE_IS_DERIVED(sender)) {
        g_print("Invoking print_int1(): derived.i=%d\n", i);
    } else if (FAKE_IS_BASE(sender)) {
        g_print("Invoking print_int1(): base.i=%d\n", i);
    }
}

void print_int2(GObject *sender, int i, gpointer data) {
    if (FAKE_IS_DERIVED(sender)) {
        g_print("Invoking print_int2(): derived.i=%d\n", i);
    } else if (FAKE_IS_BASE(sender)) {
        g_print("Invoking print_int2(): base.i=%d\n", i);
    }
}

void print_string1(GObject *sender, gchar* str, gpointer data) {
    if (FAKE_IS_DERIVED(sender)) {
        g_print("Invoking print_string1(): derived.str=\"%s\"\n", str);
    } else if (FAKE_IS_BASE(sender)) {
        g_print("Invoking print_string1(): base.str=\"%s\"\n", str);
    }
}

void print_string2(GObject *sender, gchar* str, gpointer data) {
    if (FAKE_IS_DERIVED(sender)) {
        g_print("Invoking print_string2(): derived.str=\"%s\"\n", str);
    } else if (FAKE_IS_BASE(sender)) {
        g_print("Invoking print_string2(): base.str=\"%s\"\n", str);
    }
}

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject5 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (6) – Properties

March 11th, 2011 No comments

Properties are supported by the GObject library. To use it, you should override the set_property() and get_property() function in GObjectClass:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

enum {
    PROP_0,
    PROP_BASE_ID,
    PROP_BASE_NAME
};

static void fake_base_set_property(GObject *object,
    guint property_id, const GValue *value, GParamSpec *pspec) {
    FakeBase *self = FAKE_BASE(object);
    switch (property_id) {
        case PROP_BASE_ID:
            self->priv->id = g_value_get_int(value);
            break;
        case PROP_BASE_NAME:
            g_free(self->priv->name);
            self->priv->name = g_value_dup_string(value);
            break;
        default:
            /* We don't have any other property... */
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void fake_base_get_property(GObject *object,
    guint property_id, GValue *value, GParamSpec *pspec) {
    FakeBase *self = FAKE_BASE(object);
    switch (property_id) {
        case PROP_BASE_ID:
            g_value_set_int(value, self->priv->id);
            break;
        case PROP_BASE_NAME:
            g_value_set_string(value, self->priv->name);
            break;
        default:
            /* We don't have any other property... */
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
            break;
    }
}

static void fake_base_class_init(FakeBaseClass *klass, gpointer data) {
    // ...
    /* properties */
    GObjectClass *gobject_klass = G_OBJECT_CLASS(klass);
    gobject_klass->set_property = fake_base_set_property;
    gobject_klass->get_property = fake_base_get_property;
    GParamSpec *pspec;
    pspec = g_param_spec_int("base-id", "Base ID", 
        "Set/Get Base ID", -1000, 1000, 0, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
    g_object_class_install_property(gobject_klass, PROP_BASE_ID, pspec);
    pspec = g_param_spec_string("base-name",
        "Base Name", "Set/Get Base Name ", NULL, G_PARAM_READWRITE);
    g_object_class_install_property(gobject_klass, PROP_BASE_NAME, pspec);
    // ...
}

All APIs are clear and easy to use, please refer to the official document. Last but not least, properties can be inherited by derived classes. Here’s my test code:

int main() {
    // ...
    /* Base object */
    FakeBase *base = (FakeBase *)g_object_new(FAKE_TYPE_BASE, "base-id", 111, NULL);
    GValue base_name = { 0 };
    g_value_init(&base_name, G_TYPE_STRING);
    g_value_set_static_string(&base_name, "aaa");
    g_object_set_property(G_OBJECT(base), "base-name", &base_name);
    g_value_unset(&base_name);
    /* Derived object */
    FakeDerived *derived = (FakeDerived *)g_object_new(
        FAKE_TYPE_DERIVED, "base-id", 222, "derived-age", 333, NULL);
    g_object_set(derived, "base-name", "bbb", "derived-hash", "ccc", NULL);
    // ...
}

As you see, we can get/set properties one by one or using a parameter list.

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject5 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (5) – Private Members

March 11th, 2011 No comments

Here’s some trivial note on using GObject library.

1. Private members

Recall our definition of Base type:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

/* Base object struct */
typedef struct _Base {
    GTypeInstance parent;
    /* instance variable, should be hidden */
    gint base_instance_i;
} Base;

/* Base class struct */
typedef struct _BaseClass {
    GTypeClass parent;
    /* instance method, used as a virtual method */
    void (*base_instance_dump)(struct _Base *instance);
} BaseClass;

It expose the visibility of base_instance_i field. We should keep encapsulation in OOP. GObject library has support for this. We can define the class as:

/* private data of Base object */
typedef struct _FakeBasePrivate FakeBasePrivate;

/* Base object struct */
typedef struct _FakeBase {
    /* GObject as the first field */
    GObject parent;
    /* private data */
    FakeBasePrivate *priv;
} FakeBase;

/* Base class struct */
typedef struct _FakeBaseClass {
    /*
     * The type GObject is supposed to be the base class of other user-defined classes.
     *   - Reference count support.
     *   - Support adding properties to GObject subclasses.
     *   - Support signals for asynchronized event handling like "event" in C#.
     */
    /* GObjectClass as the first field */
    GObjectClass parent;
    /*
     * Since glib 2.24, there're new functions to keep privacy:
     *   - g_type_add_class_private()
     *   - g_type_class_get_private()
     */
    /* private static field */
    gint version;
    /* private dynamic field */
    gchar *author;
    /* instance method, used as a virtual method */
    void (*virtual_dump)(struct _FakeBase *instance);
} FakeBaseClass;

We declare a new FakeBasePrivate struct to contain all private field used in FakeBase type. And the private struct is defined in *.c file, so its internal representation remains invisible. Then in *.c file, we got:

struct _FakeBasePrivate {
    gint id;
    gchar *name;
};

static void fake_base_class_init(FakeBaseClass *klass, gpointer data) {
    // ...
    /* Registers a private structure for an instantiable type. */
    g_type_class_add_private(klass, sizeof(FakeBasePrivate));
    // ...
}

static void fake_base_instance_init(FakeBase *instance, gpointer data) {
    // ...
    instance->priv = 
        G_TYPE_INSTANCE_GET_PRIVATE(instance, FAKE_TYPE_BASE, FakeBasePrivate);
    // ...
}

The private member is malloc in class_init() callback, and is ready to use after invoking instance_init(). When we will use property mechanism to get/set these private field later.

2. Naming convention

Official document: http://library.gnome.org/devel/gobject/stable/gtype-conventions.html. Just follow it to make your code more readable.

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject5 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (4) – An Inheritable Class

March 11th, 2011 No comments

Now, we will begin to implement some real OO mechanism using GObject library. In this article, we will make our fundamental type Inheritable.

Here’s comes our Base type:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

// base.h
#ifndef BASE_H_
#define BASE_H_

#include <glib-object.h>

/* Base object struct */
typedef struct _Base {
    GTypeInstance parent;
    /* instance variable, should be hidden */
    gint base_instance_i;
} Base;

/* Base class struct */
typedef struct _BaseClass {
    GTypeClass parent;
    /* instance method, used as a virtual method */
    void (*base_instance_dump)(struct _Base *instance);
} BaseClass;

/* static method of Base class */
void base_class_set_i(gint i);

/* non-virtual public method for Base object */
void base_instance_set_i(Base *instance, gint i);

/* virtual public method for Base object, both version are supported */
void base_instance_dump(Base *instance);

/* type method */
GType base_get_type();


#endif /* BASE_H_ */
// base.c
#include "base.h"

/* static field of Base class */
gint base_class_i;

/* static method of Base class */
void base_class_set_i(gint i) {
    base_class_i = i;
    g_print("Invoking base_class_set_i(): base_class_i=%d\n", base_class_i);
}

void base_instance_set_i(Base *instance, gint i) {
    instance->base_instance_i = i;
    g_print("Invoking base_instance_set_i(): base_instance_i=%d\n", instance->base_instance_i);
}

void base_instance_dump(Base *instance) {
    g_print("Invoking base_instance_dump(): base_instance_i=%d\n", instance->base_instance_i);
}

static void base_class_init(BaseClass *klass, gpointer data) {
    base_class_i = 100;
    g_print("Calling base_class_init(): base_class_i=%d\n", base_class_i);
    klass->base_instance_dump = base_instance_dump;
}

static void base_instance_init(Base *instance, gpointer data) {
    instance->base_instance_i = 200;
    g_print("Calling base_instance_init(): base_instance_i=%d\n", instance->base_instance_i);
}

GType base_get_type() {
    static GType base_type = 0;
    if (base_type == 0) {
        static const GTypeInfo base_type_info = {
            sizeof(BaseClass),  /* class_size */
            NULL,               /* base_init */
            NULL,               /* base_finalize */
            (GClassInitFunc)base_class_init, /* class_init */
            NULL,               /* class_finalize */
            NULL,               /* class_data */
            sizeof(Base),       /* instance_size */
            0,                  /* n_preallocs */
            (GInstanceInitFunc)base_instance_init, /* instance_init */
            NULL                /* value_table */
        };
        GTypeFundamentalInfo foo_type_fundamental_info = {
            G_TYPE_FLAG_CLASSED           /* Indicates a classed type */
            | G_TYPE_FLAG_INSTANTIATABLE  /* Indicates an instantiable type */
            | G_TYPE_FLAG_DERIVABLE       /* Indicates a flat derivable type */
            | G_TYPE_FLAG_DEEP_DERIVABLE  /* Indicates a deep derivable type */
        };
        base_type = g_type_register_fundamental(g_type_fundamental_next(),
            "BaseFundamentalType", &base_type_info, &foo_type_fundamental_info, 0);
    }
    return base_type;
}

In base_instance_init(), we assigned the base_instance_dump() callback. Thus, we can invoke this function by both global function or instance function of BaseClass class. Additional flags G_TYPE_FLAG_DERIVABLE and G_TYPE_FLAG_DEEP_DERIVABLE are also passed to the GTypeFundamentalInfo struct to enable inheritance.

It’s time to define our Derived type:

// derived.h
#ifndef DERIVED_H_
#define DERIVED_H_

#include "base.h"
#include <glib-object.h>

/* Derived object struct */
typedef struct _Derived {
    /* The GTypeClass structure is still the first member of the class structure */
    Base parent;
    /* should be hidden */
    gint derived_instance_i;
} Derived;

/* Derived class struct */
typedef struct _DerivedClass {
    /* The TypeInstance structure is still the first member of the instance structure */
    BaseClass parent;
} DerivedClass;

/* non-virtual public method for Derived object */
void derived_instance_set_i(Derived *instance, gint i);

/* (Overwrite) virtual public method for Derived object, both version are supported */
void derived_instance_dump(Base *instance);

/* type method */
GType derived_get_type();


#endif /* DERIVED_H_ */
// derived.c
#include "derived.h"

void derived_instance_set_i(Derived *instance, gint i) {
    instance->derived_instance_i = i;
    g_print("Invoking derived_instance_set_i(): derived_instance_i=%d\n", instance->derived_instance_i);
}

void derived_instance_dump(Base *instance) {
    Derived *derived = G_TYPE_CHECK_INSTANCE_CAST(instance, derived_get_type(), Derived);
    g_print("Invoking derived_instance_dump(): base_instance_i=%d, derived_instance_i=%d\n", instance->base_instance_i, derived->derived_instance_i);
}

static void derived_class_init(DerivedClass *klass, gpointer data) {
    g_print("Calling derived_class_init()\n");
    base_class_set_i(300);
    /* override */
    BaseClass *base_klass = G_TYPE_CHECK_CLASS_CAST(klass, base_get_type(), BaseClass);
    base_klass->base_instance_dump = derived_instance_dump;
}

static void derived_instance_init(Derived *instance, gpointer data) {
    instance->derived_instance_i = 400;
    g_print("Calling derived_instance_init(): derived_instance_i=%d\n", instance->derived_instance_i);
}

GType derived_get_type() {
    static GType derived_type = 0;
    if(derived_type == 0) {
        static const GTypeInfo derived_type_info = {
            sizeof(DerivedClass), /* class_size */
            NULL,               /* base_init */
            NULL,               /* base_finalize */
            (GClassInitFunc)derived_class_init, /* class_init */
            NULL,               /* class_finalize */
            NULL,               /* class_data */
            sizeof(Derived),    /* instance_size */
            0,                  /* n_preallocs */
            (GInstanceInitFunc)derived_instance_init, /* instance_init */
            NULL                /* value_table */
        };
        derived_type = g_type_register_static(
            base_get_type(), "DerivedStaticClass", &derived_type_info, 0);
    }
    return derived_type;
}

Our Derived type inherits Base by replacing GTypeClass and GTypeInstance with the corresponding struct of the Base type. According to the memory layout of structs, GTypeClass and GTypeInstance are still the first member of corresponding struct. In derived_get_type(), we register Derived type using g_type_register_static() since it’s not a fundamental at all. And the first parameter is the type id of Base type.

Let’s have some time to look up how to implement polymorphism. In derived_class_init(), we re-assign the base_instance_dump() callback to from the Base‘s implementation to Derived‘s implementation.

Test code:

int main() {
    g_type_init();
    my_dump_type(base_get_type());
    my_dump_type(derived_get_type());

    /*
     * Official document:
     * Use of g_type_create_instance() is reserved for implementators of
     * fundamental types only. E.g. instances of the GObject hierarchy should
     * be created via g_object_new() and never directly through
     * g_type_create_instance() which doesn't handle things like singleton
     * objects or object construction.
     */
    Base *base = (Base *)g_type_create_instance(base_get_type());
    base_class_set_i(101);
    base_instance_set_i(base, 201);
    Derived *derived = (Derived *)g_type_create_instance(derived_get_type());
    derived_instance_set_i(derived, 401);

    /* Test polymorphism */
    Base *instances[2] = { base, (Base *)derived };
    int i;
    for (i = 0; i < 2; i++) {
        Base *inst = instances[i];
        BaseClass *klass = G_TYPE_INSTANCE_GET_CLASS(inst, base_get_type(), BaseClass);
        klass->base_instance_dump(inst);
    }

    return 0;
}

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject4 folder.

Categories: C/C++ Tags: ,

OOP Using GObject (3) – An Instantiatable Class

March 11th, 2011 No comments

We will make fundamental type instantiatable and complete our first usage sample in this article. An object class should be defined firstly:

NOTE: PLEASE READ ALL COMMENT CAREFULLY.

/* Foo object struct */
typedef struct _Foo {
    /*
     * Official document:
     * All instance structures must contain as first member a TypeInstance structure.
     */
    GTypeInstance parent;
    /* instance variable */
    int foo_instance_i;
} Foo;

Also, we re-define the class struct:

/* Foo class struct */
typedef struct _FooClass {
    /*
     * Official document:
     * All class structures must contain as first member a GTypeClass structure.
     */
    GTypeClass parent;
} FooClass;

/* static field of Foo class */
int foo_class_i;

/* static method of Foo class */
void foo_class_set_i(int i) {
    foo_class_i = i;
    printf("Invoking foo_class_set_i(): foo_class_i=%d\n", foo_class_i);
}

GTypeClass should be the first member of a class struct, while TypeInstance the first member of a object struct. You may wonder why there’s two int variable in both struct. The foo_class_i is like a static variable in C++ class, while The foo_instance_i is like an instance variable in C++ class. And remember fields in a class struct? It is used as meta info.

The registry function also need modification:

GType foo_get_type() {
    static GType foo_type = 0;
    if (foo_type == 0) {
        static const GTypeInfo foo_type_info = {
            sizeof(FooClass),   /* class_size */
            NULL,               /* base_init */
            NULL,               /* base_finalize */
            (GClassInitFunc)foo_class_init, /* class_init */
            NULL,               /* class_finalize */
            NULL,               /* class_data */
            sizeof(Foo),        /* instance_size */
            0,                  /* n_preallocs */
            (GInstanceInitFunc)foo_instance_init, /* instance_init */
            NULL                /* value_table */
        };
        /* G_TYPE_FLAG_INSTANTIATABLE: Indicates an instantiable type (implies classed) */
        GTypeFundamentalInfo foo_type_fundamental_info = {
            G_TYPE_FLAG_CLASSED | G_TYPE_FLAG_INSTANTIATABLE
        };
        foo_type = g_type_register_fundamental(g_type_fundamental_next(),
            "FooClassedFundamentalType", &foo_type_info, &foo_type_fundamental_info, 0);
    }
    return foo_type;
}

We assigned the instance_init() callback. It is called when a instance of our Foo class is created. You may ask where is the corresponding instance_finalize() callback? Hey, we will discuss it in upcoming articles. The instance_init() callback can be regarded as the constructor of a C++ class. Note, an additional G_TYPE_FLAG_INSTANTIATABLE flag is also added in the GTypeFundamentalInfo struct.

Let’s see how to create an instance:

int main() {
    g_type_init();
    my_dump_type(foo_get_type());

    /* Use g_type_create_instance if implement a fundamental class */
    Foo *foo = (Foo *)g_type_create_instance(foo_get_type());
    foo_class_set_i(101);
    foo_instance_set_i(foo, 201);

    printf("Is instance of int? %s\n",
        G_TYPE_CHECK_INSTANCE_TYPE(foo, G_TYPE_INT) ? "yes" : "no");
    printf("Is instance of FooClassedFundamentalType? %s\n",
        G_TYPE_CHECK_INSTANCE_TYPE(foo, foo_get_type()) ? "yes" : "no");

    return 0;
}

Congratulations! You’ve finished the learning of our fundamental sample.

All source code is available in my skydrive: http://cid-481cbe104492a3af.office.live.com/browse.aspx/share/dev/TestOO. In the TestGObject-{date}.zip/TestGObject3 folder.

Categories: C/C++ Tags: ,