Apr 22

       众所周知,在一个GtkAboutDialog控件中,是可以用gtk_about_dialog_set_logo来设置logo的,其函数原型如下:

void                gtk_about_dialog_set_logo           (GtkAboutDialog *about,
                                                         GdkPixbuf *logo);

       显而易见,logo在这里是以GdkPixbuf控件的形式插入到GtkAboutDialog控件about中的,然而这里却暗藏了一个陷阱,而这个陷阱,有可能导致严重的内存泄漏。

       我们不妨以一下一段代码为例(编译为test_about_dialog):

#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>

void show_about_dialog(GtkWidget *button);

int main(int argc,char **argv)
{
	GtkWidget *window;
	GtkWidget *button;
	
	gtk_init(&argc,&argv);

	window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
	
	button=gtk_button_new_with_label("Click me!");
	gtk_container_add(GTK_CONTAINER(window),button);
	
	g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(show_about_dialog),NULL);
	g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);

	gtk_widget_show_all(window);
	
	gtk_main();

	return 0;
}

void show_about_dialog(GtkWidget *button)
{
	GtkWidget *about_dialog;
	GdkPixbuf *logo;
	
	about_dialog=gtk_about_dialog_new();
	logo=gdk_pixbuf_new_from_file("./test.png",NULL);
	gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
	
	gtk_dialog_run(GTK_DIALOG(about_dialog));

	gtk_widget_destroy(about_dialog);

	return;

}


       程序运行以后,出来一个窗体,这时我们打开系统监视器(如果你熟悉命令行的话,用top命令也可以),这时可以看到test_about_dialog运行时占用了约1.4M的内存:

       这时我们点一下"Click me"按钮,test_about_dialog占用的内存量上升到约3.9M:

       此时,我们关闭AboutDialog,然后再次点击"click me"按钮以打开AboutDialog,此时,test_about_dialog占用的内存量已经上升到了5.9M:

       多次重复这一操作,test_about_dialog占用的内存会越来越多,并且,每次关闭AboutDialog以后,内存占用量并不降低,显然,AboutDialog中出现了内存泄漏。

       而事实上,关键就在于这句代码:

	gtk_widget_destroy(about_dialog);

       按照惯例,这句代码将释放about_dialog即其子控件所占用的所有资源,然而事实呢?

       我们可以看一下gtk_widget_destroy的代码:

void
gtk_widget_destroy (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));

  gtk_object_destroy ((GtkObject*) widget);
}

        显然,它调用了gtk_object_destroy,而gtk_object_destroy的代码如下:

void
gtk_object_destroy (GtkObject *object)
{
  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_OBJECT (object));
  
  if (!(GTK_OBJECT_FLAGS (object) & GTK_IN_DESTRUCTION))
    g_object_run_dispose (G_OBJECT (object));
}

         而g_object_run_dispose代码如下:

void
g_object_run_dispose (GObject *object)
{
  g_return_if_fail (G_IS_OBJECT (object));
  g_return_if_fail (object->ref_count > 0);

  g_object_ref (object);
  TRACE (GOBJECT_OBJECT_DISPOSE(object,G_TYPE_FROM_INSTANCE(object), 0));
  G_OBJECT_GET_CLASS (object)->dispose (object);
  TRACE (GOBJECT_OBJECT_DISPOSE_END(object,G_TYPE_FROM_INSTANCE(object), 0));
  g_object_unref (object);
}

         而GtkAboutDialog的dispose函数继承自GtkDialog,而GtkDialog的dispose函数又继承自GtkWindow,而GtkWindow的dispose函数如下所示:

static void
gtk_window_dispose (GObject *object)
{
  GtkWindow *window = GTK_WINDOW (object);

  gtk_window_set_focus (window, NULL);
  gtk_window_set_default (window, NULL);

  G_OBJECT_CLASS (gtk_window_parent_class)->dispose (object);
}

         事实上,dispose函数将会自底向上一层层地执行下去,那究竟是哪个语句最终导致资源被释放呢?

         其实就是g_object_run_dispose中的这个语句:

  g_object_unref (object);

         他将使得object的ref_count降到零,进而调用dispose函数以及destroy函数,而真正负责释放资源的,是destroy函数。由destroy函数的特点知,该函数将会自底向上一层层地执行。而实现释放其子控件占用的资源的,是GtkContainer的destroy函数:

static void
gtk_container_destroy (GtkObject *object)
{
  GtkContainer *container = GTK_CONTAINER (object);

  if (GTK_CONTAINER_RESIZE_PENDING (container))
    _gtk_container_dequeue_resize_handler (container);

  if (container->focus_child)
    {
      g_object_unref (container->focus_child);
      container->focus_child = NULL;
    }

  /* do this before walking child widgets, to avoid
   * removing children from focus chain one by one.
   */
  if (container->has_focus_chain)
    gtk_container_unset_focus_chain (container);

  gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);

  GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

         事实上,这段中真正负责销毁控件的,便是这句:

  gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);

         而gtk_container_foreach代码如下:

void
gtk_container_foreach (GtkContainer *container,
		       GtkCallback   callback,
		       gpointer      callback_data)
{
  GtkContainerClass *class;
  
  g_return_if_fail (GTK_IS_CONTAINER (container));
  g_return_if_fail (callback != NULL);

  class = GTK_CONTAINER_GET_CLASS (container);

  if (class->forall)
    class->forall (container, FALSE, callback, callback_data);
}

     从这里我们可以看出,gtk_container_destroy将对所有子控件调用gtk_widget_destroy使得它们的ref_count都降1,如果它们的ref_count都等于1的话,显然这些子控件将被销毁——由此可见,用gtk_widget_destroy销毁一个控件及其子控件是有条件的——他们的ref_count都必须等于1,而这一般都是可以满足的——如果你不单独对某个控件使用g_object_ref的话。更重要的是,forall的代码也明确的指出,子控件指的是加入到box中的children链表中的控件(即用gtk_box_pack_start等函数加入到box中的控件)。注意:这里的forall实质上是一个纯虚函数指针,因为GtkContainer控件中的forall指针为NULL,而该指针到box控件中将会被重载:

 container_class->forall = gtk_box_forall;

       而gtk_box_forall的代码如下:

static void
gtk_box_forall (GtkContainer *container,
		gboolean      include_internals,
		GtkCallback   callback,
		gpointer      callback_data)
{
  GtkBox *box = GTK_BOX (container);
  GtkBoxChild *child;
  GList *children;

  children = box->children;
  while (children)
    {
      child = children->data;
      children = children->next;

      if (child->pack == GTK_PACK_START)
	(* callback) (child->widget, callback_data);
    }

  children = g_list_last (box->children);
  while (children)
    {
      child = children->data;
      children = children->prev;

      if (child->pack == GTK_PACK_END)
	(* callback) (child->widget, callback_data);
    }
}

       那为什么对GtkAboutDialog控件about调用gtk_widget_destroy并不能销毁控件logo呢?答案已经很显然了,logo并不是GtkAboutDialog的子控件!所以,你如果对GtkAboutDialog控件about调用gtk_widget_destroy的话,并不能销毁logo控件!换句话说,logo控件仍然会占用相应的内存资源,而这正是内存泄漏出现的关键!

       那为什么logo控件不是GtkAboutDialog的子控件呢?

       这是因为把logo控件“加入”到窗体,实质是把它转成GtkImage控件logo_image,并调用gtk_box_pack_start把logo_image加入到GtkAboutDialog控件的窗体中实现的。相关代码如下:

void
gtk_about_dialog_set_logo (GtkAboutDialog *about,
                           GdkPixbuf      *logo)
{
  GtkAboutDialogPrivate *priv;

  g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));

  priv = (GtkAboutDialogPrivate *)about->private_data;

  g_object_freeze_notify (G_OBJECT (about));

  if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
    g_object_notify (G_OBJECT (about), "logo-icon-name");

  if (logo != NULL)
    gtk_image_set_from_pixbuf (GTK_IMAGE (priv->logo_image), logo);
  else
    {
      GList *pixbufs = gtk_window_get_default_icon_list ();

      if (pixbufs != NULL)
        {
          GtkIconSet *icon_set = icon_set_new_from_pixbufs (pixbufs);

          gtk_image_set_from_icon_set (GTK_IMAGE (priv->logo_image),
                                       icon_set, GTK_ICON_SIZE_DIALOG);

          gtk_icon_set_unref (icon_set);
          g_list_free (pixbufs);
        }
    }

  g_object_notify (G_OBJECT (about), "logo");

  g_object_thaw_notify (G_OBJECT (about));
}

         把logo_image加入到GtkAboutDialog控件中的相关代码如下:

  priv->logo_image = gtk_image_new ();
  gtk_box_pack_start (GTK_BOX (vbox), priv->logo_image, FALSE, FALSE, 0);

         从中也可以看出,logo_name才是GtkAboutDialog的子控件。因此,对GtkAboutDialog控件调用gtk_widget_destroy,是无法销毁gtk_about_dialog_set_logo中logo指针所指向的GdkPixbuf控件的。而在我们写的示例程序中,每次执行show_about_dialog函数,都会执行:

	logo=gdk_pixbuf_new_from_file("./test.png",NULL);

         而在此之前,logo指针所指向的资源并没有被释放,于是内存泄漏就产生了。

         为了解决这一问题,我们需要对logo控件手动调用g_object_unref,故修改后的代码如下:

#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>

void show_about_dialog(GtkWidget *button);

int main(int argc,char **argv)
{
	GtkWidget *window;
	GtkWidget *button;
	
	gtk_init(&argc,&argv);

	window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
	
	button=gtk_button_new_with_label("Click me!");
	gtk_container_add(GTK_CONTAINER(window),button);
	
	g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(show_about_dialog),NULL);
	g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);

	gtk_widget_show_all(window);
	
	gtk_main();

	return 0;
}

void show_about_dialog(GtkWidget *button)
{
	GtkWidget *about_dialog;
	GdkPixbuf *logo;
	
	about_dialog=gtk_about_dialog_new();
	logo=gdk_pixbuf_new_from_file("./test.png",NULL);
	gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
	
	gtk_dialog_run(GTK_DIALOG(about_dialog));

	gtk_widget_destroy(about_dialog);
	g_object_unref(G_OBJECT(logo));

	return;

}

 

Apr 8

       今天不知道为什么,Ubuntu又崩溃了,考虑到安装的程序太多,不想重装,在网上搜了几个小时,终于找到一个有效的方法挽救了操作系统。

        整个操作的过程如下所示(文件夹名称lucid不需要更改):


        1.进入livecd

        2.在livecd的操作系统中连接到网络

        3.创建两个目录:

sudo mkdir /media/lucid
sudo mkdir /media/lucid/proc /media/lucid/dev /media/lucid/etc

        4.挂载Linux操作系统的/分区:

sudo mount /dev/sda1 /media/lucid

           注意:这是整个过程的关键所在——你必须自己确定你的/分区所在的设备。确定方法:打开nautilus,找到你的Ubuntu的/分区,打开该/分区下的etc目录中的fstab文件,找到mount-point(就是每一行的第二个值)为“/”的行,其上面的注释会指出/分区所在的设备

        5.绑定目录:

sudo mount -o bind /proc /media/lucid/proc
sudo mount -o bind /dev /media/lucid/dev/
sudo mount -o bind /dev/pts /media/lucid/dev/pts

        6.拷贝resolv.conf文件:

sudo cp /etc/resolv.conf /media/lucid/etc/resolv.conf

        7.更新真正的Ubuntu操作系统中的源信息:

sudo chroot /media/lucid apt-get update

        8.更新操作系统的所有软件包:

sudo chroot /media/lucid apt-get upgrade

        9.重启(livecd点重启需要再按一下Enter键),整个过程结束。


        总的来说,整个过程对人品有一定要求,需要谨慎使用。

        参考的帖子:

        [1] http://ubuntuforums.org/showthread.php?t=1584931

Mar 20

    在Linux中用xl2tpd建立L2TP协议的VPN连接需要两个配置文件,一个是给xl2tpd的配置文件,一个是给pppd的配置文件(xl2tpd会调用pppd)。

    为说明方便,我们假设我们要建立的VPN连接的名字为testvpn,连到的l2tp服务器地址为xxx.xxx.xxx.xxx,用户名为someone,密码为passwordstring。那么该xl2tpd的配置文件应该如下所示:

[global]
access control = no
port = 1701

[lac testvpn]
name = someone
lns = xxx.xxx.xxx.xxx
pppoptfile = /etc/ppp/peers/testvpn.l2tpd
ppp debug = no

    实际使用中,我们一般只需要修改四处:

  1. [lac testvpn]这一句将VPN连接的名字设置为testvpn,你应该把它改成你想要的名字
  2. name = someone这一句将您的用户名配置为someone,你应该把它改成你想要的名字
  3. lns = xxx.xxx.xxx.xxx这一句说明你要连接的服务器的地址为xxx.xxx.xxx.xxx
  4. pppoptfile = /etc/ppp/peers/testvpn.l2tpd告诉xl2tpd为该VPN连接调用pppd时,pppd的配置文件的路径为/etc/ppp/peers/testvpn.l2tpd,你应该把它改成你真正建立的pppd的名字

    以上这个文件被您修改并保存后,假设放在/etc/xl2tpd目录,名字为testvpn.options。

    下面,我们还需要一个pppd的配置文件,也就是之前提到的/etc/ppp/peers/testvpn.l2tpd。

    为此,我们打开/etc/ppp/peers目录,在其中新建一个文件testvpn.l2tpd(如果你在xl2tpd的配置文件中pppd配置文件写的是/etc/ppp/peers/testvpn.l2tpd的话)。结合之前有关VPN连接的说明,该pppd配置文件应该如下所示:

remotename testvpn
user "someone"
password "passwordstring"
unit 0
lock
nodeflate
nobsdcomp
noauth
persist
nopcomp
noaccomp
maxfail 5
debug

    实际使用,您一般只需要修改以下三处:

  1. remotename testvpn,这一句将VPN连接的名字设为testvpn,您应该把它改成您想要的名字
  2. user "someone",这一句将VPN连接的用户名设为someone,您应该把它改成您真正的用户名
  3. password "passwordstring",这一句将VPN连接的密码设为passwordstring,您应该把它改成您真正的密码

    至此,所有准备工作都完成了,下面我们需要做几件事情:

  1.切换到root用户

     2. 检查目录/var/run/xl2tpd是否存在。不存在的话,手动新建一个。

  3.启动xl2tpd

    在终端中输入下列命令并回车:

xl2tpd -c /etc/xl2tpd/testvpn.options

    你需要把/etc/xl2tpd/testvpn.options改成你的真正的xl2tpd配置文件的绝对路径(未测试相对路径是否可以)。

    4.建立VPN连接。

        在终端中输入下列命令并回车:

echo 'c testvpn' >/var/run/xl2tpd/l2tp-control

        当然,你需要把testvpn改成你的VPN连接的名字(即xl2tpd的配置文件中,跟在lac后面的词语)。

    5.查看VPN连接是否成功建立:

    如果是Ubuntu的话,一般需查看/var/log/syslog文件,如果文件中pppd条目中出现succeeded等词语,说明连接成功,VPN连接已经成功建立

         如果是Fedora的话,一般需改成查看/var/log/message文件。

         事实上,查看的文件是由/etc/rsyslog.conf的内容决定的。

    6.如果顺利的话,至此VPN连接已经成功建立,但是如果想让默认连接都走VPN的话(这也是通常的情况),我们还需要修改路由配置。

         在终端中输入下列命令并回车:

route del default
route add -net 0.0.0.0 netmask 0.0.0.0 dev ppp0

         至此,所有配置都已完成。

    7.断开VPN连接

    在终端中输入下列命令并回车:

echo 'd testvpn' >/var/run/xl2tpd/l2tp-control

         当然,你需要把testvpn改成你的VPN连接的名字(即xl2tpd的配置文件中,跟在lac后面的词语)。

 

PS:如果你觉得以上这些难以理解的话,可以使用vpnpptp软件帮助你建立L2TP协议的VPN连接(全图形化界面,非常方便)

        项目地址:http://code.google.com/p/vpnpptp/

Feb 28

    1.在Ubuntu中尝试用sudo pkill rsyslogd杀死rsyslogd,但该命令执行后rsyslogd仍然在运行。经实验发现,在Ubuntu中,第一次杀死rsyslogd之前一定要执行sudo stop rsyslog使rsyslogd停止运行,以后再杀死rsyslogd时则不需要。

    2.用rsyslogd命令启动rsyslogd时,加-f参数可以指定配置文件(而不是用默认的/etc/rsyslog.conf),但是该配置文件的路径必须是绝对路径,而不能是相对路径

Feb 20

    在GObject中,接口类型(Interface)是一种可以类化(classed)但不可以实例化(instantiable)的类型。所有接口类型都是G_TYPE_INTERFACE的子类,并且不能再被继承,但可以拥有一个乃至多个先决条件。事实上,接口类型都只有类结构类型,而没有实例结构类型。

    例如,GTK_TYPE_EDITABLE就是一个典型的接口类型:

typedef struct _GtkEditable       GtkEditable;         /* Dummy typedef */
typedef struct _GtkEditableClass  GtkEditableClass;

struct _GtkEditableClass
{
  GTypeInterface		   base_iface;

  /* signals */
  void (* insert_text)              (GtkEditable    *editable,
				     const gchar    *text,
				     gint            length,
				     gint           *position);
  void (* delete_text)              (GtkEditable    *editable,
				     gint            start_pos,
				     gint            end_pos);
  void (* changed)                  (GtkEditable    *editable);

  /* vtable */
  void (* do_insert_text)           (GtkEditable    *editable,
				     const gchar    *text,
				     gint            length,
				     gint           *position);
  void (* do_delete_text)           (GtkEditable    *editable,
				     gint            start_pos,
				     gint            end_pos);

  gchar* (* get_chars)              (GtkEditable    *editable,
				     gint            start_pos,
				     gint            end_pos);
  void (* set_selection_bounds)     (GtkEditable    *editable,
				     gint            start_pos,
				     gint            end_pos);
  gboolean (* get_selection_bounds) (GtkEditable    *editable,
				     gint           *start_pos,
				     gint           *end_pos);
  void (* set_position)             (GtkEditable    *editable,
				     gint            position);
  gint (* get_position)             (GtkEditable    *editable);
};

    从表面上来看,GTK_TYPE_EDITABLE类拥有一个实例结构类型GtkEditable。然而,正如注释所说,这一行代码是所谓的"Dummy typedef",GtkEditable类型其实并不存在(不过我们依然可以定义指向GtkEditable类型的指针变量,也可以将其它的指针类型转化成指向GtkEditable类型的指针类型)。

    为方便叙述,我们假设我们需要定义一个接口类型TYPE_HUMAN,该接口的先决条件是实现了接口TYPE_ANIMAL,而对象TYPE_BOY实现了该接口。

    完整的代码如下:

#include <glib-object.h>

#define TYPE_ANIMAL (animal_get_type())
#define ANIMAL(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_ANIMAL,Animal))
#define ANIMAL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_ANIMAL,AnimalInterface))

typedef struct _Animal Animal;
typedef struct _AnimalInterface AnimalInterface;


struct _AnimalInterface
{
    GTypeInterface base_iface;

    void (*sleep)(Animal *animal);
    void (*eat)(Animal *animal);
    void (*run)(Animal *animal);
};

void animal_sleep(Animal *animal);
void animal_eat(Animal *animal);
void animal_run(Animal *animal);


static void animal_default_init(AnimalInterface *iface);

G_DEFINE_INTERFACE(Animal,animal,G_TYPE_INVALID);

static void animal_default_init(AnimalInterface *iface)
{

}

void animal_sleep(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->sleep(animal);
}

void animal_eat(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->eat(animal);
}

void animal_run(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->run(animal);
}

/**********我是分割线**********/

#define TYPE_HUMAN (human_get_type())
#define HUMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_HUMAN,Human))
#define HUMAN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_HUMAN,HumanInterface))

typedef struct _Human Human;
typedef struct _HumanInterface HumanInterface;

struct _HumanInterface
{
    GTypeInterface base_iface;

    void (*say)(Human *human);
    void (*study)(Human *human);
};

void human_say(Human *human);
void human_study(Human *human);


static void human_default_init(HumanInterface *iface);

G_DEFINE_INTERFACE(Human,human,TYPE_ANIMAL);

static void human_default_init(HumanInterface *iface)
{
    static gboolean is_initialized=FALSE;
    
    if (!is_initialized)
    {
        g_object_interface_install_property(iface,g_param_spec_uint("age","Age","The age of the person",
                                            0,150,0,G_PARAM_READABLE|G_PARAM_WRITABLE));

        is_initialized=TRUE;
    }
}

void human_say(Human *human)
{
    HUMAN_GET_INTERFACE(human)->say(human);
}
void human_study(Human *human)
{
    HUMAN_GET_INTERFACE(human)->study(human);
}


/**********我是分割线**********/

#define TYPE_BOY (boy_get_type())
#define BOY(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_BOY,Boy))

typedef struct _Boy Boy;
typedef struct _BoyClass BoyClass;

struct _Boy
{
    GObject parent;

    GString *name;
    guint age;
};

struct _BoyClass
{
    GObjectClass parent_class;

};

GObject *boy_new_with_name_and_age(gchar *name,guint age);


enum
{
    PROP_0,
    PROP_AGE
};

static void boy_init(Boy *self);
static void boy_class_init(BoyClass *klass);
static void boy_animal_init(AnimalInterface *iface);
static void boy_human_init(HumanInterface *iface);

static void boy_finalize(GObject *object);

static void boy_get_property(GObject *object,guint property_id,GValue *value,GParamSpec *pspec);
static void boy_set_property(GObject *object,guint property_id,const GValue *value,GParamSpec *pspec);

static void boy_sleep(Animal *animal);
static void boy_eat(Animal *animal);
static void boy_run(Animal *animal);

static void boy_say(Human *human);
static void boy_study(Human *human);

G_DEFINE_TYPE_EXTENDED(Boy,boy,G_TYPE_OBJECT,0,G_IMPLEMENT_INTERFACE(TYPE_ANIMAL,boy_animal_init)\
                       G_IMPLEMENT_INTERFACE(TYPE_HUMAN,boy_human_init));

static void boy_init(Boy *self)
{
    self->name=NULL;
}

static void boy_class_init(BoyClass *klass)
{
    GObjectClass *oclass;

    oclass=G_OBJECT_CLASS(klass);
    oclass->finalize=boy_finalize;

    oclass->set_property=boy_set_property;
    oclass->get_property=boy_get_property;

    g_object_class_override_property(oclass,PROP_AGE,"age");
}

static void boy_animal_init(AnimalInterface *iface)
{
    iface->sleep=boy_sleep;
    iface->eat=boy_eat;
    iface->run=boy_run;
}

static void boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}

static void boy_finalize(GObject *object)
{
    Boy *boy=BOY(object);

    g_string_free(boy->name,TRUE);

    G_OBJECT_CLASS(g_type_class_peek_parent(object))->finalize;
}

static void boy_get_property(GObject *object,guint property_id,GValue *value,GParamSpec *pspec)
{
    Boy *boy=BOY(object);

    switch (property_id)
    {
        case PROP_AGE:
            g_value_set_uint(value,boy->age);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);

    }
}

static void boy_set_property(GObject *object,guint property_id,const GValue *value,GParamSpec *pspec)
{
    Boy *boy=BOY(object);

    switch (property_id)
    {
        case PROP_AGE:
            boy->age=g_value_get_uint(value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);

    }
}

static void boy_sleep(Animal *animal)
{
    g_print("I'm sleeping now!\n");
}

static void boy_eat(Animal *animal)
{
    g_print("I hate eating vegetables!\n");
}

static void boy_run(Animal *animal)
{
    g_print("I will come first!\n");
}

static void boy_say(Human *human)
{
    g_print("Hello!\n");
}

static void boy_study(Human *human)
{
    g_print("I like Mathematics!\n");
}

GObject *boy_new_with_name_and_age(gchar *name,guint age)
{
    GObject *object;

    object=g_object_new(TYPE_BOY,"age",age,NULL);

    BOY(object)->name=g_string_new(name);


    return object;
}
/**********我是分割线**********/

int main()
{
    GObject *boy;

    g_type_init();

    boy=boy_new_with_name_and_age("Jim",14);

    animal_eat(ANIMAL(boy));
    
    human_study(HUMAN(boy));

    return 0;
}

    从以上代码可以看出,定义一个接口类型,主要分为以下几步:

    1.定义接口相应的C语言结构:

typedef struct _Human Human;
typedef struct _HumanInterface HumanInterface;

struct _HumanInterface
{
	GTypeInterface base_iface;

	void (*say)(Human *human);
	void (*study)(Human *human);
};

       所有接口类型的C语言结构,第一个成员必须是GTypeInterface,下面跟的函数指针,则是该接口的虚方法。注意,这里的"typedef struct _Human Human"就是所谓的"Dummy typedef",它使得我们可以为虚方法的形参中那个指向实现该接口的类的某一实例结构类型变量的指针(按惯例应为第一个形参,相当于C++中的this指针)提供一个统一的指针类型。

    2.接下来,我们需要为该接口类型提供一个默认初始化函数(与下面的初始化函数相区别),该函数将负责在运行时刻在GType类型系统中添加该接口类型的信号与属性信息。由于任何一个类在实现该接口时,该默认初始化函数都会被调用,所以我们需要用一个变量来确保负责添加信号与属性的函数体只被调用一次:

static void human_default_init(HumanInterface *iface)
{
    static gboolean is_initialized=FALSE;
    
    if (!is_initialized)
    {
        g_object_interface_install_property(iface,g_param_spec_uint("age","Age","The age of the person",
                                            0,150,0,G_PARAM_READABLE|G_PARAM_WRITABLE));

        is_initialized=TRUE;
    }
}

    3.然后,我们需要一个在运行时刻负责在GType类型系统中进行注册的函数human_get_type。当然,我们可以自己“手写”(也许用词不是很准确)这个函数。不过为方便起见我们可以使用宏G_DEFINE_INTERFACE,该宏定义如下:

#define G_DEFINE_INTERFACE(TN, t_n, T_P)    G_DEFINE_INTERFACE_WITH_CODE(TN, t_n, T_P, ;)

      从中可以看出,该宏有三个参数TN,t_n,T_P。TN和t_n都是接口类型的名字,不同的是,TN要求组成该名字的各个单词的首字母大写,其余字母小写,而t_n则要求组成该名字的各个单词的字母都要小写,各个单词之间要下划线"_"分隔开。该宏要求你定义的接口类型的名字为TN##Interface,接口类型的初始化函数为t_n##_default_init("##"是C语言预处理操作符)。T_P则是该接口类型的先决条件(即实现TN##Interface接口的类在实现该接口之前要实现的接口)GType ID。如果你定义的接口的先决条件不止一个,你可以使用G_DEFINE_INTERFACE_WITH_CODE宏以及g_type_interface_add_prerequisite函数。

    4.为方便使用,我们需要提供一些宏以及所谓的helper function:

#define TYPE_HUMAN (human_get_type())
#define HUMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_HUMAN,Human))
#define HUMAN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_HUMAN,HumanInterface))


void human_say(Human *human)
{
    HUMAN_GET_INTERFACE(human)->say(human);
}
void human_study(Human *human)
{
    HUMAN_GET_INTERFACE(human)->study(human);
}

       宏HUMAN会检测指针obj指向的实例结构类型变量所属的类是否实现了TYPE_HUMAN接口,如果没有,会发出警告,并返回NULL,如果有,则会返回一个指向Human类型的新指针(虽然Human类型实际上并不存在)。

    5.接下来,我们需要让一个类实现该接口,也即提供接口中虚方法的实现并提供一个初始化函数对接口结构变量中的函数指针进行赋值:

static void boy_say(Human *human);
static void boy_study(Human *human);

static void boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}

static void boy_say(Human *human)
{
    g_print("Hello!\n");
}

static void boy_study(Human *human)
{
    g_print("I like Mathematics!\n");
}

      当然,我们还需要在TYPE_BOY类注册时说明该类实现了TYPE_HUMAN接口,为此,我们在这里使用G_IMPLEMENT_INTERFACE宏,该宏需要配合宏G_DEFINE_TYPE_EXTENDED使用:

G_DEFINE_TYPE_EXTENDED(Boy,boy,G_TYPE_OBJECT,0,G_IMPLEMENT_INTERFACE(TYPE_ANIMAL,boy_animal_init)\
                       G_IMPLEMENT_INTERFACE(TYPE_HUMAN,boy_human_init));

      宏G_IMPLEMENT_INTERFACE的定义如下:

#define G_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init)       { \
  const GInterfaceInfo g_implement_interface_info = { \
    (GInterfaceInitFunc) iface_init, NULL, NULL \
  }; \
  g_type_add_interface_static (g_define_type_id, TYPE_IFACE, &g_implement_interface_info); \
}

      该宏有两个参数TYPE_IFACE和iface_init。TYPE_IFACE是被实现的接口类型的GType ID,而iface_init则是该类针对该接口的初始化函数。

Feb 14

    天翼家庭套餐赠送的路由器是不能自动拨号的,换句话说,仅仅连上无线网是不够的:你需要自己手动拨号。

    但是,Ubuntu自带的networkmanager是很脑残的:建立无线网连接和建立DSL连接是冲突的,建立DSL连接就会无线网连接就会断开......

    这一问题最后的解决方案如下(wicd+pppoeconf):

    1.连上互联网(如果就是天翼家庭套餐提供的有线网,插上网线后执行pppoeconf拨号),在终端中执行:

sudo apt-get install wicd

    2.wicd安装完成后,在终端中执行:

sudo gedit /etc/NetworkManager/nm-system-settings.conf

       查看managed项后面的值,如果是true就改成false

    3.重启

    4.此时,右上角networkmanager的图标应该已经消失了,同时,会出现一个新的图标,这应该就是wicd的图标,单击该图标,出现wicd的界面。刷新一下,找到天翼家庭套餐的无线网,连上。(当然,你的电脑所在的位置要能搜到天翼家庭套餐的无线网的信号)

    5.执行sudo pppoeconf,配置拨号连接,完成后,执行plog查看连接情况,如果出现如下的字样,说明连接成功(当然,IP地址等数值可能不一样):

Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: Remote message: Authentication success,Welcome!
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: PAP authentication succeeded
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: peer from calling number 00:18:82:67:66:4D authorized
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: replacing old default route to wlan0 [192.168.1.1]
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: local  IP address 180.118.45.81
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: remote IP address 180.118.47.254
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: primary   DNS address 218.2.135.1
Feb 14 12:52:01 nanjingabcdefg-L41II3 pppd[8876]: secondary DNS address 61.147.37.1

       然后,该干什么干什么吧

    几点说明:

    1.用pppoeconf配置完成后,以后连接互联网只要执行sudo pon dsl-provider就行了。

    2.用pppoeconf配置时,最好不要选开机自动建立连接。否则,如果忘了这一点,手动执行sudo pon dsl-provider,就悲剧了。

    3.如果无线网掉线,重新连上无线网以后可能需要重新拨号(执行route -n查看路由,如果没有ppp端口就需要重新拨号了)。重新拨号前请执行sudo poff断开之前的连接(虽然实际上已经断开了,但如果不执行,路由配置就悲剧了)。

Jan 28

    注:之前曾写过一篇类似文章,结果被我误删了

 

    按照GObject手册的说法,Boxed Type机制是用来包装(wrap)C语言结构体的一种机制。这里所谓的包装,其实就是在GType类型系统中注册该类型,并且该类型将成为GBoxed的子类。之后,这一类型就能够使用和GBoxed类型有关的所有函数了,如g_value_get_boxed等等。

    首先,我们看一下GBoxed在GType类型系统中的“注册信息”:

void
g_boxed_type_init (void)
{
  static const GTypeInfo info = {
    0,                          /* class_size */
    NULL,                       /* base_init */
    NULL,                       /* base_destroy */
    NULL,                       /* class_init */
    NULL,                       /* class_destroy */
    NULL,                       /* class_data */
    0,                          /* instance_size */
    0,                          /* n_preallocs */
    NULL,                       /* instance_init */
    NULL,                       /* value_table */
  };
  const GTypeFundamentalInfo finfo = { G_TYPE_FLAG_DERIVABLE, };
  GType type;

  /* G_TYPE_BOXED
   */
  type = g_type_register_fundamental (G_TYPE_BOXED, g_intern_static_string ("GBoxed"), &info, &finfo,
				      G_TYPE_FLAG_ABSTRACT | G_TYPE_FLAG_VALUE_ABSTRACT);
  g_assert (type == G_TYPE_BOXED);
}

    从以上信息中,我们可以看出,GBoxed是一个基本类型,不可实例化(instantiable)、不可类化(classed),可以被继承,但不可以被深继承(即继承了GBoxed的子类不能再被继承)。

    要想在GType类型系统中注册一个Boxed Type,我们需要提供一个copy_func和一个free_func,从名字上也可以看出,前者负责该类型的拷贝,后者负责该类型的释放。

    为更方便地说明问题,我们假设我们需要包装以下的StudentInfo类型:

typedef struct _StudentInfo StudentInfo;

struct _StudentInfo
{
	gint number;
	GString *name;
	gdouble grade;
};

    为了包装该类型,我们自然需要提供一个copy_func和一个free_func:

StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=number;
	new_std->name=g_string_new(name);
	new_std->grade=grade;

	return new_std;
}

StudentInfo *student_info_copy(const StudentInfo *std_info)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=std_info->number;
	new_std->name=g_string_new(std_info->name->str);
	new_std->grade=std_info->grade;

	return new_std;
}

    下面,我们需要一个student_info_type_init函数来承担运行时刻注册该类型的任务。为方便起见,我们也为该函数配套了一个宏:

GType student_info_get_type()
{
    static GType student_info_id=0;

    if (student_info_id==0)
    {
        student_info_id=g_boxed_type_register_static("StudentInfo",(GBoxedCopyFunc)student_info_copy,(GBoxedFreeFunc)student_info_free);
    }

    return student_info_id;
}


#define TYPE_STUDENT_INFO (student_info_get_type())

    从上面的代码中可以看出,Boxed Type的注册工作实质上是由g_boxed_type_register_static函数完成的,该函数原型如下:

GType  g_boxed_type_register_static (const gchar *name,
                                     GBoxedCopyFunc boxed_copy,
                                     GBoxedFreeFunc boxed_free);

    该函数有三个参数,其中name是Boxed Type的名字,GBoxedCopyFunc和GBoxedFreeFunc则是用typedef定义的函数指针类型:

typedef gpointer (*GBoxedCopyFunc) (gpointer boxed);
typedef void (*GBoxedFreeFunc) (gpointer boxed);  

    至此,包装工作就完成了。

    但是上面给出的student_info_get_type()函数有一个问题:它不是线程安全的。而且,每次写这么多也显得很臃肿,为此,我们可以使用一个宏G_DEFINE_BOXED_TYPE:

#define G_DEFINE_BOXED_TYPE(TypeName, type_name, copy_func, free_func) G_DEFINE_BOXED_TYPE_WITH_CODE (TypeName, type_name, copy_func, free_func, {})

    该宏有4个参数:

  • TypeName: 该参数是该Boxed Type的名字,且不用引号包围。
  • type_name: 该参数将成为_get_type()的前缀,即最终自动生成的函数的名字为type_name_get_type()
  • copy_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下三种之一:
    typedef gpointer (*copy_func) (gpointer boxed);
    typedef TypeName *(*copy_func) (const TypeName *boxed);
    typedef TypeName *(*copy_func) (TypeName *boxed);
  • free_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下两种之一:
    typedef void (*free_func) (gpointer boxed);
    typedef void (*free_func) (TypeName *boxed);

    因此,如果想生成student_info_get_type()函数的话,只要按如下所示填写参数:

G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

    如果你由于种种原因而需要自己写*_get_type()函数的话,为使其线程安全,可以将以下代码作为模板使用:

GType
g_strv_get_type (void)
{
  static volatile gsize g_define_type_id__volatile = 0;

  if (g_once_init_enter (&g_define_type_id__volatile))
    {
      GType g_define_type_id =
        g_boxed_type_register_static (g_intern_static_string ("GStrv"),
                                      (GBoxedCopyFunc) g_strdupv,
                                      (GBoxedFreeFunc) g_strfreev);

      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
    }

  return g_define_type_id__volatile;
}

    事实上,很久以前,G_DEFINE_BOXED_TYPE展开以后,也是和上面的代码类似的——之所以要改用现在的写法,主要还是为了增强对类型的检测。

    完整的测试代码如下:

#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>

typedef struct _StudentInfo StudentInfo;

struct _StudentInfo
{
	gint number;
	GString *name;
	gdouble grade;
};

StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=number;
	new_std->name=g_string_new(name);
	new_std->grade=grade;

	return new_std;
}

void student_info_print(const StudentInfo *std_info)
{
	g_print("Number:%3d Name:%5s Grade:%3.1f",std_info->number,std_info->name->str,std_info->grade);
}

StudentInfo *student_info_copy(const StudentInfo *std_info)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=std_info->number;
	new_std->name=g_string_new(std_info->name->str);
	new_std->grade=std_info->grade;

	return new_std;
}

void student_info_free(StudentInfo *std_info)
{
	g_string_free(std_info->name,TRUE);

	g_free(std_info);
}

G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

#define TYPE_STUDENT_INFO (student_info_get_type())

int main()
{
	StudentInfo *stdi;
	StudentInfo *stdi_copy;

	g_type_init();

	stdi=student_info_new(1,"JIm",86.5);
	stdi_copy=g_boxed_copy(TYPE_STUDENT_INFO,stdi);

	student_info_print(stdi_copy);

	g_boxed_free(TYPE_STUDENT_INFO,stdi);
	g_boxed_free(TYPE_STUDENT_INFO,stdi_copy);

	return 0;
}

 

Jan 26

    在GObject中,如果想给自己写的类加上signal,一般需要在*_class_init函数中使用g_signal_new函数,但是这个函数的参数比较复杂:

guint    g_signal_new(const gchar *signal_name,
                      GType itype,
                      GSignalFlags signal_flags,
                      guint class_offset,
                      GSignalAccumulator accumulator,
                      gpointer accu_data,
                      GSignalCMarshaller c_marshaller,
                      GType return_type,
                      guint n_params,
                      ...);

    从该函数的原型中,我们可以看出,该函数的参数个数是可变的。

    下面,我姑妄从前往后依次解释一下各个参数的含义:

  • const gchar *signal_name:该参数是信号的名字,由分隔符以及ASCII码中的字母和数字构成,并且第一个字符必须是字母。分隔符可以是"-"或"_"——事实上,系统会先调用g_strdelimit把"_"转化为"-"再存储signal_name。因此,在调用g_singal_emit_by_name时,detailed_signal参数中的分隔符必须是"-"。
  • GType itype:该参数是signal所依附的类的在GType类型系统中注册时得到的ID,也就是*_get_type()函数的返回值。
  • GSignalFlags signal_flags:该参数是信号的属性标记,有七种,我将在最后解释:
    • G_SIGNAL_RUN_FIRST
    • G_SIGNAL_RUN_LAST
    • G_SIGNAL_RUN_CLEANUP
    • G_SIGNAL_NO_RECURSE
    • G_SIGNAL_DETAILED
    • G_SIGNAL_ACTION
    • G_SIGNAL_NO_HOOKS
  • guint class_offset:该参数是itype对应的类的class结构中的一个函数指针相对于class结构的实例的首地址的偏移量。该函数指针所对应的函数常被称为"per-object handler","default (signal) handler"或"object methond handler",并将在信号发出后被调用(如调用g_signal_emit_by_name)。常配合宏G_STRUCT_OFFSET使用(该宏能够返回结构体变量的成员相对于该结构体的变量的首地址的偏移量)。如果设为0,则表示该类没有"per-object handler"。
  • GSignalAccumulator accumulator:该参数是一个函数指针,其对应的函数将在该信号的每个handler执行完以后执行。GSignalAccumulator类型的定义如下:
    typedef gboolean (*GSignalAccumulator)  (GSignalInvocationHint *ihint,
                                             GValue *return_accu,
                                             const GValue *handler_return,
                                             gpointer data);
    
    从中可以看出该函数指针类型有4个参数:
  • GSignalInvocationHint *ihint:该参数是一个GSignalInvocationHint类型的指针,而GSignalInvocation结构的定义如下:
    typedef struct {
      guint	signal_id;
      GQuark detail;
      GSignalFlags run_type;
    } GSignalInvocationHint;

其中,signal_id是导致GSignalAccumulator所指函数被调用的signal的ID(因为该信号signal调用了某个handler)。detail是该signal的detail部分对应的GQuark(其实就是字符串的散列值)。run_type则反映了handler被调用时signal发射进行到的阶段,其值为 G_SIGNAL_RUN_FIRST,G_SIGNAL_RUN_LAST或G_SIGNAL_CLEANUP。

  • GValue *return_accu:该参数是一个GValue类型的指针,可以被程序员用来返回任何在GType类型系统注册的,带有value_table的类型。
  • const GValue *handler_return:该参数是一个GValue类型的指针,指向一个GValue变量,该变量存储了GSignalAccumulator所指函数被调用前,信号调用的handler的返回值,可以用g_value_get_*系列函数获取其存储的值。
  • gpointer data:该参数即为g_signal_new的参数gpointer accu_data的值

显然,该函数的返回类型为gboolean。如果其返回值为FASLE,则signal发射过程就会被中止(即不再调用后面的hander),否则会继续下去。事实上,"delete-event"等带有event后缀的signal就是利用了这一点——这些信号的某个回调函数如果返回了TRUE,则以后的回调函数就不会被调用。

我们可以看一下"delete-event"的accumulator, _gtk_boolean_handled_accumulator的代码:

gboolean
_gtk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
				  GValue                *return_accu,
				  const GValue          *handler_return,
				  gpointer               dummy)
{
  gboolean continue_emission;
  gboolean signal_handled;
  
  signal_handled = g_value_get_boolean (handler_return);
  g_value_set_boolean (return_accu, signal_handled);
  continue_emission = !signal_handled;
  
  return continue_emission;
}

注意,如果该signal有accumulator,该signal的回调函数类型(由c_marshaller反映)必须有返回值,否则该signal不能有accumulator,也即在调用g_signal_new时以NULL作为该形参的实参。

  • gpointer accu_data:该参数将作为用户自定义参数传入accumulator所指向的函数中。
  • GSignalCMarshaller c_marshaller:该参数是一个GSignalCMarshall类型的函数指针,其值反映了回调函数的返回值类型和额外参数类型(所谓“额外参数”,即指除回调函数中instance和user_data以外的参数)。            

例如,g_closure_marshal_VOID_VOID说明该signal的回调函数为以下的callback类型:

typedef void (*callback)  (gpointer instance, gpointer user_data);

而g_closure_marshal_VOID_POINTER则说明该signal的回调函数为以下的callback类型:

typedef void (*callback)  (gpointer instance, gpointer arg1, gpointer user_data);

如果默认提供的GClosureMarshall中没有你需要的,你可以用glib-genmarshall生成它,具体可参见devhelp中有关glib-genmarshall的说明。

  • GType return_type:该参数的值应为回调函数的返回值在GType类型系统中的ID。
  • guint n_params:该参数的值应为回调函数的额外参数的个数。
  • ...:这一系列的参数的值应为回调函数的额外参数在GType类型系统中的ID,且这一系列参数中第一个参数的值为回调函数的第一个额外参数在GType类型系统中的ID,依次类推。

 

最后,我们来解释一下GSignalFlags中各个特征标志的含义:

  • G_SIGNAL_RUN_FIRST:调用回调函数时,"per-object handler"对应的回调函数将第一个调用
  • G_SIGNAL_RUN_LAST:调用回调函数时,"per-object handler"对应的回调函数将在用户用g_signal_connect连接的回调函数之后调用,并在用户用g_signal_connect_after连接的回调函数之前调用
  • G_SIGNAL_RUN_CLEANUP:调用回调函数时,"per-object handler"对应的回调函数将最后一个调用
  • G_SIGNAL_NO_RECURSE:信号发射时,如果信号的上次发射还没有结束,那么本次信号发射将不再进行,而只是使上次的信号发射重新开始。
  • G_SIGNAL_DETAILED:信号名字可以使用"signal_name::detailed"的形式。
  • G_SIGNAL_ACTION:程序员可以在代码中自由地调用g_signal_emit族的函数来发射信号,而不需要把g_signal_emit族的函数放在一段代码中再来调用。
  • G_SIGNAL_NO_HOOKS:信号发射过程中不支持钩子函数。

 

例子请看cloverprice的文章:《GObject 08: A class with a signal》

传送门:http://cloverprince.javaeye.com/blog/500964

 

注:信号的发射以我的理解,就是按照要求调用回调函数的过程……

Jan 21

    一般情况下,在C语言中,函数指针定义时就会说明其指向的函数的参数情况以及返回值类型,比如以下定义:

void (*func_p)(int a);

    以上代码就声明了一个函数指针func_p,其指向的函数的返回值类型为void类型,即没有返回值,并且该函数有且只有一个int类型的参数。

     那有没有方法使得函数指针能够指向参数类型不同的函数呢?事实上,在gcc中,我们可以使用透明联合类型。这里所谓的“透明”,指的是对用户而言,该联合类型是透明的。自然,用户也就无法发觉。

     透明联合类型的定义的一个范例如下所示:

typedef union u_t
{
    int *a;
    float b;
    struct
    {
        short c;
        char d;
    };
} u_t __attribute__((__transparent_union__));

    从中可以看出,透明联合类型定义时,同普通的联合类型是很相似的,只是要在末尾加上attribute属性transparent_union。事实上__transparent_union__的确也可以写为transparent_union,但为了避免与自定义的宏重复,最好在两边加上双下划线。

   另外,对透明联合类型的成员有以下两点要求:

  1. 浮点类型(float, double, float _Complex, 以及double _Complex)以及向量类型(vector)可以作为透 明联合类型的成员,但是不能作为第一个成员。
  2. 透明联合类型中的任意一个成员的所占的内存空间的大小必须小于等于该透明联合类型中的第一个成员的所占的内存空间的大小。

   利用该透明联合类型,我们可以定义如下的函数指针:

void (*func_p)(u_t u);

   该函数指针可以正确无误的指向以下两个函数(所谓正确无误是指不会提示assignment from incompatible pointer type):

void func_a(int *a)
{
	/*Some code here*/
}

void func_b(float b)
{
	/*Some code here*/
}

   由于u_t中第三个参数为匿名结构体,所以无法写出一个参数为该结构体的函数,函数指针自然也无法指向它。

   我们需要注意,下面一段代码也是可以顺利编译通过的:

func_p=func_a;

func_p(4.5F);

   而下面一个语句是无法编译通过的:

func_a(4.5F);

   从中也可以看出,透明联合类型削弱了C语言的类型检测机制。或者,换言之,它起到了类似强制类型转换的效果。GObject在定义_G_DEFINE_BOXED_TYPE_BEGIN宏时,便利用了这一点:

#define _G_DEFINE_BOXED_TYPE_BEGIN(TypeName, type_name, copy_func, free_func) \
GType \
type_name##_get_type (void) \
{ \
  static volatile gsize g_define_type_id__volatile = 0; \
  if (g_once_init_enter (&g_define_type_id__volatile))  \
    { \
      GType (* _g_register_boxed) \
        (const gchar *, \
         union \
           { \
             TypeName * (*do_copy_type) (TypeName *); \
             TypeName * (*do_const_copy_type) (const TypeName *); \
             GBoxedCopyFunc do_copy_boxed; \
           } __attribute__((__transparent_union__)), \
         union \
           { \
             void (* do_free_type) (TypeName *); \
             GBoxedFreeFunc do_free_boxed; \
           } __attribute__((__transparent_union__)) \
        ) = g_boxed_type_register_static; \
      GType g_define_type_id = \
        _g_register_boxed (g_intern_static_string (#TypeName), copy_func, free_func); \
      { /* custom code follows */

   通过_g_register_boxed这一函数指针,copy_func和free_func的参数类型得以放宽,增加了用户的自由度。

   事实上,透明联合类型也可以用在函数中,作为函数的参数或返回值的类型来使用。

   例如以下一个函数:

u_t func_u(u_t u);
{
	/*Some code here*/	
}

    该函数调用时,其实参的类型可以是int类型,也可以是float类型,同时,其返回值类型也是不确定的。换言之,通过透明联合类型,我们使得一个函数可以接受多种类型的参数,返回多种类型的参数。

    考虑到在底层,类型实质上是不存在的,因此所谓的透明联合类型,也就是在一定程度上打破了类型对我们的束缚,使数据以一种更底层的角度呈现在我们面前。不过这样也弱化了C语言对类型的检测,由此也可能带来一些很严重的错误。