D-BUS學習筆記
我第一個看到的文章是ali's Blog [1], 其中提到二支程式server.c client.c,我試著去compile這二支程式, 沒什麼太大的問題就可以成功。因為這是所謂的dbus c api, 也就是low lever api,在ubuntu下只要安裝套件libdbus-1-dev, 就會安裝這個api:
建一個檔案,其名為Makefile,內容如下:
也許時間已久,文章中所提這二支程式的出處,我找了許久,看來連結已經不見!所以對程式的說明也沒有了。還好程式很簡短,大致上可以看出來,server.c的部份其實只是在bus上附加一個監聽signal的接收功能,並篩選想要的signal,收到時印出其帶來的參數。而client.c 則只是送出一個符合其條件的signal而已。
所以來看看第二篇文章http://www.matthew.ath.cx/misc/dbus[2],對於dbus c api有較多的範例程式。我在試他的範例(dbus-example.c)時,碰到了一個以前沒注意的問題,就是System Bus的使用,必須有授權,在網路上也看到別人在試這篇文章的程式有相同的問題。別人的回答是不要用System Bus,改用Session Bus就可以了。果然也是如此,但我想試試如何授權讓程式可以使用System Bus。所以就花點時間研究一下,發現在/etc/dbus-1目錄下有兩個設定檔:
這樣一來, 原本會出現的錯誤, 就消失了:
同樣分別在兩個terminal中執行:
我在試時,發現方法叫用必須明確的用allow send_destination, allow receive_sender在設定檔中授權,否則不會成功。但是signal只要在設定檔中做完allow owner的部份就可以正常收送了。
我還注意到兩個情況:
不管是以上那一篇文章的範例程式,都是很瑣碎,在撰寫時思考的是如何去組成訊息及其參數。或者是接收到訊息後,要如何解開,並取得其帶來的參數。建議先了解D-BUS訊息運作的基本原理(可看get on the dbus[4])後,再來看範例。
使用這個dbus c api是很低階的沒錯,每一個小操作,例如送一個signal,就要寫好多程式碼。所以官方在文件中也都建議使用binding,即較高階的語言或開發工具提供的較高階的程式庫來使用。這樣可以讓開發者專注心力在服務的內容開發,而不用分心去思考低階的訊息傳送接收問題。也因為這樣,我們會注意到,低階的寫法,思考不太相同!用d-feet來查看範例,都看不到提供了什麼方法:
當然,範例還是可以正常的執行啦。不過,顯然有一些工作沒有做(可能是Introspect方法沒實作),而這些工作要做也不少,但又無關於功能。所以使用binding,它就會幫你做了。
dbus_g_bus_get() 取得至dbus的連線
dbus_g_proxy_new_for_name() 對應遠端物件介面至proxy物件
dbus_g_proxy_call() 利用proxy物件叫用遠端物件的方法
看起來,是比使用原生的dbus c api要好了一些。不過,我在試這支程式時,發現在d-feet裡一直找不到它所使用的物件,這可是奇了,是d-feet不太行,還是這是個特别的服務呢?後記:之後在另一台ubuntu 13.04試時,發現在d-feet裡可以看到程式要叫用的方法。如Fig.3, 查看了一下,發現ubuntu 13.04的d-feet為0.3.3-1是比原本試的那台ubuntu 12.10的d-feet版本新。(不過,我只看到在連接org.freedesktop.DBus下有一個物件/,而其實程式中叫用的是物件/org/freedesktop/DBus)
我想在此先談一下名詞和基本概念:除了前面提到的get on the dbus[4]外, tuxpool dbus-tutorial[7], fmddlmyy[8],都是很好的入門文章。fmddlmyy是大陸朋友寫的,個人覺得寫的很好,深入淺出值得推薦,不過簡體字對我們是個問題,特别是專有名詞方面。剛好接下來,我會介紹一個語言vala,也正好有大陸朋友做了一個不錯的文件翻譯vala introduce[9],其中正好有英文與簡體字的對照。大家對照著看,或丢到Word去做簡繁轉換及專有名稱替換,之後再印出來讀,會好一點。下面簡單整理一下重要名詞及其相當用詞。
我個人對這些名詞和其之間關係的理解,畫成如上Fig.4,不過,詳細的説明和介紹,還是請參看以上提到的文章,當然還有很多寫得不錯的文章,可以google到。
dbus-tutorial[5]雖然介紹了基本的dbus-glib程式寫法(該文有一半在講解如何寫glib程式),但是只有一個簡單的客戶端程式範例和一些零散的程式片段,其實並不容易上手。石頭閒語[10]則提到比較實用的作法是去使用DBusGlibBindings。正如石頭成先生所說,DBusGlibBindings這個源碼,其實是個比較完整的架構,借用其工具,我們只要修改其中program.c和XML檔就可以産生自訂的服務。
但是當我連入這個網頁時,卻看到其警語:「Note: The example code is outdated. It needs to be converted from dbus-glib to GIO's gdbus.」如Fig.5所示,這也是沒錯啦,dbus-glib是已經有點過時了,看來現在是比較推GIO's gdbus。再往下看,我注意到他們推另一個方式,用vala來讓一切更容易一點。我看了這個網頁並且試了一下範例程式,真得簡單很多。vala是一個看來很有意思的語言,基本上語法是大量借用C#,所以玩過微軟的.NET就會覺得很熟悉。建議自己去試一下範例程式,網頁上也教了編譯的方法,很容易就上手。我試了一下,用dbus c api寫的客戶端程式去叫用vala寫的服務也是可以運作的。
以下比較一下客戶端寫法,在叫用同一個服務的方法時,所需撰寫程式的不同:
1. dbus c api
以下整理各個範例程式,以供有興趣的朋友測試,請注意程式來源皆在前文已提及,在此只是加入Makefile以方便編譯,目前只有在ubuntu Linux 12.10及13.04測試過可用。
DBus C API simple server and client for signal: dbus-test
DBus C API Matthew's sample code: dbus-example
dbus-glib sample code: dbus-glib
GLib/GIO GDbus API vala sample: vala-dbus
python dbus sample client to call vala sample's service: demo-py
前面談到用vala來寫server端或服務會比較容易,後來看到用python來寫服務也很容易(似乎更容易一點)可參考python dbus[11]這一篇:
它介紹到兩支程式,一支短一點,另一支長一點:
第一支程式跑起來後,用d-feet看起來如下圖(Fig.6):
就只是提供兩個method,一個是HelloWorld(),叫用它時你可以提供一個字串參數,它會被印在Terminal,同時它會返回一個字串陣列。如下圖(Fig.7)。當然用d-feet來叫用method會比較輕鬆,只要點選就好了。我們也可以下dbus-send指令:
請注意返回值中最後一個字串是服務的唯一名。我這個版本的d-feet似乎有個小bug,就是無法正確顯示每個服務的唯一名,不論你切到那一個服務,它顯示的唯一名都一樣。反而舊版的d-feet可以正確顯示!
另一個method是Exit(),叫用這個method就會結束這支程式(或此服務),但是我直接在d-feet中叫用,好像會造成d-feet有點問題!不過,應該是d-feet的bug,不是DBus的問題。
第二支程式稍為長了一點,但也展示了較多的寫法:首先它在服務端多了一個signal,程式展示如何設置signal,以及如何在需要時觸發這個signal。另外寫了一個類別會去註冊要接收這個signal,當收到時會去叫用一個叫做handler()的method,此方法會列印出一些訊息。它還公開另兩個方法,一個是Say(),一個是Stop()。Stop()和前一支程式的Exit()一樣,就是用來結束自己的服務。Say()有點意思,它其實做了三件事:
叫用Say()的效果如Fig.9,螢幕右上角會出現快顯提示,而左下角可以看到服務本身的signal接收器也收到這個signal的發生,而且做了動作。
是不是感覺用python寫dbus服務真的容易許多。
參考來源:
[1] lazycat, D-Bus, Ali's Blog, 2012/3/5
[2] Matthew Johnson, Using the DBUS C API
[3] Rex Tsai, 基本的DBus偵錯技巧, Rex's blah blah blah, 2011/3/8
[4] Robert Love, Get on the D-Bus, Linux Journal, 2005/1/5
[5] Pennington et al., D-Bus Tutorial
[6] tvuillemin, How to compile a basic D-Bus/glib example, stackoverflow, 2013/1/10
[7] Michael, dbus tutorial - part 1, TuxPool, 2010/4/23
[8] fmddlmyy, dbus實例講解, fmddlmyy的专栏, 2008/12/23
[9] I'm Matrix, 【原创翻译】Vala编程手册, ubuntu forum, 2013/2/8
[10] 石頭成, dbus-glib bindings入門磚, 石頭閒語, 2010/7/12
[11] 石頭成, Python DBus教學精要, 石頭閒語, 2011/4/14
[12] Pennington et al., D-Bus Specification 0.19, 2012/2/20
$ sudo apt-get install libdbus-1-dev然後就如該文所提到的,在compile時,必須指定library:
$ gcc server.c -o server -l dbus-1此時再去compile程式,可能會發現找不到dbus.h的問題。其實檔案是存在的,只是gcc不知道去那裡找而已。我們只要加入-I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include 告訴gcc就可以了。
$ gcc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -o server server.c -l dbus-1如此,可以compile成功。但每次要打這麼多字,實在記不得,也容易打錯,所以使用Makefile吧!
建一個檔案,其名為Makefile,內容如下:
All: server client server: server.c gcc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -o server server.c -l dbus-1 client: client.c gcc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -o client client.c -l dbus-1我們把server.c client.c Makefile三個檔案,都放在同一個目錄,叫做dbus-test好了。這樣以後只要:
$ cd dbus-test $ make就會正確的編出server client二支可執行的程式。我們也不用去記這些很長的路徑名稱了。用過Makefile的朋友應該看得出來,其實Makefile可以再簡短一些。改成如下:
All: server client %: %.c gcc -I/usr/include/dbus-1.0 -I/usr/lib/dbus-1.0/include -o $@ $< -l dbus-1其中gcc這一行其實可以再改得更好一點,如果有用pkg-config套件的話,這一行可以改為:
$(CC) $(shell pkg-config --cflags dbus-1) -o $@ $< $(shell pkg-config --libs dbus-1)當然,要記得在檔案前定義CC,例如“CC=gcc"。(其實也可以省略,因為是預設定義)
也許時間已久,文章中所提這二支程式的出處,我找了許久,看來連結已經不見!所以對程式的說明也沒有了。還好程式很簡短,大致上可以看出來,server.c的部份其實只是在bus上附加一個監聽signal的接收功能,並篩選想要的signal,收到時印出其帶來的參數。而client.c 則只是送出一個符合其條件的signal而已。
所以來看看第二篇文章http://www.matthew.ath.cx/misc/dbus[2],對於dbus c api有較多的範例程式。我在試他的範例(dbus-example.c)時,碰到了一個以前沒注意的問題,就是System Bus的使用,必須有授權,在網路上也看到別人在試這篇文章的程式有相同的問題。別人的回答是不要用System Bus,改用Session Bus就可以了。果然也是如此,但我想試試如何授權讓程式可以使用System Bus。所以就花點時間研究一下,發現在/etc/dbus-1目錄下有兩個設定檔:
- system.conf
- session.conf
- system.d
- session.d
$ cd /etc/dbus-1/system.d $ sudo cp avahi-dbus.conf dbus-example.conf $ sudo vi dbus-example.conf將內容改成:
Fig. 1 |
$ ./dbus-example listen Listening for method calls Name Error (Connection ":1.141" is not allowed to own the service "test.method.server" due to security policies in the configuration file) Not Primary Owner (-1)變成
$ ./dbus-example listen Listening for method calls
所以有兩個方法可以來設定dbus系統滙流排授權: 1.使用/etc/dbus-1/目錄下的system.conf或者複製改名為system-local.conf再修改此檔。 2.另外可以在/etc/dbus-1/system.d/目錄下自己新增任意檔名.conf的設定檔來做,如我上面所提的方式。 兩者的差別在方法一必須重啟dbus服務,而方法二並不需要重啟服務。這支範例程式的玩法有兩個:收送signal,及回應與叫用方法。分別在兩個terminal中執行:
$ ./dbus-example listen $ ./dbus-example query hello可以看到叫用方法並帶一個字串的參數,遠端程序可以收到這個字串參數,並印出來。而叫用端可以看到方法叫用後收到的回應(回傳值),而回應本身可以帶多個參數(此範例為兩個參數)。
同樣分別在兩個terminal中執行:
$ ./dbus-example receive $ ./dbus-example send hello可以看到送signal時帶一個參數,遠端程序可以收到這個字串參數,同樣可以正確的印出來。
我在試時,發現方法叫用必須明確的用allow send_destination, allow receive_sender在設定檔中授權,否則不會成功。但是signal只要在設定檔中做完allow owner的部份就可以正常收送了。
我還注意到兩個情況:
- 和使用session bus的程式相比,system bus的訊息傳送速度明顯會慢一點
- 使用dbus-monitor --system來監看,可以看到signal的傳送,但看不到方法的傳送
cat > /etc/dbus-1/system-local.conf <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> <busconfig> <policy context="default"> <!-- All messages may be received by default --> <allow receive_requested_reply="false" receive_type="method_call" eavesdrop="true"/> <allow receive_requested_reply="false" receive_type="method_return" eavesdrop="true"/> <allow receive_requested_reply="false" receive_type="error" eavesdrop="true"/> <allow receive_requested_reply="false" receive_type="signal" eavesdrop="true"/> <allow eavesdrop="true"/> </policy> <policy user="root"> <allow send_destination="*" eavesdrop="true"/> <allow receive_sender="*" eavesdrop="true"/> </policy> </busconfig>另外,程式在ubuntu 10.04可以正常執行,在ubuntu 12.10就有狀況: reply_to_method_call()函數在叫用dbus_message_iter_append_basic()時會出問題。
$ ./dbus-example listen Listening for method calls Method called with hello process 6215: arguments to dbus_message_iter_append_basic() were incorrect, assertion "*bool_p == 0 || *bool_p == 1" failed in file ../../dbus/dbus-message.c line 2613. This is normally a bug in some application using the D-Bus library.看了一下dbus-message.c的程式,看來也只是檢查傳入參數是否正確而已。試著把stat變數由bool型別改成dbus_bool_t,並將其值設為TRUE,再重新編譯,就可以了。另外,也許dbus有更新的關係,範例程式在send, query時都會列出程式有bug的訊息,説不可以關閉連線。所以這兩個動作的函數最後,我都將關閉連線的程式碼註解掉。//dbus_connection_close(conn);
不管是以上那一篇文章的範例程式,都是很瑣碎,在撰寫時思考的是如何去組成訊息及其參數。或者是接收到訊息後,要如何解開,並取得其帶來的參數。建議先了解D-BUS訊息運作的基本原理(可看get on the dbus[4])後,再來看範例。
使用這個dbus c api是很低階的沒錯,每一個小操作,例如送一個signal,就要寫好多程式碼。所以官方在文件中也都建議使用binding,即較高階的語言或開發工具提供的較高階的程式庫來使用。這樣可以讓開發者專注心力在服務的內容開發,而不用分心去思考低階的訊息傳送接收問題。也因為這樣,我們會注意到,低階的寫法,思考不太相同!用d-feet來查看範例,都看不到提供了什麼方法:
Fig. 2 |
後記:為了證實我的憶測是否正確,我稍加修改dbus-example.c這支由Matthew[2]撰寫的範例程式,除了原有的test.method.Type.Method外,加入DBus規格書[12]中所規定的標準介面 org.freedesktop.DBus.Introspectable.Introspect。並依規格傳回XML格式的字串(如Fig.10所示),在Terminal下./dbus-example listen命令後,果然d-feet工具就可以看到範例程式所提供的方法了(如Fig.11所示)。而且,在d-feet中直接按Method兩下,也可以正確的執行並得到預期的回傳值(如Fig.12)。修改後的程式碼可在此下載。接著dbus-tutorial[5] 是眾人推薦要讀的,其中第一個範例程式介紹了在C語言中使用GLib程式庫來存取DBUS的程式寫法。同樣的又是不知道如何編譯,網路上有人問了,也有人答了how-to-compile-a-basic-d-bus-glib-example[6] 所以請自行參看,主要是有些套件要裝外,編譯的連結參數-ldbus-glib-1也要下對。這支範例程式主要使用glib的三個函數:
Fig.10
Fig.11
Fig.12
dbus_g_bus_get() 取得至dbus的連線
dbus_g_proxy_new_for_name() 對應遠端物件介面至proxy物件
dbus_g_proxy_call() 利用proxy物件叫用遠端物件的方法
看起來,是比使用原生的dbus c api要好了一些。不過,我在試這支程式時,發現在d-feet裡一直找不到它所使用的物件,這可是奇了,是d-feet不太行,還是這是個特别的服務呢?後記:之後在另一台ubuntu 13.04試時,發現在d-feet裡可以看到程式要叫用的方法。如Fig.3, 查看了一下,發現ubuntu 13.04的d-feet為0.3.3-1是比原本試的那台ubuntu 12.10的d-feet版本新。(不過,我只看到在連接org.freedesktop.DBus下有一個物件/,而其實程式中叫用的是物件/org/freedesktop/DBus)
Fig.3 |
我想在此先談一下名詞和基本概念:除了前面提到的get on the dbus[4]外, tuxpool dbus-tutorial[7], fmddlmyy[8],都是很好的入門文章。fmddlmyy是大陸朋友寫的,個人覺得寫的很好,深入淺出值得推薦,不過簡體字對我們是個問題,特别是專有名詞方面。剛好接下來,我會介紹一個語言vala,也正好有大陸朋友做了一個不錯的文件翻譯vala introduce[9],其中正好有英文與簡體字的對照。大家對照著看,或丢到Word去做簡繁轉換及專有名稱替換,之後再印出來讀,會好一點。下面簡單整理一下重要名詞及其相當用詞。
- bus
- connection = bus name(d-feet) = service
- object = object path = path name
- interface
- methods and signals
Fig. 4 |
dbus-tutorial[5]雖然介紹了基本的dbus-glib程式寫法(該文有一半在講解如何寫glib程式),但是只有一個簡單的客戶端程式範例和一些零散的程式片段,其實並不容易上手。石頭閒語[10]則提到比較實用的作法是去使用DBusGlibBindings。正如石頭成先生所說,DBusGlibBindings這個源碼,其實是個比較完整的架構,借用其工具,我們只要修改其中program.c和XML檔就可以産生自訂的服務。
Fig.5 |
以下比較一下客戶端寫法,在叫用同一個服務的方法時,所需撰寫程式的不同:
1. dbus c api
dbmsg = dbus_message_new_method_call("org.example.Demo", // connection "/org/example/demo", // object "org.example.Demo", // interface "Ping"); // method if (!dbmsg) { return -1; } word = "hello world"; if (!dbus_message_append_args(dbmsg, DBUS_TYPE_STRING, &word, DBUS_TYPE_INVALID)) { return -1; } // send message and wait for reply, -1 means wait forever reply = dbus_connection_send_with_reply_and_block(dbconn, dbmsg, -1, NULL); if (!reply) { return -1; } // read the parameters dbus_message_iter_init(reply, &iter); dbus_message_iter_get_basic(&iter, &ri); dbus_connection_flush(dbconn); printf("return value: %d\n", ri); dbus_message_unref(dbmsg); dbus_message_unref(reply);2. dbus-glib
proxy = dbus_g_proxy_new_for_name (connection, "org.example.Demo", "/org/example/demo", "org.example.Demo"); dbus_g_proxy_call (proxy, "Ping", &error, G_TYPE_INVALID, G_TYPE_INT, &ri, G_TYPE_INVALID); g_print ("return value: %d\n", ri);3. vala
demo = Bus.get_proxy_sync (BusType.SESSION, "org.example.Demo", "/org/example/demo"); int reply = demo.ping ("Hello from Vala"); stdout.printf ("%d\n", reply);4. python
bus = dbus.SessionBus() obj = bus.get_object('org.example.Demo', '/org/example/demo') iface = dbus.Interface(obj, 'org.example.Demo') ri = iface.Ping('Hello') print ri很明顯的,這是進化。由低階到高階的進步,光由程式碼的行數就可以看出來,用vala寫的程式較少,而且其語法較簡潔自然。和用python寫的程式相比,甚至更短了一些。不過兩者都已經很簡潔了,一個是編譯式,一個是直譯式,各有其用途啦。
以下整理各個範例程式,以供有興趣的朋友測試,請注意程式來源皆在前文已提及,在此只是加入Makefile以方便編譯,目前只有在ubuntu Linux 12.10及13.04測試過可用。
DBus C API simple server and client for signal: dbus-test
DBus C API Matthew's sample code: dbus-example
dbus-glib sample code: dbus-glib
GLib/GIO GDbus API vala sample: vala-dbus
python dbus sample client to call vala sample's service: demo-py
前面談到用vala來寫server端或服務會比較容易,後來看到用python來寫服務也很容易(似乎更容易一點)可參考python dbus[11]這一篇:
它介紹到兩支程式,一支短一點,另一支長一點:
第一支程式跑起來後,用d-feet看起來如下圖(Fig.6):
Fig.6 |
$ dbus-send --print-reply --dest=com.example.SampleService /SomeObject com.example.SampleInterface.HelloWorld string:hello method return sender=:1.113 -> dest=:1.117 reply_serial=2 array [ string "Hello" string " from example-service.py" string "with unique name" string ":1.113" ]
Fig.7 |
另一個method是Exit(),叫用這個method就會結束這支程式(或此服務),但是我直接在d-feet中叫用,好像會造成d-feet有點問題!不過,應該是d-feet的bug,不是DBus的問題。
第二支程式稍為長了一點,但也展示了較多的寫法:首先它在服務端多了一個signal,程式展示如何設置signal,以及如何在需要時觸發這個signal。另外寫了一個類別會去註冊要接收這個signal,當收到時會去叫用一個叫做handler()的method,此方法會列印出一些訊息。它還公開另兩個方法,一個是Say(),一個是Stop()。Stop()和前一支程式的Exit()一樣,就是用來結束自己的服務。Say()有點意思,它其實做了三件事:
- 叫用另一個DBus服務的方法,以便在螢幕上快顯一個訊息提示。
- 觸發它唯一的signal
- 返回一個字串
Fig.8 |
Fig.9 |
是不是感覺用python寫dbus服務真的容易許多。
參考來源:
[1] lazycat, D-Bus, Ali's Blog, 2012/3/5
[2] Matthew Johnson, Using the DBUS C API
[3] Rex Tsai, 基本的DBus偵錯技巧, Rex's blah blah blah, 2011/3/8
[4] Robert Love, Get on the D-Bus, Linux Journal, 2005/1/5
[5] Pennington et al., D-Bus Tutorial
[6] tvuillemin, How to compile a basic D-Bus/glib example, stackoverflow, 2013/1/10
[7] Michael, dbus tutorial - part 1, TuxPool, 2010/4/23
[8] fmddlmyy, dbus實例講解, fmddlmyy的专栏, 2008/12/23
[9] I'm Matrix, 【原创翻译】Vala编程手册, ubuntu forum, 2013/2/8
[10] 石頭成, dbus-glib bindings入門磚, 石頭閒語, 2010/7/12
[11] 石頭成, Python DBus教學精要, 石頭閒語, 2011/4/14
[12] Pennington et al., D-Bus Specification 0.19, 2012/2/20
留言