最近更新: 2010-09-18

php dbus 0.1.0 撰寫 DBus service 的使用經驗

php dbus 0.1.0 (Derick's php-dbus) 仍然在發展階段,其中還有許多待解決的問題。故目前使用時,可能會碰到許多意外事件。本文整理了我的使用經驗,可為 PHP 程序員以 php-dbus 撰寫 DBus 服務之參考。

基本概念

php-dbus 0.1.0 目前的實現內容,只能將一個 PHP 類別視為一個 DBus 個體(object)介面(interface)內容。 PHP 類別內的靜態方法將自動被視為 DBus 個體介面的方法。 所以 PHP 實作一個 DBus 個體介面時,要撰寫一個類別,在其中設計靜態方法。 最後調用 Dbus::registerObject($object_path, $interface_name, $php_class_name); 將此類別註冊為指定的 DBus 個體介面。

你註冊的 DBus 介面名稱與其對應的類別名稱無關,而且也沒有綁定關係。 若你需要,你可以重複將這個類別註冊為不同的 DBus 個體之介面。

例如下面的範例,將提供一個名為 blog.rock 的 DBus 服務。 此服務將註冊三個 DBus 個體,並且各自定義了一個 DBus 介面。 而這三個 DBus 介面都是由同一個 PHP 類別所註冊。

class DbusEchoInterface {
    static function Echo() {
        return "hello";
    }
}

$bus->requestName('blog.rock');

// DBus 個體 '/blog/rock/obj1' 定義了介面 'blog.rock.Echo'.
$bus->registerObject('/blog/rock/obj1', 'blog.rock.Echo', 'DbusEchoInterface');
// DBus 個體 '/blog/rock/obj2' 定義了介面 'blog.rock.Echo'.
$bus->registerObject('/blog/rock/obj2', 'blog.rock.Echo', 'DbusEchoInterface');
// DBus 個體 '/blog/rock/abc' 定義了介面 'blog.abc.Hello'.
$bus->registerObject('/blog/rock/abc',  'blog.abc.Hello', 'DbusEchoInterface');

設計介面方法時,基本上和設計一般的 PHP 類別方法相同,在參數列指示傳入的參數名稱,透過 return 回傳值。 傳入的參數可以是任何 PHP 內建的資料型別,例如整數、字串、陣列。回傳的值也可以是任何 PHP 內建的資料型別。 但受限於 DBus Type 本身可處理的內容,回傳值有下列注意要項。

  • DbusArray 不允許容納不同資料型別的元素。
  • 因為 PHP 語法並不支援一個函數回傳多個值,所以回傳多個值的情形另行處理,而且其中有 bug 。
DbusArray

PHP 的陣列有兩種型式,一種是純數字索引的傳統陣列,另一種是以鍵值索引的關聯式陣列。前者對應於 DbusArray ,後者對應於 DbusDict 。而且 DbusArray 內的元素,必須是同一種資料型別。

當你回傳的 PHP 陣列含有不同資料型別的元素時,php-dbus 會嘗試以 DbusDict 型態處理,但可能會失敗。既使成功轉換為 DbusDict 了,此結果也可能不符合呼叫者的預期。若呼叫者屬於 C/C#/Java 靜態型別語言,它們將擲出型別例外。

多個回傳值與 DbusSet

如果你想直接回傳多個值,而不透過陣列,那麼你必須使用一個 DbusSet 個體包裹它們。此外,DbusSet 目前有 bug 。它不會複製要回傳值的內容,故被呼叫的方法結束後,放置在 DbusSet 內的回傳值,其內容所在的記憶體空間也被釋放了。以至於呼叫者要取得回傳值時,將取不到正確值,甚或是發生違法存取記憶體的錯誤。故而,你要放在 DbusSet 內的回傳值,必須是一個全域變數或是一個類別的靜態變數。如下所示:

    static $result_b;
    static function EchoTwo($a) {
        echo "Two";
        //BUG: (php-dbus 0.1.0) It does not copy value into set.
        // If you put local variables in set, it would not return correct values.
        //return new DbusSet($a, 2); //incorrect
        self::$result_b = 2; //put in a static variable.
        return new DBusSet($a, self::$result_b);
    }

DBusSignal

DBus Signal 被 php-dbus 實作為一個獨立的類別 DbusSignal。要在某個 DBus 介面上實現一個 Signal 的方式是在你新增 DbusSignal 個體時,指定這個訊號要附加的介面名稱。另一方面,為了方便 DBus 個體可以主動發出訊號,最好是將你配置的 DbusSignal 個體指派給 DBus 類別的靜態成員。如此一來,你就可以透過該靜態成員,呼叫 DbusSignal 個體的 send() 方法送出訊號。如下所示。

// add signal 'Signal1' to interface 'blog.rock.Echo'
// of object '/blog/rock/obj1'
class DbusEchoInterface {
    static $signal1;

    static function emit() {
        // 在 DBus 個體內部透過成員主動發出訊號
        self::$signal1->send();
    }
}

// 新增一個 DbusSignal ,附加於 /blog/rock/obj1 個體之 blog.rock.Echo 介面上。
// 並指派給DbusEchoInterface::$signal1
DbusEchoInterface::$signal1 = new DbusSignal(
    $bus,
    '/blog/rock/obj1',
    'blog.rock.Echo',
    'Signal1'
);

//送出訊號。
DbusEchoInterface::$signal1->send();

Introspect

大多數程式語言對於 DBus 的支援,一般皆透過 dbus-glib-binding 實現,同時運用本身的反射機制,自動為你實作的 DBus 個體產生 org.freedesktop.DBus.Introspectable 介面。但是 php-dbus 採用 DBus low-level API 實作,並受限於其能力,故不會自動產生 org.freedesktop.DBus.Introspectable org.freedesktop.DBus.Properties 這兩個泛用介面;後者較為少用,一般也不會實作它。若你需要提供此介面,則你必須自行定義 org.freedesktop.DBus.Introspectableorg.freedesktop.DBus.Properties 兩介面的內容。當然不定義也可以,只是其他人就不能透過 org.freedesktop.DBus.Introspectable 查詢你的 DBus 服務的介面。

定義 org.freedesktop.DBus.Introspectable 的方式非常簡單。它只有一個名為 Introspect 的方法,回傳一個內容為 XML 文件的字串。該 XML 文件透過固定的方式描述你的 DBus 服務提供的 DBus 個體與其介面。你可以向 org.freedesktop.DBus 服務查詢其內容,了解其 XML 文件的格式。細節則請參考 D-Bus 規格文件。

在本文中,我使用一個固定的 XML 文件描述本文實作的範例服務。

<?php
// interface and methods, see:
//http://svn.php.net/viewvc/pecl/dbus/trunk/examples/request-name.php?view=markup

class DbusIntrospectableInterface {
    static $introspect_xml =<<<INTROSPECT_XML
<!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="blog.rock">
    <method name="EchoZero">
      <arg direction="out" type="i"/>
    </method>
    <method name="EchoOne">
      <arg name="name" direction="in" type="s"/>
      <arg direction="out" type="s"/>
    </method>
    <method name="EchoTwo">
      <arg name="name" direction="in" type="s"/>
      <arg direction="out" type="s"/>
      <arg direction="out" type="i"/>
    </method>
    <method name="EchoArray">
      <arg direction="out" type="as"/>
    </method>
    <signal name="Singal1">
    </signal>
    <signal name="Singal2">
      <arg type="s"/>
    </signal>
  </interface>
</node>
INTROSPECT_XML;

    static function Introspect() {
        return DbusIntrospectableInterface::$introspect_xml;
    }
}

//echo DbusIntrospectableInterface::Introspect();
?>

撰寫 DBus client 與傾聽訊號

我在 《Write a PHP DBus client》一文中,便已示範過如何撰寫 DBus 客戶端以及傾聽服務的訊號。請自行前往閱讀。在此僅提供一個傾聽本文示範服務之訊號的客戶端程式。

<?php
//http://svn.php.net/viewvc/pecl/dbus/trunk/examples/signal-watch.php?view=markup
$bus = new Dbus( Dbus::BUS_SESSION );

$bus->addWatch( 'blog.rock' );
while (true) {
    $s = $bus->waitLoop(1000);
    if (!$s)
        continue;
    if ($s->matches('blog.rock', 'Signal1')) {
        echo "get signal 1\n";
    }
    if ($s->matches('blog.rock', 'Signal2')) {
        echo "get signal 2\n";
        var_dump($s->getData());
    }
}

?>

Service sample

綜合以上所述,實作一個完整可用的 DBus 服務。

<?php
require_once 'my_introspectable.php';

// interface and methods, see:
//http://svn.php.net/viewvc/pecl/dbus/trunk/examples/request-name.php?view=markup
class DbusEchoInterface {
    static function EchoZero() {
        echo "Zero";
        return 0;
    }
    static function EchoOne($name) {
        echo "One";
        return "Hello " . $name;
    }

    static $result_b;
    static function EchoTwo($a) {
        echo "Two";
        //BUG: (php-dbus 0.1.0) It does not copy value into set.
        // If you put local variables in set, it would not return correct values.
        //return new DbusSet($a, 2);
        self::$result_b = 2;
        return new DBusSet($a, self::$result_b);
    }

    static function EchoArray() {
        echo "Array";
        //notice: dbus array cound not contain elements of multiple type.
        return array("array", "echo");
    }

// signals, see:
//http://svn.php.net/viewvc/pecl/dbus/trunk/examples/signal-send.php?view=markup
    static function TestSignal() {
        echo "emit signal";
        self::$Signal1->send();
        self::$Signal2->send("signal2");
    }

    static $Signal1;
    static $Signal2;
}

// connect to SessionBus
$bus = new Dbus( Dbus::BUS_SESSION, true);

define('SERVICE_NAME', 'blog.rock');
define('OBJECT_PATH', '/blog/rock');
define('MAIN_INTERFACE', 'blog.rock');

define('INTROSPECTABLE_INTERFACE', 'org.freedesktop.DBus.Introspectable');

// request service name.
$bus->requestName(SERVICE_NAME);

// add signal
DbusEchoInterface::$Signal1 = new DbusSignal(
    $bus,
    OBJECT_PATH,
    MAIN_INTERFACE,
    'Signal1'
);

DbusEchoInterface::$Signal2 = new DbusSignal(
    $bus,
    OBJECT_PATH,
    MAIN_INTERFACE,
    'Signal2'
);

// register object path and interface.
$bus->registerObject(OBJECT_PATH, MAIN_INTERFACE, 'DbusEchoInterface');

$bus->registerObject(OBJECT_PATH, INTROSPECTABLE_INTERFACE, 'DbusIntrospectableInterface');

// register another interface...
//$bus->registerObject('/nl/test', 'nl.another.test', 'AnotherService');

while (true) {
    try {
        $bus->waitLoop(1000);
        echo '.';
    }
    catch (Exception $e) {
        echo $e->getMessage();
    }
}
?>

各位可以先用 dbus-send 觀察此服務的是否正常運作。

開啟第一個終端機,執行 sample-service.php,放置play.

# 終端機1
$ php sample-service.php
....Zero.....One..........Two...............Array...

開啟第二個終端機,執行 wait-signal.php, 放置play.

# 終端機2
$ php wait-signal.php
get signal 1
get signal 2
string(7) "signal2"

開啟第三個終端機,用 dbus-send 向 sample-service 要求服務,並觀察前兩個終端機輸出的訊息。

# 終端機3
$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock org.freedesktop.DBus.Introspectable.Introspect

$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock blog.rock.EchoZero
method return sender=:1.79 -> dest=:1.82 reply_serial=2
   int32 0
$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock blog.rock.EchoOne string:"world"
method return sender=:1.79 -> dest=:1.83 reply_serial=2
   string "Hello world"
$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock blog.rock.EchoTwo string:"two"
method return sender=:1.79 -> dest=:1.84 reply_serial=2
   string "two"
   int32 2
$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock blog.rock.EchoArray
method return sender=:1.79 -> dest=:1.85 reply_serial=2
   array [
      string "array"
      string "echo"
   ]

$ dbus-send --session --print-reply --dest=blog.rock \
  /blog/rock blog.rock.TestSignal

參考文件

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/13799073.html