菜鳥學dbus-python,翻譯dbus-python指南,錯誤之處請在所難免,請諸位不吝賜教,多多指正!查看英文原版請點這里。
- 連接總線Connecting to the Bus
- 方法調用Making method calls
- 代理對象proxy objects
- 接口和方法Interfaces and methods
- See also
- 數據類型Data types
- 基本類型Basic types
- 基本類型轉換Basic types conversions
- 容器類型Container types
- 返回值和byte_arrays和utf8_strings選項
- 異步方法調用Making asynchronous method calls
- 設置一個事件循環Setting up an event loop
- 向后兼容dbusglib
- Qt主循環
- 異步調用Making asynchronous calls
- See also
- 設置一個事件循環Setting up an event loop
- 接收信號Receiving signals
- 信號匹配Signal matching
- 從一個信號獲取更多信息Getting more information from a signal
- 字符串參數匹配String argument matching
- 從代理對象接收信號Receiving signals from a proxy object
- See also
- Exporting 對象Exporting objects
- 從dbusservicesObject繼承
- 使用dbusservicemethod Exporting方法Exporting methods with dbusservicemethod
- 找出調用者的bus nameFinding out the callers bus name
- 異步方法實現Asynchronous method implementations
- 使用dbusservicesignal發送信號Emitting signals with dbusservicesignal
- 例子Example
- Licence for this document
連接總線(Connecting to the Bus)
使用D-Bus的應用程序通常都會連接到一個總線服務(bus daemon)上,這個總線服務在程序之間傳遞消息。為了使用D-Bus,你需要創建一個Bus對象來代表到總線服務的連接。
一般來說,我們感興趣的總線(bus)服務有2種:會話總線和系統總線。
每個登陸的用戶所屬的會話都應該有一個會話總線(session bus),會話總線只在本會話內有效。它用于桌面程序間的通信。可以通過創建一個SessionBus的對象連接到會話總線:
import dbus
session_bus = dbus.SessionBus()
系統總線是全局的,通常在系統啟動的時候就被加載了;它用于與系統服務通信,例如udev, NetworkManager和Hardware Abstraction Layer daemon(hald)。可以通過創建一個SystemBus對象來連接到系統總線。
import dbus
system_bus = dbus.SystemBus()
當然,你可以在程序里兩個總線都連接。
在一些特殊的情況下,你可能不使用默認的總線(Bus),或者根本就不連接到總線上,這些可以通過dbus-python 0.81.0中新添加的API來實現。但是這里不做介紹,他們是別的指南中的一個主題。
方法調用(Making method calls)
D-Bus應用程序可以export對象給其他應用程序使用。為了使用其他應用程序提供的對象,你需要知道:
- 總線名(bus name)。總線名用來標識你想與哪個應用程序通信。我們通常使用一個眾所周知的名字(well-known name)來識別一個程序。這個眾所周知的名字是一個反域名的點分式字符串,例如org.freedesktop.NetworkManager或者com.example.WordProcessor。
對象路徑(object path)。應用程序可以export很多對象。例如example.com的文檔處理程序可能提供一個對象代表文檔處理程序自身,并且為每個打開的文檔窗口創建一個對象,還可能為文檔中的每一個段落創建一個對象。
為了標識要與哪一個對象通信,你可以使用對象路徑來指定。對象路徑是一個由反斜線作為分隔的字符串,類似Linux系統下的文件名。例如,example.com的文檔處理程序可能提供一個對象路徑為/的對象代表程序自身,還可能提供對象路徑為/documents/123和/documents/345的對象代表打開的文檔窗口。
正如你所期望的那樣,你對遠端對象做的最主要的事情就是調用它們的方法。在Python中,方法可以有參數,同時它們可能返回一個或更多的返回值。代理對象(proxy objects)
為了與一個遠端的對象通信,我們使用代理對象。代理對象是一個Python對象,它是遠端對象的一個代理或替身-當調用代理對象的一個方法時,會導致dbus-python對遠端對象的方法調用,并將遠端對象的返回值作為代理對象方法調用的返回值。
可以對Bus調用get_object方法來獲取代理對象。例如,NetworkManager有一個眾所周知的名稱org.freedesktop.NetworkManager, NetworkManager export一個對象路徑為/org/freedesktop/NetworkManager的對象。NetworkManager為每一個網絡接口(network interface)都在對象路徑下創建一個對象,例如,/org/freedesktop/NetworkManager/Devices/eth0。你可以像這樣獲取一個代理對象來代理eth0:import dbus bus = dbus.SystemBus() proxy = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0')#proxy 是一個dbus.proxies.ProxyObject類型的對象
接口和方法(Interfaces and methods)
D-Bus使用接口(interfaces)給方法(methods)提供命名空間的機制。一個接口(interface)就是一組相關的方法和信號(后面再介紹信號),接口名字是一個反域名的點分式字符串。例如,每一個代表一個網絡接口的NetworkManager對象都實現了接口org.freedesktop.NetworkManager.Devices,在這個接口中有一個方法是gerProperties。
要調用一個方法,就在代理對象上調用相同的方法,并通過dbus_interface關鍵字參數將接口名傳入:import dbus bus = dbus.SystemBus() eth0 = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0') props = eth0.getProperties(dbus_interface='org.freedesktop.NetworkManager.Devices')#props 是一個屬性元組,元組的第一項是對象路徑。
簡便起見,如果你要在同一個接口上調用多個方法時,可以先構造一個dbus.Interface的對象,通過這個對象調用方法,這樣就可以避免每次調用方法都要傳入接口名的麻煩:
import dbus bus = dbus.SystemBus() eth0 = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Devices/eth0') eth0_dev_iface = dbus.Interface(eth0, dbus_interface='org.freedesktop.NetworkManager.Devices') props = dbus_dev_iface.getProperties()#props 同樣是一個屬性元組,元組的第一項是對象路徑。
See also
可以參考examples/example-client.py中的例子。在運行它之前,你需要在后臺或者另一個shell上運行examples/example-service.py
數據類型(Data types)
與Python不同,D-Bus使用靜態類型-每個方法都有一個標識來代表其參數的類型,其他類型的參數是不被接受的。
D-Bus有一個內省機制(introspection mechanism),dbus-python通過這個機制來嘗試發現正確的參數類型。如果嘗試成功,Python類型會被自動轉化為正確的D-Bus數據類型,如果可以;如果類型不恰當,會報TypeError錯誤。
如果內省失敗(或者參數類型可變-詳見下文),你必須提供正確的參數類型。dbus-python根據D-Bus的數據類型提供了相應的Python類型,一些Python類型可以自動的轉化為D-Bus數據類型。如果你使用這些類型之外的類型時,會TypError出錯,提示你dbus-python不能猜出D-Bus的標識。
基本類型(Basic types)
以下是支持的基本數據類型。
Python types | converted to D-Bus tyep | notes |
---|---|---|
D-Bus proxy object | ObjectPath(signature’o’) | (+) |
dbus.Interface | ObjectPath(signature’o’) | (+) |
dbus.service.service.Object | ObjectPath(signature’o’) | (+) |
dbus.Boolean | Boolean(signature’b’) | a subclass of int |
dbus.Byte | byte(signature’y’) | a subclass of int |
dbus.Int16 | 16-bit signed integer(‘n’) | a subclass of int |
dbus.Int32 | 32-bit signed integer(‘i’) | a subclass of int |
dbus.Int64 | 64-bit signed integer(‘x’) | (*) |
dbus.UInt16 | 16-bit unsigned integer(‘q’) | a subclass of int |
dbus.UInt32 | 32-bit unsigned integer(‘u’) | (*)_ |
dbus.UInt64 | 64-bit unsigned integer(‘t’) | (*)_ |
dbus.Double | double-precision float(‘d’) | a subclass of float |
dbus.ObjectPath | object path(‘o’) | a subclass of str |
dbus.Signature | signature(‘g’) | a subclass of str |
dbus.String | string(’s’) | a subclass of unicode |
dbus.UTF8String | string(’s’) | a subclass of str |
bool | Boolean(‘b’) | |
int or subclass | 32-bit signed integer(‘i’) | |
long or subclass | 64-bit signed integer(‘x’) | |
float or subclass | double-precision float(‘d’) | |
str or subclass | string(’s’) | must be valid UTF-8 |
unicode or subclass | string(’s’) |
標記(*)的類型,在不同的平臺上可能是int的子類或者long的子類。
(+):D-Bus proxy objects, exported D-Bus service objects 以及其他任何擁有dbus_object_path屬性(必須為字符串,可以轉化為對象路徑)的對象。這在你使用dbus-python編寫面向對象的API時,可能對你有幫助。
基本類型轉換(Basic types conversions)
如果內省成功,dbus-python還接受:
- 對于Boolean參數,任何對象(經由int(bool(…))轉化)
- 對于byte參數,a single-character string,即,單字字符串(經由ord()轉化)
- 對于byte和integer參數,任何integer(必須在正確的區間)
- 對于object-path和signature參數,任何str或者unicode的子類(值必須遵循正確的語法)。
容器類型(Container types)
D-Bus支持4種容器類型:array(包含同種類型數據的可變長度隊列), struct(一個固定長度隊列,它的成員可能是不同的數據類型),dictionary(從同一基本類型到同一類型的映射),variant(一種可以存儲任何D-Bus數據類型的容器,包含另一variant在內)。
Array的代表是Python的list或者dbus.Array(list的一個子類)。發送一個array時,如果內省標識(introspected signature)可用,則使用內省標識;否則,如果傳遞了signature關鍵字參數給array的構造函數,則使用傳遞的signature來作為array內容的signature;如果內省失敗,也沒有給array的構造函數傳遞signature關鍵字參數,則dbus-python將根據array的第一個元素來猜測其signature.
一個array的signature格式為’ax’,其中的’x’代表array元素的signature。例如,你可以用’as’表示字符串array(array of strings),或者用’a(ii)’表示結構體數組(array of structs each containing two 32-bit integer),這個array的元素是有2個32-bit整數組成的結構體。
還有一種類型dbus.ByteArray,它是str的一個子類。這種類型被廣泛用于代表一個D-Bus的字節數組(signature ‘ay’)。
Struct的代表是Python的tuple或者dbus.Struct(tuple的一個子類)。當發送一個struct時,如果內省的signature可用,則使用內省的signature;否則,如果傳遞signature關鍵字參數給Struct(指南中使用的的Array,個人感覺是寫錯了,不是是否理解有誤)的構造函數,則使用此傳遞的signature作為struct內容的signature;否則dbus-python將通過struct(同樣,指南原文使用的是Array)的第一個元素來猜測signature(這里個人有一點小疑問,struct的成員數據類型是不一定相同的,這個要怎么根據第一個元素猜?)。
一個struct的signature是用一個元括號將其成員的signature括起來。例如’(is)’是一個struct的signature,這個struct包含了一個32-bit的integer成員和一個string類型成員。
Dictionary的代表是Python的Dictionary或者dbus.Dictionary(一個dict的子類)。當發送一個dictionary時,如果內省的signature可用,則使用內省signature;否則,如果給Dictionary的構造函數傳遞了signature關鍵字參數,則使用傳遞的signature作為dictionary內部的key和value的signature;否則,dbus-python會根據dict中隨意的一個item來猜測其signature。
dictionary的signature的格式為’a{xy}’,其中,’x’是dictionary中key的signature(它不能是容器的類型),’y‘是dictionary中的value的signature。例如,’a{s(ii)}’是一個dictionary的signature,這個dictionary的key是一個字符串,value是一個由2個32-bit的整數組成的結構體。
variant的代表是在D-Bus的任意一個數據類型上,通過其構造函數將關鍵字參數variant_level的值設置為一個大于0的數。(variant_level 1表示一個包含一些其他數據類型(不能是variant類型)的成員variant。variant_level 2 表示一個包含另一個variant(被包含的variant是一個包含除了variant類型之外的其他數據成員的variant)成員的variant,等等)。如果將一個非variant(non-variant)數據傳遞給一個內省要求為variant的參數,則,這個非variant數據會被自動封裝為variant類型。
variant的signature是’v’。
返回值和byte_arrays和utf8_strings選項
如果一個D-Bus方法沒有返回值,則Python的代理方法返回None。
如果一個D-Bus方法有一個返回值,則Python代理方法會根據dbus.Types的相應類型返回那個數值-默認情況下,如果方法返回dbus.String(Unicode的一個子類)類型,則代理方法返回string。如果方法返回值是由dbus.Byte組成的dbus.Array,則Python代理方法返回array。
如果一個D-Bus方法返回多個返回值,則Python代理方法返回一個包含這些數值的tuple。
如果你想讓返回的string類型是dbus.UTF8String(str的一個子類),需要給python的代理方法傳遞關鍵字參數utf8_string=True。
如果你想讓byte array數值以dbus.ByteArray(同樣也是str的子類,在實踐中,這個類型經常是你最需要的)的類型返回,需要給python代理方法傳遞關鍵字參數byte_arrays=True。
異步方法調用(Making asynchronous method calls)
異步(非阻塞)方法調用允許多個方法調用同時進行,并且允許你在等待返回結果之前做其他事情。要使用異步方法調用,你首先需要一個事件循環或者“主循環”(main loop)。
設置一個事件循環(Setting up an event loop)
當前,dbus-python唯一支持的主循環(main loop)就是GLib。
dbus-python有一個全局默認的主循環,這是使用這個功能的最簡便方法。要把GLib設置為默認主循環可以通過:
from dbus.mainloop.glib import DBusGMainLoop
DBusGMainLoop(set_as_default=True)
這些操作必須要在連接到bus服務之前完成。
可以通過pygi開始運行主循環:
from gi.repository import GLib
loop = GLib.MainLoop()
loop.run()
當loop.run()正在執行時,GLib會在適當的時候調用你的回調函數。要停止事件循環,調用loop.quit()。
你也可以通過在連接Bus服務時,通過傳遞一個main loop給Bus的構造函數,這樣也可以設置主循環:
import dbus
from dbus.mainloop.glib import DBusGMainLoop
dbus_loop = DBusGMainLoop()
bus = dbus.SessionBus(mainloop=dbus_loop)
這個功能不是很有用,除非我們能夠支持多個主循環。
向后兼容:dbus.glib
在dbus-python 0.80以前的版本, 設置GLib作為默認主循環的方式是:
import dbus.glib
執行以上import語句會自動加載GLib的主循環并將其作為默認設置。這中做法由于太不明顯已經被拋棄。但是知道這個事情對你要編寫或者理解以前版本的代碼還是有用的。
Qt主循環
PyQt V4.2和其以后版本支持將dbus-python整合到Qt事件循環中。要把D-Bus連接到Qt的主循環中,掉用dbus.mainloop.qt.DBusQtMainLoop
代替dbus.mainloop.glib.DBusGMainLoop即可。除此以外,Qt循環和GLib循環的使用方式一樣。
異步調用(Making asynchronous calls)
要做異步調用,需要傳遞2個可調用的關鍵字參數reply_handler和error_handler給代理方法(代理對象的方法)。代理方法會理解返回None。一段時間以后,在事件循環的運行過程中,會發生以下兩種情況之一:
- reply_handler以method的返回值作為參數被調用;或者
- error_handler以一個代表遠端異常的DBusException實例為參數被調用。
See also
examples/example-async-client.py異步調用了examples/example-service.py提供的服務,這些服務會返回一個值或者異常。與examples/example-client.py一樣,你也需要先在后臺或者另一個shell運行examples/example-service.py。
接收信號(Receiving signals)
要接收信號,Bus需要連接到一個事件循環上。只有當事件循環運行時,信號才能被接收。
信號匹配(Signal matching)
要響應信號,你可以在Bus對象上使用add_signal_receiver方法。當接收到匹配的信號后,會調用安排好的回調函數。add_signal_receiver有以下參數:
- a callable(the handler_function),當接收到信號時會調用這個函數,它的參數將作為信號的參數。
- 信號名,signal_name:默認為None,匹配所有名字
- D-Bus接口,dbus_interface:默認為None,匹配所有接口
- 發送端的眾所周知名或唯一名,bus_name:默認None,匹配所有發送者。眾所周知名匹配當前這個周知名的擁有者。
- 發送端的對象路徑,path:默認為None,匹配所有對象路徑。
add_signal_receiver 也有兩個關鍵字參數utf8_strings和byte_arrays,它們會影響調用handler函數時使用的數據類型。它們對于代理對象也會產生同樣的影響。
add_signal_receiver返回一個SignalMatch對象,這個對象唯一有用的公共API是無參數的remove方法,調用這個方法會從連接中刪除信號的匹配。
從一個信號獲取更多信息(Getting more information from a signal)
你也可以安排更多的信息傳遞給handler函數。如果你傳遞關鍵字參數sender_keyword, destination_keyword, interface_keyword, member_keyword或者path_keyword給connect_to_signal方法,則信號的相應部分的信息會作為關鍵字參數傳遞給handler函數。例如:
def handler(sender=None):print "got signal from %r" % sender
iface.connect_to_signal("Hello", handler, sender_keyword='sender')
當接收到來自com.example.Foo的無參數信號Hello時,handler函數會被調用,并且以sender=’com.example.Foo’為參數。
字符串參數匹配(String argument matching)
如果關鍵字參數的格式是argN,N是一個小的非負數,并且它們的值必須是unicode或UTF-8格式的字符串。此時,只有當接收到的信號的參數是這個關鍵字參數字符串時,才會調用handler函數。
從代理對象接收信號(Receiving signals from a proxy object)
代理對象有一個特殊的connect_to_signal方法,它安排一個可調用函數,當代理對象接收到來自其對應的遠端的信號時,就會調用這個可調用函數。代理對象的connect_to_signal的參數有:
- 信號名稱。
- 一個可調用函數(the handler function),當接收到信號時被事件循環調用-它的參數將作為信號的參數。
- handler函數,一個可調用函數:與add_signal_receiver一樣
- 關鍵字參數 dbus_interface,指定接口名稱。
dbus.Interface對象也有一個類似的connect_to_signal方法,但是它不再需要關鍵字參數dbus_interface,因為接口名信息已經知道了。
盡管可以你可以創建代理對象并啟用監聽信號的服務,但是我們不應該僅僅為了監聽信號就專門創建代理對象,如果你已經創建了一個代理對象用于方法調用,那么你也可以很方便的用它來匹配信號。
See also
examples/signal-recipient.py接收信號-它示范了常用信號的匹配以及connect_to_signal的使用。在運行這個腳本之前,你需要在后臺或者另一個shell里運行examples/signal-emitter.py。
Exporting 對象(Exporting objects)
對象被exported后可以很方便的讓其他程序通過D-Bus定位到。所有dbus.service.Object的子類都會自動被exported。
要export對象,Bus需要連接到一個事件循環。只有在事件循環運行時,export方法才會被調用,隊列中的信號才會被發送。
從dbus.services.Object繼承
要把一個對象export到Bus上,只要子類化dbus.service.Object就可以了。需要將一個BusName/Bus object,以及對象路徑傳遞給對象的構造函數。例如:
class Example(dbus.service.Object):def __init__(self, object_path):dbus.service.Object.__init__(self, dbus.SessionBus(), path)
這個對象會自動支持introspection, 但是沒有其它任何有用的功能。要完善它,我們需要export一些方法和信號。
使用dbus.service.method Exporting方法(Exporting methods with dbus.service.method)
可以使用dbus.service.method裝飾器(python用于修飾函數)來export一個方法。例如:
class Example(dbus.service.Object):def __init__(self, object_path):dbus.service.Object.__init__(self, dbus.SessionBus(), path)@dbus.service.method(dbus_interface='com.example.Sample', in_signature='v', out_signature='s')def StringifyVariant(self, variant):return str(variant)
in_signature和out_signature是D-Bus的標識字符串,在數據類型章節介紹了。
你也可以傳遞關鍵字參數byte_arrays和utf8_strings,這會影響到在D-Bus調用裝飾方法時,傳遞給裝飾方法的參數的類型。這兩個關鍵字參數也對代理方法的返回值產生同樣的影響。
找出調用者的bus name(Finding out the caller’s bus name)
裝飾方法接受sender_keyword關鍵字參數。如果你把這個關鍵字參數設置為一個字符串,則發送者的唯一名會被作為那個名字的關鍵字參數傳遞給裝飾方法:
class Example(dbus.service.Object):def __init__(self, object_path):dbus.service.Object.__init__(self, dbus.SessionBus(), path)@dbus.service.method(dbus_interface='com.example.Sample', in_signature='', out_signature='s', sender_keyword='sender')def SayHello(self, sender=None):return 'Hello, %s!' % sender# ->something like 'Hello, :1.1!'
異步方法實現(Asynchronous method implementations)
例子參考examples/example-async-service.py。(這個例子服務器上還沒有)
使用dbus.service.signal發送信號(Emitting signals with dbus.service.signal)
要export一個信號,可以使用裝飾符dbus.service.signal;要發送那個信號,調用裝飾方法。通常,裝飾方法也可以包含調用時運行的代碼(不太理解,感覺是廢話),例如:
class Example(dbus.service.Object):def __init__(self, object_path):dbus.service.Object.__init__(self, dbus.SessionBus(), path)@dbus.service.signal(dbus_interface='com.example.Sample', signature='us')def NumberOfBottlesChanged(self, number, content):print "%d bottles of %s on the wall" % (number, content)e = Example('/bottle-counter')
e.NumberOfBottlesChanged(100, 'beer')
# -> emits com.example.Sample.NumberOfBottlesChanged(100, 'beer')
# and prints "100 bottles of beer on the wall"
例子(Example)
當examples/example-signal-emitter.py的某個方法被調用時,它會根據需要發送一些信號。(事實上,當一些內部狀態發生變化時你會發送一個信號。這些狀態的變化可能是D-Bus的方法調用引起的也可能不是由由D-Bus的方法調用引起的。)