dbus實例講解


dbus是什么東西?

DBus的出現,使得Linux進程間通信更加便捷,不僅可以和用戶空間應用程序進行通信,而且還可以和內核的程序進行通信,DBus使得Linux變得更加智能,更加具有交互性。

DBus分為兩種類型:system bus(系統總線),用於系統(Linux)和用戶程序之間進行通信和消息的傳遞;session bus(回話總線),用於桌面(GNOME, KDE等)用戶程序之間進行通信。

網上有一篇叫“D-Bus Tutorial”的文章,流傳較廣。不少介紹dbus的資料,都引用了其中的段落。 其實相對於這篇文章,我建議大家直接讀“D-Bus Specification”,篇幅不算長, 文字也不算枯燥。

D-Bus是針對桌面環境優化的IPC(interprocess communication )機制,用於進程間的通信或進程與內核的通信。最基本的D-Bus協議是一對一的通信協議。 但在很多情況下,通信的一方是消息總線。消息總線是一個特殊的應用,它同時與多個應用通信,並在應用之間傳遞消息。下面我們會在實例中觀察消息總線的作用。 消息總線的角色有點類似與X系統中的窗口管理器,窗口管理器既是X客戶,又負責管理窗口。

支持dbus的系統都有兩個標准的消息總線:系統總線和會話總線。系統總線用於系統與應用的通信。會話總線用於應用之間的通信。 網上有一個叫d-feet的python程序,我們可以用它來觀察系統中的dbus世界。
linux用戶可以使用sudo apt install d-feet來安裝d-feet,然后命令行d-feet就可以啟用d-feet了。

D-Bus是一個程序。它提供了API。但我們一般不會直接使用dbus的接口。dbus-glib是GTK版本的dbus接口封裝。 本文假設讀者安裝了dbus-glib,我安裝的是dbus-glib-0.76。后面還會看到,通過python操縱dbus是多么簡單。

D-Bus的基本概念

運行環境准備

sudo apt install dbus
sudo apt install d-feet
sudo apt install libgtk2.0-dev
sudo apt install libdbus-glib-1-dev
sudo apt install automake

從例子開始

我寫了一個最簡單的dbus服務器,它通過dbus提供了一個加法的接口。 大家可以下載這個例子。這是一個autotool工程,大家解包后,執行:

./autogen.sh
./configure
make

然后在src目錄運行:

./example-service

這時再運行d-feet,連接session bus,在“Bus Name”窗口會看到一個叫“org.fmddlmyy.Test”連接名。

選擇“org.fmddlmyy.Test”,在右側窗口點擊展開“Object Paths”->“/TestObj”->“Interfaces”->“org.fmddlmyy.Test.Basic”->“Methods”,可以看到一個Add方法。雙擊Add方法,彈出下面這個對話框,在Parameters窗口輸入“1,2”,點擊“Execute”按鈕,然后在“Output”窗口我們看到了輸出結果。我們剛剛創建了一個dbus服務並調用了它。

名詞

我們來解釋一下d-feet中出現的名詞。

Bus Name

可以把Bus Name理解為連接的名稱,一個Bus Name總是代表一個應用和消息總線的連接。 有兩種作用不同的Bus Name,一個叫公共名(well-known names),還有一個叫唯一名(Unique Connection Name)。

可能有多個備選連接的公共名
公共名提供眾所周知的服務。其他應用通過這個名稱來使用名稱對應的服務。可能有多個連接要求提供同個公共名的服務,即多個應用連接到消息總線,要求提供同個公共名的服務。 消息總線會把這些連接排在鏈表中,並選擇一個連接提供公共名代表的服務。可以說這個提供服務的連接擁有了這個公共名。 如果這個連接退出了,消息總線會從鏈表中選擇下一個連接提供服務。公共名是由一些圓點分隔的多個小寫標志符組成的,例如“org.fmddlmyy.Test”、“org.bluez”。

每個連接都有一個唯一名
當應用連接到消息總線時,消息總線會給每個應用分配一個唯一名。唯一名以“:”開頭,“:”后面通常是圓點分隔的兩個數字,例如“:1.0”。 每個連接都有一個唯一名。在一個消息總線的生命期內,不會有兩個連接有相同的唯一名。 擁有公眾名的連接同樣有唯一名,例如在前面的圖中,“org.fmddlmyy.Test”的唯一名是“:1.17”。

有的連接只有唯一名,沒有公眾名。可以把這些名稱稱為私有連接,因為它們沒有提供可以通過公共名訪問的服務。 d-feet界面上有個“Hide Private”按鈕,可以用來隱藏私有連接。

Object Paths

Bus Name確定了一個應用到消息總線的連接。在一個應用中可以有多個提供服務的對象。這些對象按照樹狀結構組織起來。 每個對象都有一個唯一的路徑(Object Paths)。或者說,在一個應用中,一個對象路徑標志着一個唯一的對象。

“org.fmddlmyy.Test”只有一個叫作“/TestObj”的對象。圖1中的“org.bluez”有多個對象路徑。

Interfaces

通過對象路徑,我們找到應用中的一個對象。每個對象可以實現多個接口。例如:“org.fmddlmyy.Test”的“/TestObj”實現了以下接口:

  • org.fmddlmyy.Test.Basic
  • org.freedesktop.DBus.Introspectable
  • org.freedesktop.DBus.Properties

后面講代碼時會看到,我們在代碼中其實只實現了“org.fmddlmyy.Test.Basic”這個接口。 接口“org.freedesktop.DBus.Introspectable”和“org.freedesktop.DBus.Properties”是消息總線提供的標准接口。

Methods和Signals

接口包括方法和信號。例如“org.fmddlmyy.Test”的“/TestObj”對象的“org.fmddlmyy.Test.Basic”接口有一個Add方法。 后面的例子中我們會介紹信號。

標准接口“org.freedesktop.DBus.Introspectable”的Introspect方法是個很有用的方法。 類似於Java的反射接口,調用Introspect方法可以返回接口的xml描述。我們雙擊 “org.fmddlmyy.Test”->“/TestObj”->“org.fmddlmyy.Test.Basic”->“org.freedesktop.DBus.Introspectable”的Introspect方法。 這個方法沒有輸入參數,我們直接點擊“Execute”按鈕,你在“Output”窗口看到了什么?

后面我們會用另一種方式調用Introspect方法。

消息和消息總線

應用程序A和消息總線連接,這個連接獲取了一個眾所周知的公共名(記作連接A)。應用程序A中有對象A1提供了接口I1,接口I1有方法M1。 應用程序B和消息總線連接,要求調用連接A上對象A1的接口I1的方法M1。

在上一講的加法例子中,上面這段話可以實例化為:應用程序example-service和會話總線連接。這個連接獲取了一個眾所周知的公共名“org.fmddlmyy.Test”。 應用程序example-servic中有對象“/TestObj”提供了接口“org.fmddlmyy.Test.Basic”,接口“org.fmddlmyy.Test.Basic”有方法“Add”。 應用程序d-feet和會話總線連接,要求調用連接“org.fmddlmyy.Test”上對象“/TestObj”的接口“org.fmddlmyy.Test.Basic”的方法“Add”。

應用程序B調用應用程序A的方法,其實就是應用程序B向應用程序A發送了一個類型為“method_call”的消息。 應用程序A通過一個類型為“method_retutn”的消息將返回值發給應用程序B。我們簡單介紹一下D-Bus總線上的消息。

D-Bus的消息

上一講說過最基本的D-Bus協議是一對一的通信協議。與直接使用socket不同,D-Bus是面向消息的協議。 D-Bus的所有功能都是通過在連接上流動的消息完成的。

消息類型

D-Bus有四種類型的消息:

  • method_call 方法調用
  • method_return 方法返回
  • error 錯誤
  • signal 信號

前面介紹的遠程方法調用就用到了method_call和method_return消息。顧名思義,在發生錯誤時會產生error消息。 如果把method_call看作打電話,那么signal消息就是來電了。后面還會詳細討論。

dbus-send和dbus-monitor

dbus提供了兩個小工具:dbus-send和dbus-monitor。我們可以用dbus-send發送消息。用dbus-monitor監視總線上流動的消息。 讓我們通過dbus-send發送消息來調用前面的Add方法,這時dbus-send充當了應用程序B。用dbus-monitor觀察調用過程中的消息。

啟動example-service:

./example-service

在另一個控制台啟動dbus-monitor:

dbus-monitor

dbus-monitor默認監視會話總線。執行:

dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.fmddlmyy.Test.Basic.Add int32:100 int32:999

輸出為:

method return sender=:1.21 -> dest=:1.22 reply_serial=2
   int32 1099

dbus-monitor的相關輸出包括:

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ""
   string ":1.22"
method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello
method call sender=:1.22 -> dest=org.fmddlmyy.Test path=/TestObj; interface=org.fmddlmyy.Test.Basic; member=Add
   int32 100
   int32 999
method return sender=:1.21 -> dest=:1.22 reply_serial=2
   int32 1099
signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ":1.22"
   string ""

:1.22就是dbus-send在本次調用中與會話總線所建立連接的唯一名。:1.21是連接“org.fmddlmyy.Test”的唯一名。 在以上輸出中我們可以看到:1.22向“org.fmddlmyy.Test”發送method_call消息,調用Add方法。 :1.21通過method_return消息將調用結果發回:1.22。其它輸出信息會在以后說明。

dbus-send的詳細用法可以參閱手冊。調用遠程方法的一般形式是:

dbus-send [--system | --session] --type=method_call --print-reply --dest=連接名 對象路徑 接口名.方法名 參數類型:參數值 參數類型:參數值

dbus-send支持的參數類型包括:string, int32, uint32, double, byte, boolean。

消息總線的方法和信號

概述

消息總線是一個特殊的應用,它可以在與它連接的應用之間傳遞消息。 可以把消息總線看作一台路由器。正是通過消息總線,D-Bus才在一對一的通信協議基礎上實現了多對一和一對多的通信。

消息總線雖然有特殊的轉發功能,但消息總線也還是一個應用。 其它應用與消息總線的通信也是通過1.1節的基本消息類型完成的。作為一個應用,消息總線也提供了自己的接口,包括方法和信號。

我們可以通過向連接“org.freedesktop.DBus ”上對象“/”發送消息來調用消息總線提供的方法。 事實上,應用程序正是通過這些方法連接到消息總線上的其它應用,完成請求公共名等工作的。

清單

消息總線對象支持第一講中提到的標准接口"org.freedesktop.DBus.Introspectable", 我們可以調用org.freedesktop.DBus.Introspectable.Introspect方法查看消息總線對象支持的接口。例如:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.20 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus">
    <method name="Hello">
      <arg direction="out" type="s"/>
    </method>
    <method name="RequestName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="ReleaseName">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="StartServiceByName">
      <arg direction="in" type="s"/>
      <arg direction="in" type="u"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="NameHasOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="b"/>
    </method>
    <method name="ListNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="ListActivatableNames">
      <arg direction="out" type="as"/>
    </method>
    <method name="AddMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="RemoveMatch">
      <arg direction="in" type="s"/>
    </method>
    <method name="GetNameOwner">
      <arg direction="in" type="s"/>
      <arg direction="out" type="s"/>
    </method>
    <method name="ListQueuedOwners">
      <arg direction="in" type="s"/>
      <arg direction="out" type="as"/>
    </method>
    <method name="GetConnectionUnixUser">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionUnixProcessID">
      <arg direction="in" type="s"/>
      <arg direction="out" type="u"/>
    </method>
    <method name="GetConnectionSELinuxSecurityContext">
      <arg direction="in" type="s"/>
      <arg direction="out" type="ay"/>
    </method>
    <method name="ReloadConfig">
    </method>
    <method name="GetId">
      <arg direction="out" type="s"/>
    </method>
    <signal name="NameOwnerChanged">
      <arg type="s"/>
      <arg type="s"/>
      <arg type="s"/>
    </signal>
    <signal name="NameLost">
      <arg type="s"/>
    </signal>
    <signal name="NameAcquired">
      <arg type="s"/>
    </signal>
  </interface>
</node>
"

從輸出可以看到會話總線對象支持標准接口“org.freedesktop.DBus.Introspectable”和接口“org.freedesktop.DBus”。 接口“org.freedesktop.DBus”有16個方法和3個信號。下表列出了“org.freedesktop.DBus”的12個方法的簡要說明:

org.freedesktop.DBus.RequestName (in STRING name, in UINT32 flags, out UINT32 reply)
請求公眾名。其中flag定義如下:
DBUS_NAME_FLAG_ALLOW_REPLACEMENT 1
DBUS_NAME_FLAG_REPLACE_EXISTING 2
DBUS_NAME_FLAG_DO_NOT_QUEUE 4

返回值reply定義如下:
DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER 1
DBUS_REQUEST_NAME_REPLY_IN_QUEUE 2
DBUS_REQUEST_NAME_REPLY_EXISTS 3
DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER 4

org.freedesktop.DBus.ReleaseName (in STRING name, out UINT32 reply)
釋放公眾名。返回值reply定義如下:
DBUS_RELEASE_NAME_REPLY_RELEASED 1
DBUS_RELEASE_NAME_REPLY_NON_EXISTENT 2
DBUS_RELEASE_NAME_REPLY_NOT_OWNER 3

org.freedesktop.DBus.Hello (out STRING unique_name)
一個應用在通過消息總線向其它應用發消息前必須先調用Hello獲取自己這個連接的唯一名。返回值就是連接的唯一名。dbus沒有定義專門的切斷連接命令,關閉socket就是切斷連接。
在1.2節的dbus-monitor輸出中可以看到dbus-send調用消息總線的Hello方法。

org.freedesktop.DBus.ListNames (out ARRAY of STRING bus_names)
返回消息總線上已連接的所有連接名,包括所有公共名和唯一名。例如連接“org.fmddlmyy.Test”同時有公共名“org.fmddlmyy.Test”和唯一名“:1.21”, 這兩個名稱都會被返回。

org.freedesktop.DBus.ListActivatableNames (out ARRAY of STRING bus_names)
返回所有可以啟動的服務名。dbus支持按需啟動服務,即根據應用程序的請求啟動服務。

org.freedesktop.DBus.NameHasOwner (in STRING name, out BOOLEAN has_owner)
檢查是否有連接擁有指定名稱。

org.freedesktop.DBus.StartServiceByName (in STRING name, in UINT32 flags, out UINT32 ret_val)
按名稱啟動服務。參數flags暫未使用。返回值ret_val定義如下:

  1. 服務被成功啟動
  2. 已經有連接擁有要啟動的服務名

org.freedesktop.DBus.GetNameOwner (in STRING name, out STRING unique_connection_name)
返回擁有指定公眾名的連接的唯一名。

org.freedesktop.DBus.GetConnectionUnixUser (in STRING connection_name, out UINT32 unix_user_id)
返回指定連接對應的服務器進程的Unix用戶id。

org.freedesktop.DBus.AddMatch (in STRING rule)
為當前連接增加匹配規則。

org.freedesktop.DBus.RemoveMatch (in STRING rule)
為當前連接去掉指定匹配規則。

org.freedesktop.DBus.GetId (out STRING id)
返回消息總線的ID。這個ID在消息總線的生命期內是唯一的。

接口“org.freedesktop.DBus”的3個信號是:

org.freedesktop.DBus.NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner)
指定名稱的擁有者發生了變化。

org.freedesktop.DBus.NameLost (STRING name)
通知應用失去了指定名稱的擁有權。

org.freedesktop.DBus.NameAcquired (STRING name)
通知應用獲得了指定名稱的擁有權。

練習

讓我們來試試消息總線提供的方法。

從ListName到d-feet的基本邏輯

用dbus-send調用:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.23 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.Notifications"
      string "org.freedesktop.Tracker"
      string "org.freedesktop.PowerManagement"
      string ":1.7"
      string ":1.8"
      string "org.gnome.ScreenSaver"
      string ":1.9"
      string ":1.10"
      string ":1.22"
      string ":1.11"
      string "org.gnome.GnomeVFS.Daemon"
      string ":1.23"
      string ":1.12"
      string ":1.13"
      string ":1.0"
      string ":1.14"
      string ":1.1"
      string ":1.15"
      string ":1.2"
      string ":1.16"
      string ":1.3"
      string "org.gnome.GkbdConfigRegistry"
      string ":1.4"
      string "org.fmddlmyy.Test"
      string ":1.5"
      string "org.gnome.SettingsDaemon"
      string ":1.6"
   ]

這是會話總線當前已連接的連接名。在d-feet窗口的左側窗口顯示的就是ListNames返回的連接名。 聰明的讀者也許已經想到使用消息總線的“org.freedesktop.DBus.ListNames”方法和各連接的“org.freedesktop.DBus.Introspectable.Introspect”, 我們就可以像d-feet一樣查看總線上所有連接的所有對象的所有接口的所有方法和信號。

你的想法很好。但有一個問題,我們必須對連接中的對象調用“org.freedesktop.DBus.Introspectable.Introspect”方法。 ListNames只列出了連接名,我們怎么獲取連接中的對象路徑呢?

答案很簡單,如果我們不知道對象路徑就從根目錄開始吧。連接中的對象是按照樹型結構組織的。我們遍歷連接的對象樹就可以找到所有的對象。 調用對象的“org.freedesktop.DBus.Introspectable.Introspect”方法就可以查看對象的所有接口的所有方法和信號。 例如:假設我們不知道連接"org.fmddlmyy.Test"里有什么對象,我們可以對根對象"/"執行:

dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.22 -> dest=:1.25 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="TestObj"/>
</node>
"

"org.fmddlmyy.Test"的對象樹的根節點只有一個子節點"TestObj",再查看"/TestObj":

dbus-send --session --type=method_call --print-reply --dest=org.fmddlmyy.Test /TestObj org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.22 -> dest=:1.26 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.DBus.Introspectable">
    <method name="Introspect">
      <arg name="data" direction="out" type="s"/>
    </method>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <method name="Get">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="out" type="v"/>
    </method>
    <method name="Set">
      <arg name="interface" direction="in" type="s"/>
      <arg name="propname" direction="in" type="s"/>
      <arg name="value" direction="in" type="v"/>
    </method>
    <method name="GetAll">
      <arg name="interface" direction="in" type="s"/>
      <arg name="props" direction="out" type="a{sv}"/>
    </method>
  </interface>
  <interface name="org.fmddlmyy.Test.Basic">
    <method name="Add">
      <arg name="arg0" type="i" direction="in"/>
      <arg name="arg1" type="i" direction="in"/>
      <arg name="ret" type="i" direction="out"/>
    </method>
  </interface>
</node>
"

作為一個練習,讓我們來查看系統總線的上的bluez接口。執行:

dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListNames

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.30 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string ":1.7"
      string ":1.8"
      string ":1.9"
      string "org.freedesktop.SystemToolsBackends"
      string ":1.30"
      string "org.freedesktop.NetworkManagerInfo"
      string ":1.20"
      string "org.freedesktop.Avahi"
      string ":1.21"
      string "org.bluez"
      string ":1.22"
      string "org.freedesktop.NetworkManager"
      string "org.freedesktop.ConsoleKit"
      string ":1.23"
      string "com.redhat.dhcp"
      string ":1.13"
      string ":1.0"
      string ":1.14"
      string ":1.1"
      string ":1.15"
      string ":1.2"
      string "org.freedesktop.Hal"
      string "com.redhat.NewPrinterNotification"
      string ":1.16"
      string ":1.3"
      string ":1.17"
      string ":1.4"
      string ":1.18"
      string ":1.5"
      string ":1.19"
      string ":1.6"
   ]

我們看到連接"org.bluez"。查看它的根對象:

dbus-send --system --type=method_call --print-reply --dest=org.bluez / org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.31 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="org"/>
</node>
"

接着查對象"/org":

dbus-send --system --type=method_call --print-reply --dest=org.bluez /org org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.32 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <node name="bluez"/>
</node>
"

接着查對象"/org/bluez":

dbus-send --system --type=method_call --print-reply --dest=org.bluez /org/bluez org.freedesktop.DBus.Introspectable.Introspect

輸出為:

method return sender=:1.7 -> dest=:1.33 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/org/bluez">
        <interface name="org.bluez.Manager">
                <method name="InterfaceVersion">
                        <arg type="u" direction="out"/>
                </method>
                <method name="DefaultAdapter">
                        <arg type="s" direction="out"/>
                </method>
                <method name="FindAdapter">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <method name="ListAdapters">
                        <arg type="as" direction="out"/>
                </method>
                <method name="FindService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <method name="ListServices">
                        <arg type="as" direction="out"/>
                </method>
                <method name="ActivateService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="out"/>
                </method>
                <signal name="AdapterAdded">
                        <arg type="s"/>
                </signal>
                <signal name="AdapterRemoved">
                        <arg type="s"/>
                </signal>
                <signal name="DefaultAdapterChanged">
                        <arg type="s"/>
                </signal>
                <signal name="ServiceAdded">
                        <arg type="s"/>
                </signal>
                <signal name="ServiceRemoved">
                        <arg type="s"/>
                </signal>
        </interface>
        <interface name="org.bluez.Database">
                <method name="AddServiceRecord">
                        <arg type="ay" direction="in"/>
                        <arg type="u" direction="out"/>
                </method>
                <method name="AddServiceRecordFromXML">
                        <arg type="s" direction="in"/>
                        <arg type="u" direction="out"/>
                </method>
                <method name="UpdateServiceRecord">
                        <arg type="u" direction="in"/>
                        <arg type="ay" direction="in"/>
                </method>
                <method name="UpdateServiceRecordFromXML">
                        <arg type="u" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="RemoveServiceRecord">
                        <arg type="u" direction="in"/>
                </method>
                <method name="RegisterService">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterService">
                        <arg type="s" direction="in"/>
                </method>
                <method name="RequestAuthorization">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="CancelAuthorizationRequest">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
        </interface>
        <interface name="org.bluez.Security">
                <method name="RegisterDefaultPasskeyAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterDefaultPasskeyAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="RegisterPasskeyAgent">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterPasskeyAgent">
                        <arg type="s" direction="in"/>
                        <arg type="s" direction="in"/>
                </method>
                <method name="RegisterDefaultAuthorizationAgent">
                        <arg type="s" direction="in"/>
                </method>
                <method name="UnregisterDefaultAuthorizationAgent">
                        <arg type="s" direction="in"/>
                </method>
        </interface>
        <node name="service_audio"/>
        <node name="service_input"/>
        <node name="service_network"/>
        <node name="service_serial"/>
</node>
"

我們看到了對象"/org/bluez"的所有接口。對象"/org/bluez"還有子節點"service_audio"、"service_input"、"service_network"和"service_serial"。 必要時我們可以接着查下去。d-feet的基本邏輯就是這樣。 后面我們會自己實現一個dteeth。dteeth是命令行程序,可以遍歷指定連接的對象樹,列出所有對象的所有接口的方法和信號。

ListActivatableNames和服務器的自動啟動

運行:

dbus-send --system --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListActivatableNames
dbus-send --session --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.ListActivatableNames

返回的數據是一樣的。在我的電腦上返回的數據是:

array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.Notifications"
      string "net.ekiga.helper"
      string "org.freedesktop.PowerManagement"
      string "org.freedesktop.Tracker"
      string "org.freedesktop.SystemToolsBackends.GroupsConfig"
      string "org.freedesktop.SystemToolsBackends.NTPConfig"
      string "org.gnome.Tomboy"
      string "org.freedesktop.SystemToolsBackends.HostsConfig"
      string "org.freedesktop.SystemToolsBackends.NFSConfig"
      string "org.freedesktop.SystemToolsBackends"
      string "net.ekiga.instance"
      string "org.gnome.GnomeVFS.Daemon"
      string "com.redhat.dhcp"
      string "org.freedesktop.SystemToolsBackends.TimeConfig"
      string "org.freedesktop.SystemToolsBackends.IfacesConfig"
      string "org.freedesktop.SystemToolsBackends.ServicesConfig"
      string "org.gnome.Rhythmbox"
      string "org.freedesktop.SystemToolsBackends.Platform"
      string "org.freedesktop.SystemToolsBackends.UsersConfig"
      string "org.freedesktop.SystemToolsBackends.SMBConfig"
      string "org.gnome.SettingsDaemon"
   ]

我們也可以用python腳本調用ListActivatableNames。例如:寫一個叫dls.py的腳本:

cat dls.py
#!/usr/bin/env python
import dbus
bus=dbus.SystemBus()
bus_obj=bus.get_object('org.freedesktop.DBus', '/')
iface=dbus.Interface(bus_obj, 'org.freedesktop.DBus')
names=iface.ListActivatableNames()
for n in names:
    print n

運行:

./dls.py |sort

輸出為:

com.redhat.dhcp
net.ekiga.helper
net.ekiga.instance
org.freedesktop.DBus
org.freedesktop.Notifications
org.freedesktop.PowerManagement
org.freedesktop.SystemToolsBackends
org.freedesktop.SystemToolsBackends.GroupsConfig
org.freedesktop.SystemToolsBackends.HostsConfig
org.freedesktop.SystemToolsBackends.IfacesConfig
org.freedesktop.SystemToolsBackends.NFSConfig
org.freedesktop.SystemToolsBackends.NTPConfig
org.freedesktop.SystemToolsBackends.Platform
org.freedesktop.SystemToolsBackends.ServicesConfig
org.freedesktop.SystemToolsBackends.SMBConfig
org.freedesktop.SystemToolsBackends.TimeConfig
org.freedesktop.SystemToolsBackends.UsersConfig
org.freedesktop.Tracker
org.gnome.GnomeVFS.Daemon
org.gnome.Rhythmbox
org.gnome.SettingsDaemon
org.gnome.Tomboy

使用python腳本調用dbus接口是不是很簡單。如果你看過dbus-glib的代碼(后面會講解),你對python的簡潔會有更深刻的感觸。如果你執行:

cat /usr/share/dbus-1/services/*|grep Name|awk -F= '{print $2}'|sort

你會得到:

com.redhat.dhcp
net.ekiga.helper
net.ekiga.instance
org.freedesktop.Notifications
org.freedesktop.PowerManagement
org.freedesktop.SystemToolsBackends
org.freedesktop.SystemToolsBackends.GroupsConfig
org.freedesktop.SystemToolsBackends.HostsConfig
org.freedesktop.SystemToolsBackends.IfacesConfig
org.freedesktop.SystemToolsBackends.NFSConfig
org.freedesktop.SystemToolsBackends.NTPConfig
org.freedesktop.SystemToolsBackends.Platform
org.freedesktop.SystemToolsBackends.ServicesConfig
org.freedesktop.SystemToolsBackends.SMBConfig
org.freedesktop.SystemToolsBackends.TimeConfig
org.freedesktop.SystemToolsBackends.UsersConfig
org.freedesktop.Tracker
org.gnome.GnomeVFS.Daemon
org.gnome.Rhythmbox
org.gnome.SettingsDaemon
org.gnome.Tomboy

這條命令的輸出與ListActivatableNames的輸出是不是基本相同?你能看懂上面這條命令嗎?它將"/usr/share/dbus-1/services/"下所有文件交給grep篩選出包含“Name”的行。將包含“Name”的行交給awk處理,awk用"="作為列分隔符,取出第二列然后交給sort排序后輸出。 "/usr/share/dbus-1/services/"目錄就是dbus放service文件的地方。需要自動啟動的服務器會在這個目錄放一個service文件,例如:

cat /usr/share/dbus-1/services/dhcdbd.service
[D-BUS Service]
Name=com.redhat.dhcp
Exec=/usr/sbin/dhcdbd

Name是服務器的公共名,Exec是服務器的執行路徑。在客戶請求一個服務,但該服務還沒有啟動時。dbus會根據service文件自動啟動服務。我們再寫一個調用“org.fmddlmyy.Test”的Add接口的python腳本:

cat add.py
#!/usr/bin/env python
import dbus
bus = dbus.SessionBus()
obj = bus.get_object( 'org.fmddlmyy.Test', '/TestObj' )
iface = dbus.Interface(obj, 'org.fmddlmyy.Test.Basic')
sum = iface.Add(100, 999)
print sum

在啟動“org.fmddlmyy.Test”服務器前調用這個腳本

./add.py

會得到錯誤輸出:

...
dbus.exceptions.DBusException: org.freedesktop.DBus.Error.ServiceUnknown: The name org.fmddlmyy.Test was not provided by any .service files

我們編輯一個service文件:

cat org.fmddlmyy.Test.service
[D-BUS Service]
Name=org.fmddlmyy.Test
Exec=/home/lvjie/work/dbus/hello-dbus3-0.1/src/example-service

把這個文件放到"/usr/share/dbus-1/services/"目錄后,再執行add.py:

sudo cp org.fmddlmyy.Test.service /usr/share/dbus-1/services/
cd ../../py
./add.py
1099

這次dbus自動啟動了服務器,我們的客戶腳本得到了正確的輸出,你有沒有感到dbus的神奇?dbus在自動啟動服務器后,不會自動關閉。如果沒人管它,這個服務器會一直開着。

其它方法

再演示幾個“org.freedesktop.DBus”接口的方法。NameHasOwner判斷有沒有連接擁有指定的公共名:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.NameHasOwner string:"org.fmddlmyy.Test"

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.31 reply_serial=2
   boolean true

GetNameOwner返回公共名對應的唯一名:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.GetNameOwner string:"org.fmddlmyy.Test"

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.32 reply_serial=2
   string ":1.30"

GetConnectionUnixUser返回指定連接對應的服務器進程的Unix用戶id:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.GetConnectionUnixUser string:":1.30"

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.33 reply_serial=2
   uint32 1000

這就是我的用戶id:

id -u lvjie
1000

GetId返回消息總線的ID:

dbus-send --session --type=method_call --print-reply --dest=org.freedesktop.DBus / org.freedesktop.DBus.GetId

輸出為:

method return sender=org.freedesktop.DBus -> dest=:1.34 reply_serial=2
   string "dc209fee5f8ce01b0c23da0049668f11"

數據類型和dteeth

我想在freerunner(一個開源linux手機)上查看fso(openmoko的諸多軟件版本之一)的dbus信息。但fso的python沒有gtk模塊,跑不了d-feet。 在上一講我介紹了d-feet的基本思路:用“org.freedesktop.DBus.ListNames”枚舉消息總線上的連接,用“org.freedesktop.DBus.Introspectable.Introspect” 從"/"開始遍歷連接的對象樹。上一講我們手工查看了兩個連接,那么我們能不能寫一個程序自動遍歷連接的對象樹, 輸出指定連接的所有對象的所有接口的所有方法和信號?

當然可以,為此我寫了一個叫dteeth的python腳本。不過在介紹這個腳本前,讓我們先看看dbus的數據類型。

dbus的數據類型

dbus用xml描述接口,例如:

<?xml version="1.0" encoding="UTF-8" ?>

<node name="/org/freesmartphone/GSM/Device">
  <interface name="org.freesmartphone.GSM.SMS">
    <method name="SendMessage">
       <arg name="number" type="s"/>
       <arg name="contents" type="s"/>
       <arg name="featuremap" type="a{sv}"/>
       <arg type="i" direction="out"/>
    </method>
    <signal name="IncomingMessage">
       <arg name="address" type="s"/>
       <arg name="contents" type="s"/>
       <arg name="features" type="a{sv}"/>
    </signal>
  </interface>
</node>

其實前兩講已經看過很多例子了。node就是接口中的對象,node可以包含node,構成對象樹。 dbus的接口描述文件統一采用utf-8編碼。 我相信讀者很容易理解這個接口描述文件。我只想解釋一下描述參數數據類型的type域。 dbus的數據類型是由"s"或"a{sv}"這樣的類型簽名(Type Signatures)定義的。 類型簽名中可以使用以下標記:

標記 含義
a ARRAY 數組
b BOOLEAN 布爾值
d DOUBLE IEEE 754雙精度浮點數
g SIGNATURE 類型簽名
i INT32 32位有符號整數
n INT16 16位有符號整數
o OBJECT_PATH 對象路徑
q UINT16 16位無符號整數
s STRING 零結尾的UTF-8字符串
t UINT64 64位無符號整數
u UINT32 32位無符號整數
v VARIANT 可以放任意數據類型的容器,數據中包含類型信息。例如glib中的GValue。
x INT64 64位有符號整數
y BYTE 8位無符號整數
() 定義結構時使用。例如"(i(ii))"
{} 定義鍵-值對時使用。例如"a{us}"

a表示數組,數組元素的類型由a后面的標記決定。例如:

  • "as"是字符串數組。
  • 數組"a(i(ii))"的元素是一個結構。用括號將成員的類型括起來就表示結構了,結構可以嵌套。
  • 數組"a{sv}"的元素是一個鍵-值對。"{sv}"表示鍵類型是字符串,值類型是VARIANT。

在以后的例子中,我們會親手實現上面這個xml描述的接口,包括服務器和客戶程序。 到時候,讀者會對dbus的數據類型有更直觀的認識。

dteeth

運行dteeth

可以從這里下載dteeth的源代碼。其中包含兩個python腳本:dteeth.py和_introspect_parser.py。 dteeth.py是我寫的。_introspect_parser.py是個開源模塊,可以分析Introspect返回的xml數據。

dteeth用法如下:

./dteeth.py -h
Usage: dteeth [--system] <name of a connection on the bus >

默認連接session總線,除非你加上--system。可以一次指定同一消息總線的多個連接。先在PC上試一試:

./dteeth.py org.fmddlmyy.Test
org.fmddlmyy.Test
    /TestObj
        org.fmddlmyy.Test.Basic
            methods
                Add( in i arg0 , in i arg1 , out i ret )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s data )
        org.freedesktop.DBus.Properties
            methods
                Set( in s interface , in s propname , in v value )
                GetAll( in s interface , out a{sv} props )
                Get( in s interface , in s propname , out v value )

我也在fso版本的freerunner手機上運行了一下,得到了org.freesmartphone.ogsmd的所有對象的所有的接口的所有方法和信號:

org.freesmartphone.ogsmd
    /org/freedesktop/Gypsy
        org.freedesktop.Gypsy.Time
            signals
                TimeChanged( i time )
            methods
                GetTime( out i )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freedesktop.Gypsy.Device
            signals
                FixStatusChanged( i fixstatus )
                ConnectionStatusChanged( b constatus )
            methods
                GetConnectionStatus( out b )
                Stop( )
                Start( )
                GetFixStatus( out i )
        org.freedesktop.Gypsy.Course
            signals
                CourseChanged( i fields , i tstamp , d speed , d heading , d climb )
            methods
                GetCourse( out i , out i , out d , out d , out d )
        org.freedesktop.Gypsy.Position
            signals
                PositionChanged( i fields , i tstamp , d lat , d lon , d alt )
            methods
                GetPosition( out i , out i , out d , out d , out d )
        org.freedesktop.Gypsy.Accuracy
            signals
                AccuracyChanged( i fields , d pdop , d hdop , d vdop )
            methods
                GetAccuracy( out i , out d , out d , out d )
        org.freesmartphone.Resource
            methods
                Enable( )
                Disable( )
                Suspend( )
                Resume( )
        org.freedesktop.Gypsy.Satellite
            signals
                SatellitesChanged( a(ubuuu) satellites )
            methods
                GetSatellites( out a(ubuuu) )
        org.freesmartphone.GPS.UBX
            signals
                DebugPacket( s clid , i length , aa{sv} data )
            methods
                SendDebugPacket( in s clid , in i length , in aa{sv} data )
                GetDebugFilter( in s clid , out b )
                SetDebugFilter( in s clid , in b state )
        org.freedesktop.Gypsy.Server
            methods
                Create( in s device , out o )
                Shutdown( in o path )
    /org/freesmartphone/Device/Audio
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.Audio
            signals
                SoundStatus( s name , s status , a{sv} properties )
                Scenario( s scenario , s reason )
            methods
                SetScenario( in s name )
                GetInfo( out s )
                GetAvailableScenarios( out as )
                PushScenario( in s name )
                GetScenario( out s )
                PullScenario( out s )
                StopSound( in s name )
                StopAllSounds( )
                PlaySound( in s name )
                StoreScenario( in s name )
    /org/freesmartphone/Device/Display/pcf50633_bl
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.Display
            methods
                SetBrightness( in i brightness )
                GetName( out s )
                SetBacklightPower( in b power )
                GetBrightness( out i )
                GetBacklightPower( out b )
    /org/freesmartphone/Device/IdleNotifier/0
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.IdleNotifier
            signals
                State( s state )
            methods
                SetState( in s state )
                GetState( out s )
                SetTimeout( in s state , in i timeout )
                GetTimeouts( out a{si} )
    /org/freesmartphone/Device/Info
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.Info
            methods
                GetCpuInfo( out a{sv} )
    /org/freesmartphone/Device/Input
        org.freesmartphone.Device.Input
            signals
                Event( s name , s action , i seconds )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
    /org/freesmartphone/Device/LED/gta02_aux_red
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.LED
            methods
                SetBrightness( in i brightness )
                GetName( out s )
                SetBlinking( in i delay_on , in i delay_off )
    /org/freesmartphone/Device/LED/gta02_power_blue
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.LED
            methods
                SetBrightness( in i brightness )
                GetName( out s )
                SetBlinking( in i delay_on , in i delay_off )
    /org/freesmartphone/Device/LED/gta02_power_orange
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.LED
            methods
                SetBrightness( in i brightness )
                GetName( out s )
                SetBlinking( in i delay_on , in i delay_off )
    /org/freesmartphone/Device/LED/neo1973_vibrator
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.LED
            methods
                SetBrightness( in i brightness )
                GetName( out s )
                SetBlinking( in i delay_on , in i delay_off )
    /org/freesmartphone/Device/PowerControl/Bluetooth
        org.freesmartphone.Device.PowerControl
            signals
                Power( s device , b power )
            methods
                Reset( )
                GetName( out s )
                SetPower( in b power )
                GetPower( out b )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Resource
            methods
                Resume( )
                Enable( )
                Disable( )
                Suspend( )
    /org/freesmartphone/Device/PowerControl/UsbHost
        org.freesmartphone.Device.PowerControl
            signals
                Power( s device , b power )
            methods
                Reset( )
                GetName( out s )
                SetPower( in b power )
                GetPower( out b )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
    /org/freesmartphone/Device/PowerControl/WiFi
        org.freesmartphone.Device.PowerControl
            signals
                Power( s device , b power )
            methods
                Reset( )
                GetName( out s )
                SetPower( in b power )
                GetPower( out b )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Resource
            methods
                Resume( )
                Enable( )
                Disable( )
                Suspend( )
    /org/freesmartphone/Device/PowerSupply/apm
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.PowerSupply
            methods
                GetName( out s )
                GetEnergyPercentage( out i )
                GetOnBattery( out b )
                GetInfo( out a{sv} )
    /org/freesmartphone/Device/PowerSupply/bat
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.PowerSupply
            signals
                PowerStatus( s status )
                Capacity( i percent )
            methods
                GetEnergyPercentage( out i )
                GetInfo( out a{sv} )
                IsPresent( out b )
                GetName( out s )
                GetCapacity( out i )
                GetPowerStatus( out s )
    /org/freesmartphone/Device/RealTimeClock/rtc0
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Device.RealTimeClock
            methods
                GetWakeupReason( out s )
                SetCurrentTime( in s t )
                Suspend( )
                GetWakeupTime( out s )
                GetName( out s )
                GetCurrentTime( out s )
                SetWakeupTime( in s t )
    /org/freesmartphone/Events
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Events
            methods
                AddRule( in s rule_str )
                TriggerTest( in s name , in b value )
    /org/freesmartphone/Framework
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Framework
            methods
                GetDebugLevel( in s logger , out s )
                GetDebugDestination( out s , out s )
                ListDebugLoggers( out as )
                ListObjectsInSubsystem( in s subsystem , out as )
                SetDebugDestination( in s category , in s destination )
                SetDebugLevel( in s logger , in s levelname )
                ListObjectsByInterface( in s interface , out ao )
                ListSubsystems( out as )
    /org/freesmartphone/GSM/Device
        org.freesmartphone.GSM.Call
            signals
                CallStatus( i index , s status , a{sv} properties )
            methods
                Activate( in i index )
                Emergency( in s number )
                SendDtmf( in s tones )
                ReleaseHeld( )
                HoldActive( )
                ReleaseAll( )
                Initiate( in s number , in s type_ , out i )
                ListCalls( out a(isa{sv}) )
                Transfer( in s number )
                Release( in i index )
                ActivateConference( in i index )
        org.freesmartphone.GSM.Debug
            methods
                DebugInjectString( in s channel , in s string )
                DebugCommand( in s command , out as )
                DebugEcho( in s echo , out s )
                DebugListChannels( out as )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.GSM.Device
            methods
                CancelCommand( )
                GetInfo( out a{sv} )
                GetAntennaPower( out b )
                SetSimBuffersSms( in b sim_buffers_sms )
                GetFeatures( out a{sv} )
                SetAntennaPower( in b power )
                GetSimBuffersSms( out b )
        org.freesmartphone.GSM.SMS
            signals
                IncomingMessage( s address , s text , a{sv} features )
            methods
                SendMessage( in s number , in s contents , in a{sv} featuremap , out i )
        org.freesmartphone.GSM.SIM
            signals
                ReadyStatus( b status )
                MemoryFull( )
                AuthStatus( s status )
                IncomingStoredMessage( i index )
            methods
                RetrievePhonebook( in s category , out a(iss) )
                SendAuthCode( in s code )
                ChangeAuthCode( in s old_pin , in s new_pin )
                SendGenericSimCommand( in s command , out s )
                ListPhonebooks( out as )
                SetServiceCenterNumber( in s number )
                GetHomeZones( out a(siii) )
                RetrieveEntry( in s category , in i index , out s , out s )
                DeleteMessage( in i index )
                SendRestrictedSimCommand( in i command , in i fileid , in i p1 , in i p2 , in i p3 , in s data , out i , out i , out s )
                GetMessagebookInfo( out a{sv} )
                GetSimReady( out b )
                GetPhonebookInfo( in s category , out a{sv} )
                GetSimInfo( out a{sv} )
                SendStoredMessage( in i index , out i )
                SetAuthCodeRequired( in b required , in s pin )
                GetAuthStatus( out s )
                StoreMessage( in s number , in s contents , in a{sv} featuremap , out i )
                GetAuthCodeRequired( out b )
                RetrieveMessage( in i index , out s , out s , out s , out a{sv} )
                StoreEntry( in s category , in i index , in s name , in s number )
                Unlock( in s puk , in s new_pin )
                GetServiceCenterNumber( out s )
                RetrieveMessagebook( in s category , out a(isssa{sv}) )
                DeleteEntry( in s category , in i index )
        org.freesmartphone.GSM.Network
            signals
                Status( a{sv} status )
                SignalStrength( i strength )
                IncomingUssd( s mode , s message )
            methods
                EnableCallForwarding( in s reason , in s class_ , in s number , in i timeout )
                ListProviders( out a(isss) )
                GetCallForwarding( in s reason , out a{sv} )
                Unregister( )
                SetCallingIdentification( in s status )
                Register( )
                SendUssdRequest( in s request )
                DisableCallForwarding( in s reason , in s class_ )
                GetSignalStrength( out i )
                GetCallingIdentification( out s )
                RegisterWithProvider( in i operator_code )
                GetNetworkCountryCode( out s )
                GetStatus( out a{sv} )
        org.freesmartphone.Resource
            methods
                Enable( )
                Disable( )
                Suspend( )
                Resume( )
        org.freesmartphone.GSM.CB
            signals
                IncomingCellBroadcast( i channel , s data )
            methods
                GetCellBroadcastSubscriptions( out s )
                SetCellBroadcastSubscriptions( in s channels )
        org.freesmartphone.GSM.PDP
            signals
                ContextStatus( i index , s status , a{sv} properties )
            methods
                SetCurrentGprsClass( in s class_ )
                ActivateContext( in s apn , in s user , in s password )
                DeactivateContext( )
                ListAvailableGprsClasses( out as )
                GetContextStatus( out s )
                GetCurrentGprsClass( out s )
    /org/freesmartphone/GSM/Server
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.GSM.HZ
            signals
                HomeZoneStatus( s zone )
            methods
                GetHomeZoneStatus( out s )
                GetKnownHomeZones( out as )
    /org/freesmartphone/PIM/Contacts
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.Contacts
            methods
                Query( in a{sv} query , out s )
                Add( in a{sv} contact_data , out s )
                GetSingleContactSingleField( in a{sv} query , in s field_name , out s )
        org.freesmartphone.PIM.Contact
            methods
                GetContent( out a{sv} )
                GetMultipleFields( in s field_list , out a{sv} )
    /org/freesmartphone/PIM/Contacts/Queries
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.ContactQuery
            methods
                GetContactPath( out s )
                Skip( in i num_entries )
                Dispose( )
                GetResult( out a{sv} )
                GetResultCount( out i )
                Rewind( )
                GetMultipleResults( in i num_entries , out aa{sv} )
    /org/freesmartphone/PIM/Messages
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.Messages
            signals
                NewMessage( s message_URI )
            methods
                GetSingleMessageSingleField( in a{sv} query , in s field_name , out s )
                Query( in a{sv} query , out s )
                Add( in a{sv} message_data , out s )
                GetFolderURIFromName( in s folder_name , out s )
                GetFolderNames( out as )
        org.freesmartphone.PIM.Message
            methods
                GetContent( out a{sv} )
                MoveToFolder( in s new_folder_name )
                GetMultipleFields( in s field_list , out a{sv} )
    /org/freesmartphone/PIM/Messages/Folders
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.Messages
            signals
                NewMessage( s message_URI )
            methods
                GetSingleMessageSingleField( in a{sv} query , in s field_name , out s )
                Query( in a{sv} query , out s )
                Add( in a{sv} message_data , out s )
                GetFolderURIFromName( in s folder_name , out s )
                GetFolderNames( out as )
        org.freesmartphone.PIM.Message
            methods
                GetContent( out a{sv} )
                MoveToFolder( in s new_folder_name )
                GetMultipleFields( in s field_list , out a{sv} )
    /org/freesmartphone/PIM/Messages/Folders/0
        org.freesmartphone.PIM.MessageFolder
            signals
                MessageMoved( s message_uri , s new_folder_name )
            methods
                GetMessageCount( out i )
                GetMessageURIs( in i first_message_id , in i message_count , out as )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
    /org/freesmartphone/PIM/Messages/Folders/1
        org.freesmartphone.PIM.MessageFolder
            signals
                MessageMoved( s message_uri , s new_folder_name )
            methods
                GetMessageCount( out i )
                GetMessageURIs( in i first_message_id , in i message_count , out as )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
    /org/freesmartphone/PIM/Messages/Queries
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.MessageQuery
            methods
                Skip( in i num_entries )
                Dispose( )
                GetResult( out a{sv} )
                GetResultCount( out i )
                Rewind( )
                GetMultipleResults( in i num_entries , out a{ia{sv}} )
                GetMessageURI( out s )
    /org/freesmartphone/PIM/Sources
        org.freesmartphone.PIM.Sources
            methods
                GetEntryCount( out i )
                InitAllEntries( )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.PIM.Source
            methods
                GetSupportedPIMDomains( out as )
                GetName( out s )
                GetStatus( out s )
    /org/freesmartphone/Phone
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Phone
            signals
                Incoming( o call )
            methods
                InitProtocols( out as )
                CreateCall( in s number , in s protocol , in b force , out o )
    /org/freesmartphone/Preferences
        org.freesmartphone.Preferences
            methods
                GetProfiles( out as )
                GetService( in s name , out o )
                GetServices( out as )
                SetProfile( in s profile )
                GetProfile( out s )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
    /org/freesmartphone/Preferences/rules
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Preferences.Service
            signals
                Notify( s key , v value )
            methods
                GetType( in s key , out s )
                SetValue( in s key , in v value )
                GetKeys( out as )
                IsProfilable( in s key , out b )
                GetValue( in s key , out v )
    /org/freesmartphone/Time
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Time
            signals
                Minute( i year , i mon , i day , i hour , i min , i sec , i wday , i yday , i isdst )
            methods
                GetLocalTime( in i seconds , out i , out i , out i , out i , out i , out i , out i , out i , out i )
    /org/freesmartphone/Time/Alarm
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )
        org.freesmartphone.Time.Alarm
            methods
                ClearAlarm( in s busname )
                SetAlarm( in s busname , in i timestamp )
    /org/freesmartphone/Usage
        org.freesmartphone.Usage
            signals
                ResourceAvailable( s resourcename , b state )
                ResourceChanged( s resourcename , b state , a{sv} attributes )
            methods
                ReleaseResource( in s resourcename )
                Suspend( )
                GetResourceState( in s resourcename , out b )
                SetResourcePolicy( in s resourcename , in s policy )
                GetResourcePolicy( in s resourcename , out s )
                GetResourceUsers( in s resourcename , out as )
                ListResources( out as )
                RegisterResource( in s resourcename , in o path )
                RequestResource( in s resourcename )
        org.freedesktop.DBus.Introspectable
            methods
                Introspect( out s )

源代碼

下面是dteeth的源代碼:
dteeth.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import dbus
import _introspect_parser
import getopt, sys
MARGIN_WIDTH = 4
ONE_MARGIN = ' ' * MARGIN_WIDTH

# signal是個元組,它有一個元素,是一個列表。列表的元素是signal的參數
# 列表的每個元素都是字典。它有兩個元素,鍵值分別是'type'和'name'
def show_signal(name, signal, margin):
    print margin+name+'(',
    args = signal[0]
    for i, arg in enumerate(args):
        if i > 0:
            print ',',
        if arg['name']:
            print '%s %s' % (arg['type'], arg['name']),
        else:
            print '%s' % arg['type'],
    print  ')'

# method是個元組,它有兩個元素,都是列表。前一個列表的元素是輸入參數,后一個列表的元素是輸出參數
def show_method(name, method, margin):
    print margin+name+'(',
# 輸入參數
    args = method[0]
    in_num = len(args)
    out_num = len(method[1])
    for i, arg in enumerate(args):
        if i > 0:
            print ',',
        if arg['name']:
            print 'in %s %s' % (arg['type'], arg['name']),
        else:
            print 'in %s' % arg['type'],
# 輸出參數
    if (in_num > 0) and (out_num > 0) :
        print ',',
    args = method[1]
    for i, arg in enumerate(args):
        if i > 0:
            print ',',
        if arg['name']:
            print 'out %s %s' % (arg['type'], arg['name']),
        else:
            print 'out %s' % arg['type'],
    print  ')'

def show_property(name, property, margin):
    print margin+name
    print margin,
    print property

# interfaces是個字典,它有三個元素,鍵值分別是'signals'、'methods'和'properties'
def show_iface(name, iface, margin):
    print margin + name
    margin += ONE_MARGIN
    signals=iface['signals']
    l = len(signals)
    if l > 0:
        print margin+'signals'
        for node in signals:
            show_signal(node, signals[node], margin+ONE_MARGIN)

    methods=iface['methods']
    l = len(methods)
    if l > 0:
        print margin+'methods'
        for node in methods:
            show_method(node, methods[node], margin+ONE_MARGIN)

    properties=iface['properties']
    l = len(properties)
    if l > 0:
        print margin+'properties'
        for node in properties:
            show_property(node, properties[node], margin+ONE_MARGIN)

def show_obj(bus, name, obj_name, margin):
    obj=bus.get_object(name, obj_name)
    iface=dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
    xml=iface.Introspect();
    data = _introspect_parser.process_introspection_data(xml)

    # data是個字典,它有兩個元素,鍵值分別是'child_nodes'和'interfaces'
    if len(data['interfaces']) > 0:
        print margin + obj_name

    for node in data['interfaces']:
        iface=data['interfaces'][node]
        show_iface(node, iface, margin+ONE_MARGIN)

    for node in data['child_nodes']:
        if obj_name == '/':
            show_obj(bus, name, '/' + node, margin)
        else:
            show_obj(bus, name, obj_name + '/' + node, margin)

def show_connection(bus, name, margin):
    print margin + name
    show_obj(bus, name, '/', margin+ONE_MARGIN)

def usage():
    print "Usage: dteeth [--system] "

def main():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "h", ["help", "system"])
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err) # will print something like "option -a not recognized"
        usage()
        sys.exit(2)

    if len(args) == 0:
        usage()
        sys.exit(2)

    use_system = False
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o == "--system":
            use_system = True
        else:
            assert False, "unhandled option"

    if use_system:
        bus=dbus.SystemBus()
    else:
        bus=dbus.SessionBus()

    for arg in args:
        show_connection(bus, arg, "")

if __name__ == "__main__":
    main()

dteeth是我寫的第一個超過10行的python腳本。對於熟悉python的讀者,dteeth應該是很簡單的。 不過我還是簡單解釋一下dteeth的主要邏輯。

dteeth的主要邏輯

main函數分析命令行,對命令行上指定的每個連接調用show_connection函數。 show_connection在打印連接名后調用show_obj函數。show_obj從根對象"/"開始遍歷連接的對象樹。

show_obj對輸入對象調用Introspect方法,返回的xml數據交由_introspect_parser處理。 _introspect_parser會從xml數據中分出inerface和node。 show_obj對inerface調用show_iface顯示。 show_obj對node會遞歸調用show_obj,實現對象樹的遍歷。

_introspect_parser的輸出格式

_introspect_parser.process_introspection_data函數分析Introspect方法返回的xml數據。 為了了解_introspect_parser的輸出格式,我們可以寫個小腳本:

cat ti.py
#!/usr/bin/env python
import dbus
import _introspect_parser
bus=dbus.SessionBus()
obj=bus.get_object('org.freesmartphone.ogsmd', '/org/freesmartphone/GSM/Device')
iface=dbus.Interface(obj, 'org.freedesktop.DBus.Introspectable')
xml=iface.Introspect();
data = _introspect_parser.process_introspection_data(xml)
print data

可以用這個腳本直接打印process_introspection_data返回的數據。下面是整理后的輸出:

{
  'interfaces':
  {
    u'org.freedesktop.DBus.Introspectable':
    {
      'signals': {},
      'methods':
      {
        u'Introspect':
        (
          [],
          [{'type': u's', 'name': u'data'}]
        )
      },
      'properties': {}
    },
    u'org.freedesktop.DBus.Properties':
    {
      'signals': {},
      'methods':
      {
        u'Set':
        (
          [{'type': u's', 'name': u'interface'}, {'type': u's', 'name': u'propname'}, {'type': u'v', 'name': u'value'}],
          []
        ),
        u'GetAll':
        (
          [{'type': u's', 'name': u'interface'}],
          [{'type': u'a{sv}', 'name': u'props'}]
        ),
        u'Get':
        (
          [{'type': u's', 'name': u'interface'}, {'type': u's', 'name': u'propname'}],
          [{'type': u'v', 'name': u'value'}]
        )
      },
      'properties': {}
    },
    u'org.freesmartphone.GSM.SMS':
    {
      'signals':
      {
        u'IncomingMessage':
        (
          [{'type': u's', 'name': None}, {'type': u's', 'name': None}, {'type': u'a{sv}', 'name': None}],
        )
      },
      'methods':
      {
        u'SendMessage':
        (
          [{'type': u's', 'name': u'number'}, {'type': u's', 'name': u'contents'}, {'type': u'a{sv}', 'name': u'featuremap'}],
          [{'type': u'i', 'name': u'arg3'}]
        )
      },
      'properties': {}
    }
  },
  'child_nodes': []
}

所有字符串前面都有前綴u,表示這些字符串都是Unicode編碼。在python中,字典用{},元組用(),列表用[]。從括號我們就能看出數據格式。

我們看到process_introspection_data返回返回一個字典。這個字典有兩個映射。一個映射的鍵值是"interfaces",另一個映射的鍵值是"child_nodes"。

映射"child_nodes"的值是一個列表,列出所有子節點的名稱。
映射"interfaces"的值還是一個字典。這個字典的每個映射的鍵值是一個接口名稱。每個映射的值類型還是字典,這個字典有3個映射,映射的鍵值分別是'signals'、'methods'和'properties',映射的值類型都是字典。

'signals'對應字典的每個鍵值是一個信號名稱。每個映射的值類型是元組。這個元組只有一個元素,類型是列表, 即信號的參數列表。

參數列表的元素類型是字典。這個字典有2個映射,映射的鍵值分別是'type'和'name'。'type'是參數類型,'name'是參數名稱。 映射的值類型都是字符串。

'methods'對應字典的每個鍵值是一個方法名稱。每個映射的值類型是元組。這個元組有兩個元素,類型是列表, 分別是方法的輸入參數列表和輸出參數列表。參數列表的元素類型和信號的參數列表相同。

我看到'properties'映射都是空的,就沒有研究。

復雜的數據類型

在dbus中怎樣處理復雜的數據類型?第一個建議是盡量不要使用復雜的數據類型。但如果確實需要呢? 有的網友建議用GArray作為容器, 不管什么參數,在客戶端都手工放入GArray,在服務器端再自己取出來。 這確實是個思路,比較適合服務器和客戶端都是自己開發的情況。 還有一篇"How to pass a variant with dbus-glib" 介紹了怎樣用GValue傳遞復雜的數據類型,讀者可以參考。

下面看看在我們的例子中是怎樣處理a{sv}參數的:
sms_features.h

#ifndef SMS_FEATURES_H
#define SMS_FEATURES_H

#include <glib-object.h>
GHashTable *sms_create_features(const char * alphabet, int csm_num, int csm_seq);
GType sms_get_features_type(void);
void sms_release_features(GHashTable *features);
void sms_show_features(GHashTable *features);
#endif

sms_features.h聲明了幾個函數。這個例子的服務器、客戶端都會調用。以下是這些函數的實現:
sms_features.c

#include "sms_features.h"

static void release_val(gpointer data)
{
    GValue *val = (GValue *)data;
    g_value_unset(val);
    g_free(val);
}

GHashTable *sms_create_features(const char * alphabet, int csm_num, int csm_seq)
{
    GHashTable *hash;
    GValue *val;

    hash = g_hash_table_new_full  (g_str_hash, NULL, NULL, release_val);

    val = g_new0(GValue, 1);
    g_value_init (val, G_TYPE_STRING);
    g_value_set_string (val, alphabet);
    g_hash_table_insert(hash, "alphabet", val);

    val = g_new0(GValue, 1);
    g_value_init (val, G_TYPE_INT);
    g_value_set_int (val, csm_num);
    g_hash_table_insert(hash, "csm_num", val);

    val = g_new0(GValue, 1);
    g_value_init (val, G_TYPE_INT);
    g_value_set_int (val, csm_seq);
    g_hash_table_insert(hash, "csm_seq", val);

    return hash;
}

GType sms_get_features_type(void)
{
    return dbus_g_type_get_map("GHashTable", G_TYPE_STRING, G_TYPE_VALUE);
}

void sms_show_features(GHashTable *features)
{
    GList *keys = g_hash_table_get_keys(features);
    gint len = g_list_length(keys);
    gint i;

    for (i = 0; i < len; i++) {
        gchar  *key = g_list_nth_data(keys, i);
        GValue *val = g_hash_table_lookup(features, key);

        g_print("%s=", key);
        switch (G_VALUE_TYPE(val)) {
        case G_TYPE_STRING:
            g_print("%s\n", g_value_get_string(val));
            break;
        case G_TYPE_INT:
            g_print("%d\n", g_value_get_int(val));
            break;
        default:
            g_print("Value is of unmanaged type!\n");
        }
    }

    g_list_free(keys);
}

void sms_release_features(GHashTable *features)
{
    g_hash_table_destroy(features);
}

sms_get_features_type調用dbus_g_type_get_map創建a{sv}類型。服務器在創建信號時用到。客戶端在調用方法和注冊信號時都會用到。 sms_create_features調用g_hash_table_new_full創建哈希表,在創建的同時登記了值對象的清理函數。 在sms_release_features調用g_hash_table_destroy銷毀哈希表時,創建時登記的值對象清理函數會被調用。

客戶端代碼

smsc.c

#include <dbus/dbus-glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib/giochannel.h>
#include "sms-marshal.h"
#include "sms_features.h"

#define SMSC_DEBUG

static void lose (const char *str, ...)
{
    va_list args;
    va_start (args, str);
    vfprintf (stderr, str, args);
    fputc ('\n', stderr);
    va_end (args);
    exit (1);
}

static void lose_gerror (const char *prefix, GError *error)
{
    if (error) {
        lose ("%s: %s", prefix, error->message);
    }
    else {
        lose ("%s", prefix);
    }
}

static void incoming_message_handler (DBusGProxy *proxy, const char *address, const char *contents, GHashTable *features, gpointer user_data)
{
    printf ("Received message with addree \"%s\" and it says: \n%s\n", address, contents);
    sms_show_features(features);
}

static void send_message(DBusGProxy *remote_object)
{
    GError *error = NULL;
    GHashTable *features;
    int ret;

    features = sms_create_features ("gsm", 8, 2);
    printf("SendMessage ");

    if (!dbus_g_proxy_call (remote_object, "SendMessage", &error,
        G_TYPE_STRING, "10987654321", G_TYPE_STRING, "hello world",
        sms_get_features_type(), features, G_TYPE_INVALID,
        G_TYPE_INT, &ret, G_TYPE_INVALID))
        lose_gerror ("Failed to complete SendMessage", error);

    printf("return %d\n", ret);
    sms_release_features(features);
}

static void shell_help(void)
{
    printf( "\ts\tsend message\n"
        "\tq\tQuit\n"
        );
}

#define STDIN_BUF_SIZE    1024
static gboolean channel_cb(GIOChannel *source, GIOCondition condition, gpointer data)
{
    int rc;
    char buf[STDIN_BUF_SIZE+1];
    DBusGProxy *remote_object = (DBusGProxy *)data;

    if (condition != G_IO_IN) {
        return TRUE;
    }

    /* we've received something on stdin.    */
    printf("# ");
    rc = fscanf(stdin, "%s", buf);
    if (rc <= 0) {
        printf("NULL\n");
        return TRUE;
    }

    if (!strcmp(buf, "h")) {
        shell_help();
    } else if (!strcmp(buf, "?")) {
        shell_help();
    } else if (!strcmp(buf, "s")) {
        send_message(remote_object);
    } else if (!strcmp(buf, "q")) {
        exit(0);
    } else {
        printf("Unknown command `%s'\n", buf);
    }
    return TRUE;
}

int main (int argc, char **argv)
{
    DBusGConnection *bus;
    DBusGProxy *remote_object;
    GError *error = NULL;
    GMainLoop *mainloop;
    GIOChannel *chan;
    guint source;
    GType features_type;

#ifdef SMSC_DEBUG
    g_slice_set_config(G_SLICE_CONFIG_ALWAYS_MALLOC, TRUE);
#endif
    g_type_init ();
    mainloop = g_main_loop_new (NULL, FALSE);

    bus = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
    if (!bus)
        lose_gerror ("Couldn't connect to session bus", error);

    remote_object = dbus_g_proxy_new_for_name (bus, "org.freesmartphone.ogsmd",
        "/org/freesmartphone/GSM/Device",
        "org.freesmartphone.GSM.SMS");
    if (!remote_object)
        lose_gerror ("Failed to get name owner", NULL);

    features_type = sms_get_features_type();
    dbus_g_object_register_marshaller (sms_marshal_VOID__STRING_STRING_BOXED, G_TYPE_NONE, G_TYPE_STRING, G_TYPE_STRING,
        features_type, G_TYPE_INVALID);
    dbus_g_proxy_add_signal (remote_object, "IncomingMessage", G_TYPE_STRING, G_TYPE_STRING, features_type, G_TYPE_INVALID);
    dbus_g_proxy_connect_signal (remote_object, "IncomingMessage", G_CALLBACK (incoming_message_handler), NULL, NULL);

    chan = g_io_channel_unix_new(0);
    source = g_io_add_watch(chan, G_IO_IN, channel_cb, remote_object);
    g_main_loop_run (mainloop);
    exit (0);
}

112行連接會話總線。116-118行在會話總線上獲取連接"org.freesmartphone.ogsmd"的對象"/org/freesmartphone/GSM/Device" 的接口"org.freesmartphone.GSM.SMS"的接口代理對象。

123行調用dbus_g_object_register_marshaller向dbus-glib登記列集函數。 125行調用dbus_g_proxy_add_signal增加對信號IncomingMessage的監聽。126行登記信號IncomingMessage的回調函數。 123行登記的還是我們用glib-genmarshal生成的函數sms_marshal_VOID__STRING_STRING_BOXED。 dbus-glib使用這個函數從signal消息中取出信號參數,傳遞給回調函數,即執行散集操作。 這說明glib-genmarshal生成的列集函數既可以用於列集,也可以用於散集。

客戶端程序同樣用IO Channel接受用戶輸入。129行在登記回調函數時將指向接口代理對象的指針作為參數傳入。 回調函數channel_cb在用戶鍵入's'命令后通過send_message函數調用org.freesmartphone.GSM.SMS接口對象的SendMessage方法。 107行的設置G_SLICE_CONFIG_ALWAYS_MALLOC同樣是為了用valgrind檢查內存泄漏。

執行

我們先運行 dbus-monitor,然后運行smss,再運行smsc。先在smsc中鍵入's'回車調用SendMessage方法。 然后在smss中鍵入's'回車發送IncomingMessage信號。然后在smsc中鍵入'q'回車退出。最后在smss中鍵入'q'回車退出。

./smss
service is running
number=10987654321
contents=hello world
csm_num=8
alphabet=gsm
csm_seq=2
h
#       s       send signal
        q       Quit
s
# q
./smsc
h
#       s       send message
        q       Quit
s
# SendMessage return 11
Received message with addree "12345678901" and it says:
hello signal!
csm_num=3
alphabet=ucs2
csm_seq=1
q

我們可以看到打印出來的信號和消息。對於同一件事情,不同的層次的觀察者會看到不同的細節,下表是dbus-monitor看到的東西:

smss連接會話總線。會話總線發NameOwnerChanged信號,通知唯一名":1.21"被分配。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.21"
   string ""
   string ":1.21"

smss向會話總線發送Hello取得自己的唯一名":1.21"。

method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello

smss調用AddMatch要求接收會話總線的NameOwnerChanged信號。

method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch
   string "type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"

smss調用AddMatch要求接收會話總線發送的所有信號。

method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch
   string "type='signal',sender='org.freedesktop.DBus',path='/',interface='org.freedesktop.DBus'"

smss調用GetNameOwner獲取連接"org.freedesktop.DBus"的唯一名。

method call sender=:1.21 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetNameOwner
   string "org.freedesktop.DBus"

會話總線發送NameOwnerChanged信號,通知唯一名為":1.21"的連接獲得了公眾名"org.freesmartphone.ogsmd"。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string "org.freesmartphone.ogsmd"
   string ""
   string ":1.21"

smss請求公眾名"org.freesmartphone.ogsmd"。分配公眾名在前,請求公眾名在后,應該是監控過程顛倒了消息次序。

method call sender=:1.21 -> dest=org.freedesktop.DBus path=/; interface=org.freedesktop.DBus; member=RequestName
   string "org.freesmartphone.ogsmd"
   uint32 0

smsc連接會話總線。會話總線發NameOwnerChanged信號,通知唯一名":1.22"被分配。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ""
   string ":1.22"

smss向會話總線發送Hello取得自己的唯一名":1.22"。

method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=Hello

smsc調用AddMatch要求接收會話總線的NameOwnerChanged信號。

method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch
   string "type='signal',sender='org.freedesktop.DBus',path='/org/freedesktop/DBus',interface='org.freedesktop.DBus',member='NameOwnerChanged'"

smsc調用AddMatch要求接收連接'org.freesmartphone.ogsmd'中對象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的信號。

method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=AddMatch
   string "type='signal',sender='org.freesmartphone.ogsmd',path='/org/freesmartphone/GSM/Device',interface='org.freesmartphone.GSM.SMS'"

smsc調用GetNameOwner獲取連接"org.freesmartphone.ogsmd"的唯一名。

method call sender=:1.22 -> dest=org.freedesktop.DBus path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=GetNameOwner
   string "org.freesmartphone.ogsmd"

smsc調用連接'org.freesmartphone.ogsmd'中對象'/org/freesmartphone/GSM/Device'的'org.freesmartphone.GSM.SMS'接口的SendMessage方法。

method call sender=:1.22 -> dest=org.freesmartphone.ogsmd path=/org/freesmartphone/GSM/Device; interface=org.freesmartphone.GSM.SMS; member=SendMessage
   string "10987654321"
   string "hello world"
   array [
      dict entry(
         string "csm_seq"
         variant int32 2
      )
      dict entry(
         string "alphabet"
         variant string "gsm"
      )
      dict entry(
         string "csm_num"
         variant int32 8
      )
   ]

smss向smsc發送method return消息,返回SendMessage方法的輸出參數。

method return sender=:1.21 -> dest=:1.22 reply_serial=5
   int32 11

smss發送IncomingMessage信號。

signal sender=:1.21 -> dest=(null destination) path=/org/freesmartphone/GSM/Device; interface=org.freesmartphone.GSM.SMS; member=IncomingMessage
   string "12345678901"
   string "hello signal!"
   array [
      dict entry(
         string "csm_seq"
         variant int32 1
      )
      dict entry(
         string "alphabet"
         variant string "ucs2"
      )
      dict entry(
         string "csm_num"
         variant int32 3
      )
   ]

會話總線通知連接":1.22",即smsc的連接已經切斷。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.22"
   string ":1.22"
   string ""

會話總線通知擁有公共名"org.freesmartphone.ogsmd"的連接已經切斷。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string "org.freesmartphone.ogsmd"
   string ":1.21"
   string ""

會話總線通知擁有唯一名":1.21"的連接已經切斷。即smss已經終止。

signal sender=org.freedesktop.DBus -> dest=(null destination) path=/org/freedesktop/DBus; interface=org.freedesktop.DBus; member=NameOwnerChanged
   string ":1.21"
   string ":1.21"
   string ""

工程

我提供下載的文件要用make distcheck制作的,其中包含了一些自動生成的文件。 執行./clean.sh可以刪掉自動生成的文件,只留下我創建的文件:

find . -type f
./clean.sh
./Makefile.am
./autogen.sh
./src/gsm_sms.h
./src/Makefile.am
./src/sms-marshal.list
./src/smss.xml
./src/smss.c
./src/gsm_sms.c
./src/sms_features.h
./src/sms_features.c
./src/smsc.c
./configure.ac

前面已經介紹過所有的源文件。我們再看看工程文件:

cat autogen.sh
#! /bin/sh
touch `find .`
aclocal
autoconf
autoheader
touch NEWS README AUTHORS ChangeLog
automake --add-missing

cat Makefile.am
SUBDIRS = src
EXTRA_DIST = autogen.sh clean.sh

autogen.sh建立工程環境。在執行clean.sh后,執行autogen.sh重新生成configure等工程文件。 其中的touch命令是為了防止文件有將來的時間戳。因為我在虛擬機中運行ubuntu,所以可能會出現這類問題。 Makefile.am將autogen.sh clean.sh也作為發布文件。最重要的工程文件是"configure.ac"和"src/Makefile.am"。

configure.ac

configure.ac

AC_INIT()
AM_INIT_AUTOMAKE(hello-dbus5, 0.1)
AM_CONFIG_HEADER(config.h)

AC_PROG_CC

# Dbus detection
PKG_CHECK_MODULES(DBUS, dbus-1 >= 1.1, have_dbus=yes, have_dbus=no)

if test x$have_dbus = xno ; then
    AC_MSG_ERROR([DBus development libraries not found])
fi
AM_CONDITIONAL(HAVE_DBUS, test x$have_dbus = xyes)

AC_SUBST(DBUS_CFLAGS)
AC_SUBST(DBUS_LIBS)


# Glib detection
PKG_CHECK_MODULES(DBUS_GLIB, gobject-2.0 >= 2.6, have_glib=yes, have_glib=no)

if test x$have_glib = xno ; then
    AC_MSG_ERROR([GLib development libraries not found])
fi

AM_CONDITIONAL(HAVE_GLIB, test x$have_glib = xyes)

AC_SUBST(DBUS_GLIB_CFLAGS)
AC_SUBST(DBUS_GLIB_LIBS)


AC_OUTPUT([Makefile
            src/Makefile])

8-17行檢查dbus庫,它們會生成編譯常數DBUS_CFLAGS和DBUS_LIBS。 20-30行檢查dbus-glib庫,它們會生成編譯常數DBUS_GLIB_CFLAGS和DBUS_GLIB_LIBS。

src/Makefile.am

src/Makefile.am

INCLUDES = \
        $(DBUS_CFLAGS)                          \
        $(DBUS_GLIB_CFLAGS)                     \
        -DDBUS_COMPILATION

LIBS = \
        $(DBUS_LIBS)                            \
        $(DBUS_GLIB_LIBS)                       \
        -ldbus-glib-1

# smss
noinst_PROGRAMS = smss

BUILT_SOURCES = smss-glue.h sms-marshal.h sms-marshal.c
smss_SOURCES = $(BUILT_SOURCES) smss.c gsm_sms.c sms_features.c
noinst_HEADERS = gsm_sms.h sms_features.h


smss-glue.h: smss.xml
        $(LIBTOOL) --mode=execute dbus-binding-tool --prefix=gsm_sms --mode=glib-server --output=smss-glue.h $(srcdir)/smss.xml

sms-marshal.h: sms-marshal.list
        $(LIBTOOL) --mode=execute glib-genmarshal --header sms-marshal.list --prefix=sms_marshal > sms-marshal.h

sms-marshal.c: sms-marshal.list
        $(LIBTOOL) --mode=execute glib-genmarshal --body sms-marshal.list --prefix=sms_marshal > sms-marshal.c

CLEANFILES = $(BUILT_SOURCES)

EXTRA_DIST = smss.xml sms-marshal.list

# smss
noinst_PROGRAMS += smsc
smsc_SOURCES= smsc.c sms-marshal.c sms_features.c

19-20行由接口描述文件smss.xml生成存根文件smss-glue.h。22-26行由列集接口定義生成包含列集函數的代碼。

結束語

本文介紹了一個簡單的dbus-glib的例子,包括服務器和客戶端。第一講中還有一個加法例子,如果你理解了本文的例子,那個例子就更簡單了。 dbus-glib源代碼中有兩個例子:

  • example-service和example-client演示方法調用。這個例子的接口描述文件中有個參數類型寫錯了,將(us)寫成(ss),運行時會出錯。 可能作者想演示一下接口定義與代碼實現不一致的后果吧。讀者可以從這里下載我修改過的代碼。
  • example-signal-emitter和example-signal-recipient演示信號發射。這個例子中,example-signal-recipient調用example-signal-emitter的方法請求發送信號。 實際上信號應該是來自服務器側的信息。我將其改成在example-signal-emitter中敲鍵發送信號。讀者可以從這里下載我修改過的代碼。

好了,《dbus實例講解》到此結束。其實我的所有文章只是希望能讓這復雜的世界簡單一點。

參考鏈接:
https://www.e-learn.cn/topic/1808992


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM