一个实现 GAsyncInitable 接口类的例子

GAsyncInitable 是一个支持异步的、可失败的对象初始化接口类,开发时需要支持异步创建对象并存在失败的可能(多数用于包含 I/O 操作的对象)即可实现此接口类。

完整的示例代码

HevIObj 类是基于 GObject 类的,类中对象实例的创建有两个方法:

void hev_iobj_new_async(GCancellable *cancellable,
                        GAsyncReadyCallback callback, gpointer user_data);
GObject * hev_iobj_new_finish(GAsyncInitable *initable,
                        GAsyncResult *res, GError **error);

对象实例化时先通过调用 hev_iobj_new_async 方法并传入回调函数,当对象初始化好后传入的回调函数将被调用,在回调函数中再调用对象的 hev_iobj_new_finish 方法即可得到类的新对象实例。

接口类的实现

static void hev_iobj_async_initable_iface_init(GAsyncInitableIface *iface);
static void hev_iobj_async_initable_init_async(GAsyncInitable *initable,
                        gint io_priority, GCancellable *cancellable,
                        GAsyncReadyCallback callback, gpointer user_data);
static gboolean hev_iobj_async_initable_init_finish(GAsyncInitable *initable,
                        GAsyncResult *result, GError **error);
 
G_DEFINE_TYPE_WITH_CODE(HevIObj, hev_iobj, G_TYPE_OBJECT,
                G_IMPLEMENT_INTERFACE(G_TYPE_ASYNC_INITABLE, hev_iobj_async_initable_iface_init));

与实现其它接口类没有什么区别,通过宏 G_IMPLEMENT_INTERFACE 格式的写入接口类型和接口类初始化函数,hev_iobj_async_initable_iface_init 接口类初始化函数中进行接口方法的重载:

static void hev_iobj_async_initable_iface_init(GAsyncInitableIface *iface)
{
        g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
        iface->init_async = hev_iobj_async_initable_init_async;
        iface->init_finish = hev_iobj_async_initable_init_finish;
}

重载的异步实例初始化函数 hev_iobj_async_initable_init_async 将在 hev_iobj_new_async 方法后被调用,它先创建一个 GSimpleAsyncResult 实例,用于返回异步操作的结果。如果异步操作支持取消,g_simple_async_result_set_check_cancellable 应该在 GSimpleAsyncResult 实例创建后立即调用,GIO 的异步操作实例是通过线程的方式实现的,g_simple_async_result_run_in_thread 方法则是创建一个新的线程并执行异步操作,传入的线程的处理函数完成后 hev_iobj_async_initable_init_finish 即被调用。详见示例代码:

static void hev_iobj_async_initable_init_async(GAsyncInitable *initable,
                        gint io_priority, GCancellable *cancellable,
                        GAsyncReadyCallback callback, gpointer user_data)
{
        HevIObj *self = HEV_IOBJ(initable);
        HevIObjPrivate *priv = HEV_IOBJ_GET_PRIVATE(self);
        GSimpleAsyncResult *simple = NULL;
 
        g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
        simple = g_simple_async_result_new(G_OBJECT(initable),
                                callback, user_data, hev_iobj_async_initable_init_async);
        g_simple_async_result_set_check_cancellable(simple, cancellable);
        g_simple_async_result_run_in_thread(simple, async_result_run_in_thread_handler,
                                io_priority, cancellable);
        g_object_unref(simple);
}

当异步操作完成后, hev_iobj_async_initable_init_finish 将被调用,在里面可通过 GError 向外抛出错误(如果出错 hev_iobj_new_finish 方法传入的 error 将被填充),并通过此函数的返回值控制对象的初始化是否完成。

static gboolean hev_iobj_async_initable_init_finish(GAsyncInitable *initable,
                        GAsyncResult *result, GError **error)
{
        g_debug("%s:%d[%s]", __FILE__, __LINE__, __FUNCTION__);
 
        g_return_val_if_fail(g_simple_async_result_is_valid(result,
                                        G_OBJECT(initable), hev_iobj_async_initable_init_async),
                                FALSE);
        if(g_simple_async_result_propagate_error(G_SIMPLE_ASYNC_RESULT(result),
                                        error))
          return FALSE;
 
        return TRUE;
}

Debug 信息显示出的对象创建和销毁的整个流程

** (process:21875): DEBUG: hev-iobj.c:137[hev_iobj_new_async]
** (process:21875): DEBUG: hev-iobj.c:70[hev_iobj_class_init]
** (process:21875): DEBUG: hev-iobj.c:82[hev_iobj_async_initable_iface_init]
** (process:21875): DEBUG: hev-iobj.c:54[hev_iobj_constructor]
** (process:21875): DEBUG: hev-iobj.c:92[hev_iobj_init]
** (process:21875): DEBUG: hev-iobj.c:61[hev_iobj_constructed]
** (process:21875): DEBUG: hev-iobj.c:109[hev_iobj_async_initable_init_async]
** (process:21875): DEBUG: hev-iobj.c:98[async_result_run_in_thread_handler]
** (process:21875): DEBUG: hev-iobj.c:146[hev_iobj_new_finish]
** (process:21875): DEBUG: hev-iobj.c:122[hev_iobj_async_initable_init_finish]
** (process:21875): DEBUG: hev-iobj.c:37[hev_iobj_dispose]
** (process:21875): DEBUG: hev-iobj.c:47[hev_iobj_finalize]

Over!

配置 Linux 系统在 AC 供电切换时自动调整 CPU 节能模式

UDev 真是个万能的东西啊(好像有点夸张)!这次我又要利用它的 rules 来实现当 AC 供电切换时自动调整 CPU 的节能模式。我的目标是当 AC 供电时各个 CPU 都是工作在 performance 模式;当电池供电时切换成 ondemand 模式。

内核模块依赖
1. acpi-cpufreq

CPU 模式切换程序 cpumode
文件路径:/usr/local/sbin/cpumode

#!/bin/bash
# Heiher <admin@heiher.info>
 
set -e
 
MODE=$1
 
cpu_set_mode()
{
	echo "$2" > "/sys/devices/system/cpu/$1/cpufreq/scaling_governor"
}
 
cpu_foreach()
{
	for i in /sys/devices/system/cpu/cpu?; do
		CPU=`basename $i`
		$1 ${CPU} $2
	done
}
 
case "${MODE}" in
	performance | ondemand)
		cpu_foreach cpu_set_mode ${MODE}
		;;
	*)
		echo "Usage: $0 [ performance | ondemand ]"
		;;
esac
 
exit 0

UDev 规则文件
文件路径:/etc/udev/rules.d/80-cpu-mode.rules

# Heiher <admin@heiher.info>
#
 
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="1", RUN+="/usr/local/sbin/cpumode performance"
SUBSYSTEM=="power_supply", ENV{POWER_SUPPLY_NAME}=="AC", ENV{POWER_SUPPLY_ONLINE}=="0", RUN+="/usr/local/sbin/cpumode ondemand"

Over!

使用 GtkBuilder 构造自定义构件

之前博客里有一个非常类似的记录 使用 Builder 构造自定义构件,是在学习 Vala 语言时记录的。说的是如何使用 GtkBuilder 构造出自定义构件,仅仅只给出了一个示例代码。

最近在写 GTK+ 程序时被代码写 UI 的方式搞的很累,尽管那个 UI 并不复杂。在程序中有使用自定义的构件(是 GtkWidget 的子类),这里再重新整理一下关于 GtkBuilder 构造自定义构件的日志,并给出一个 C 语言版本的示例。

GTK+ 从 2.12 开始实现了 GtkBuildable 接口类与 GtkBuilder 类用于处理从 XML 格式的 UI 描述数据中构造出对象的实例,提高 GTK+ UI 程序的开发效率。只要实现了 GtkBuildable 接口类的 Widget 类,都可以在 XML 格式的 UI 描述数据中引用,并使用 GtkBuilder 进行实例化。

目前 GTK+ 库中基本上所有的 Widget 类都实现了 GtkBuildable 接口类,也就意味着可以使用 GtkBuilder 进行实例化。然而有的时间情况并不总是那么好,我们的项目需要自定义新的 Widget 类型完成特定的功能,幸运的是这些自定义的 Widget 类型只要实现了 GtkBuildable 接口类也是一样可以被 GtkBuilder 实例化的。

示例代码

示例中 src/hev-window.h 和 src/hev-window.c 是基于 GtkWindow 简单派生出来的 HevWindow 类。ui/main.xml 使用 HevWindow 类构造了一个 Window,在 src/hev-main.c 中创建了一个 GtkBuilder 实例 builder 来装载 ui/main.xml 并通过 name (对应描述文件中的 id)来获取构造出来的 HevWindow 类型的实例,再调用 gtk_widget_show 方法显示出来。

PS:编译参数中需要使用 gmodule-export-2.0,这样才能导出自定义类型与信号处理函数,需要描述文件中指定的信号回调将不能正常调用。具体见示例中的 Makefile

Over!

Linux 系统中使用中兴(ZTE) R518 手机的内置 Modem 上网

中兴(ZTE) R518 是一部 CDMA 2000 1X/EVDO 网络的手机,它使用 Qualcomm 的芯片,支持 PC Modem 功能。但由于 USB ID 没有被内核 serial 类型的驱动收录导致设备不能被驱动,需要手动指定 ID 并装载 usbserial 驱动。

装载驱动

sudo modprobe usbserial vendor=0x19d2 product=0x1358

此时连接上手机系统中将会创建出三个串口设备,分别是 /dev/ttyUSB0 /dev/ttyUSB1 /dev/ttyUSB2 /dev/ttyUSB3,此时就可以使用 ModemManager + NetworkManager 拨号上网啦。

NetworkManager 连接配置

号码:#777
用户名:ctnet@mycdma.cn
密码:vnet.mobi
认证方式:PAP

Over!

基于 GDBus 的 D-Bus 服务例程

从 GLib 2.30 开始,GIO 中高级 D-Bus 支持增加了 GDBusObjectManagerServer, GDBusObjectSkeleton, GDBusInterfaceSkeleton 等类,基于这些类实现 D-Bus 服务可以把 D-Bus 概念中的对象、接口(方法、属性和信号)与 GObject 的中的方法、属性和信号映射起来,使得开发过程中对象间接关系更直观。

GDBusObjectManagerServer 是管理 D-Bus Object 的服务器的类,它可以在给定的 GDBusConnection 上导出 GDBusObjectSkeleton 类型的对象。

GDBusObjectSkeleton 是 D-Bus Object 的骨架类,在开发过程中需要实现的 D-Bus Object 可以从此类派生出来,它可以增加很多的从此类 GDBusInterfaceSkeleton 派生出来的接口类。

GDBusInterfaceSkeleton 是用于导出 GDBusObjectSkeleton 类及子类的方法、属性和信号的接口类,每个实例只能增加到一个 GDBusObjectSkeleton 实例中,在我的实例中又抽象出了一个 HevDBusInterfaceSkeleton 类型,用于在合适的时机将 GDBusInterfaceSkeleton 增加到 GDBusObjectSkeleton 中,并连接信号。

示例代码

Over!

MediaWiki 用户覆盖上传文件权限控制

以下三条权限配置可以限制:

  • 普通用户不可覆盖隶属于其它用户的文件。
  • 普通用户可以覆盖隶属于自己用户的文件。
  • 管理用户可以覆盖隶属于所有用户的文件。
$wgGroupPermissions['user']['reupload'] = false;
$wgGroupPermissions['user']['reupload-own'] = true;
$wgGroupPermissions['sysop']['reupload'] = true;

Over!

恶心的GObject[Part I][v0.1]

虽然GObject最初是为了简化C语言的OO开发而设立的,但毕竟C不是天生的OO语言,而其中有太多抽象的东西,反而让其难以入门…..

虽然以前花了很长时间去研究这东西,但限于当时的水平,一些东西并没有弄透彻,甚至有不少错误….

因为前段自己尝试用C语言来模拟OO,积累了不少经验,最近偶然又回顾了下GObject,一些似懂非懂的概念明朗了许多…..

Part I. GObject中最恶心的概念
下面罗列几个概念,都是难以弄明白的….

GCClosure,GClosure,GClosureMarshal,

GClosureNotifyData,GClosureNotify,SignalAccumulator,GSignalAccumulator

了吧,估计不少学了一段Gtk的人对上面的概念还是一知半解…

在具体展开前,我们需要知道GObject是怎么描述类,以及如何进行类型的判定和转换的….

我们知道C语言进行OO编程的时候要大量用到结构体函数指针,GObject也不例外,

而GObject中每一个类也是一到多个结构体[多个结构体是干嘛的后面会解释],GObject本身用多个int型变量来表示各个类型,类型的检测就是一个int型变量的检查,而类型的转换也是根据这个int型的变量[需要判断是否可以转换],进行结构体或指针的强制转换.

GCClosure,GClosure就是两个结构体!

前面的GCClosure多了个C,自然是C语言用的,不难想到GCClosure是GClosure一个封装,提供针对C语言的一种绑定,自然,GObject可以绑定到多种语言,比如Python,Ruby等等,目前绑定的比较好的是C[原生]/CPP/VALA/PYTHON

仔细看下

struct _GCClosure
{
  GClosure  closure;
  gpointer  callback;
};

注意,_GCClosure会用typedef在定义成GCClosure,这一点后面都类似,以后不再特殊说明,

通过GCClosure定义,不难发现其封装了一个GClosure的同时,还有一个callback,猜到了吧!GCClosure的主要功能就是回调!

估计不够淡定的朋友会觉得这也太扯淡了,回调的本质就是一个函数指针,包装这么多层干嘛?GObject这样设计自然有它的道理,

我们知道不同语言有不同的类型,比如C语言里就没有原生的String,

而即便是C语言我们仅在C语言,我们在定义回调函数时也可能要用到不同的形式,比如有的返回int,有的返回void,我们怎么区别这些呢?

不急,待我一步一步分析,
GOBject中GClosureMarshal是一个函数指针,但是要注意它是用来定义回调函数类型的而不是直接调用的!

typedef void  (*GClosureMarshal)    (GClosure   *closure,
                     GValue         *return_value,
                     guint           n_param_values,
                     const GValue   *param_values,
                     gpointer        invocation_hint,
                     gpointer    marshal_data);

看到它的参数没有,指定了回调返回值类型,回调函数参数的个数等等

而每一个GClosure都要有一个绑定的GClosureMarshal,

具体来看看

struct _GClosure
{
  /*< private >*/
  volatile          guint    ref_count : 15;
  volatile          guint    meta_marshal : 1;
  volatile          guint    n_guards : 1;
  volatile          guint    n_fnotifiers : 2;  /* finalization notifiers */
  volatile          guint    n_inotifiers : 8;  /* invalidation notifiers */
  volatile          guint    in_inotify : 1;
  volatile          guint    floating : 1;
  /*< protected >*/
  volatile          guint    derivative_flag : 1;
  /*< public >*/
  volatile          guint    in_marshal : 1;
  volatile          guint    is_invalid : 1;
 
  /*< private >*/ void   (*marshal)  (GClosure       *closure,
                        GValue /*out*/ *return_value,
                        guint           n_param_values,
                        const GValue   *param_values,
                        gpointer        invocation_hint,
                        gpointer        marshal_data);
  /*< protected >*/   gpointer data;
 
  /*< private >*/ GClosureNotifyData *notifiers;
 
};

注意,官方网上的Manual可能描述上过老,容易误导初学者,上面的源自GOjbect源码

罗列上面代码并不是大家一个一个分析的,只是要大家知道前面我说的一个Closure绑定一个Marshaller…..

我前面说过GClosureMarshal是用来定义回调函数类型的,不是用来调用的,GObject中真正的回调是marshal_data[够抽象的,这个是一个void *指针] ,关于这个我不多说什么[因为自己也没时间研究],因为一般不常用,主要用于其它语言间的绑定.

对于C语言,GObject本身已经提供了很多现成的C语言专用的Marshaller,下面给出一个最简单的C专用的Marshaller

/* VOID:VOID (./gmarshal.list:26) */
void
g_cclosure_marshal_VOID__VOID (GClosure     *closure,
                               GValue       *return_value G_GNUC_UNUSED,
                               guint         n_param_values,
                               const GValue *param_values,
                               gpointer      invocation_hint G_GNUC_UNUSED,
                               gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__VOID) (gpointer     data1,
                                           gpointer     data2);
  register GMarshalFunc_VOID__VOID callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;
 
  g_return_if_fail (n_param_values == 1);
 
  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }
  callback = (GMarshalFunc_VOID__VOID) (marshal_data ? marshal_data : cc->callback);
 
  callback (data1,
            data2);
}

看到了吧那个callback可以指向marshal_data,而marshal_data也是一个函数指针,一般来说它应指向GCClosure中的callback,只是真正调用时是从GClosure中来调用而已.

而原始的GClosureMarshal本质上也是这样的……

注意VOID_VOID表示回调返回VOID,额外的参数为VOID[就是没有额外的参数]

好了,分析的差不多了,有了上面的基础我们就不难理解signal的链接与回调

对于C语言,

一个SIGNAL在CONNECT时,实际创建了一个C语言的Closure,并绑定了一个C语言的Marshaller,而C语言Marshaller的类型,要在SIGNAL 创建时来定义[注意常见的Gtk中g_signal_connect的常见SIGNAL都是已经定义好回调的类型的,所以你在定义已知信号的回调是要按类型定义],回调函数是在GCClosure中定义的,但本质上是通过GClosure来调用….

简单表示下

GCClosure{
  GClosure {
      ...
     其它很多和类型相关的东西;
      ...
      GClosureMarshal marshal; //这是个函数指针,
      //这个的参数中有一个要指向下面定义的callback
     ...
  }
 void *callback;
}

而我们在ui使用回调是将SIGNAL发送[EMIT]出去,若在signal队列中有绑定的HANDLER[就是我们的callback],就由其处理…

signal的发送非常复杂,这里不再赘述[如果要研究这个,可以从SignalNode结构入手]…..

我们仅需要知道[对于C语言]一旦找到了相应的SIGNAL的ID,就会找到指定GClosure[不是GCClosure],然后通过其找到GClosure并调用其中的marshall函数[传入的参数有何callback相关的],进而调用回调函数.

如果是C语言,可以调用类似g_cclosure_marshal_VOID__VOID的一个定义好的marshal,其内部会调用对应的指向callback的函数[具体参考上面那个C语言VOID_VOID型marshal,调用了传入marshal_data函数指针,也就是我们绑定到GCClosure的callback指针]

好了最扯淡的部分结束了,还有四个[GClosureNotifyData , GClosureNotify , SignalAccumulator , GSignalAccumulator]没说,不过这四个比较简单,理解起来也容易些,下面为了节约时间,一并说了.

GClosureNotifyData 是一个结构体,它包含了一个函数指针GClosureNotify

SignalAccumulator 也是一个结构体,他也包含了一个函数指针GSignalAccumulator

具体代码

struct _GClosureNotifyData
{
  gpointer       data;
  GClosureNotify notify;
};
 
typedef void  (*GClosureNotify)     (gpointer    data,
                     GClosure   *closure);
 
typedef struct
{
  GSignalAccumulator func;
  gpointer           data;
} SignalAccumulator;
 
typedef gboolean (*GSignalAccumulator)  (GSignalInvocationHint *ihint,
                     GValue            *return_accu,
                     const GValue          *handler_return,
                     gpointer               data);

上面的data即回调函数传入的参数

如果我们回调的参数中有定义在堆上的,并且调用完要释放,那么应该通过绑定一个GClosureNotify型的指针,而GSignalAccumulator指针则是用来控制某个信号响应后是否继续向下传递用的.

注意: GClosure中有指向GClosureNotifyData的指针,而SignalNode结构体中有个指向SignalAccumulator结构体的指针.

好了,本篇就到此为止,基本分析了GObject中最难理解的概念,因为写的仓促和个人水平有限希望发现问题的朋友能够给予指正.

Over!

From: http://pingf.is-programmer.com/posts/21355.html