入り口に戻る

原文はこちら。

原文は CC-BY-SA-4.0 で公開されており、本サイトのこのページも同じ。

# D-Bus で独自型を使用する

D-Bus のメソッド呼び出しの引数として独自の型を使用する。

### 目次

- クラスを書く
  - Q_CLASSINFO
- XML を生成する
- XML を編集する
- アダプタ・クラスとインターフェイス・クラスを生成する
- Qt メタ・オブジェクトの魔法
  - 型を登録する
  - QDBusArgument の入出演算子を用意する
- アダプタ・クラスとインターフェイス・クラスを使用する
  - アダプタを使用してオブジェクトを公開する
  - インターフェイスを使用して遠隔のオブジェクトと対話する
  - アプリケーションの中で遠隔のオブジェクトを使用する
- 雑録
  - 自動化
  - 列挙型を大胆に整形する
- 例

## クラスを書く

公開したいクラスを書く。このクラスに独自型(custom type)を使用したシグナル、スロット、プロパティを加えていく。

以下の例で使用する「公開したいクラス」は次のものである。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #ifndef CHAT_HPP #define CHAT_HPP #include <QObject> #include <QStringList> class Message; class Chat : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_PROPERTY( QStringList users READ users) signals: void userAdded(const QString& user); void userRemoved(const QString& user); void messageSent(const Message &message); public slots: void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(const Message &message); public: Chat(QObject* parent = nullptr); virtual ~Chat(); QStringList users() const; private: QStringList m_users; }; #endif // CHAT_HPP

このクラスは、単純なユーザ管理機能を持ち、メッセージ送信手続きを備えている。この例では、Message クラスが独自の型である。Message クラスには、「ユーザ」と「テキスト・メッセージ」だけが入っている。Chat クラスのメソッドで QString の引数を２つ取るようにすることができないわけではない。けれども、そのようにすると Qt が全てを自動で処理できるようになってしまうため、このチュートリアルでは変則的なやり方を採用する必要があるのである。

Chat クラスでは、Qt が標準で多くの型に対応していることを示すため、QStringList を使用している。この点については、後ほど詳しく説明する。

### Q_CLASSINFO

`Q_CLASSINFO` 宣言は、xml ツールが使用する「インターフェイス名」を指定するためのものである。

通常はインターフェイスの名前の中に会社名を含めるので、宣言は次のようなものになる。

> Q_CLASSINFO("D-Bus Interface", "com.firm.department.product.interface")

("D-Bus Interface", "com.会社名.部署名.製品名.インターフェイス名")

## XML を生成する

Qt に付属しているツール qdbuscpp2xml を使用して自作のクラスの XML 定義ファイル(xml description)を生成する。

上の例のファイル `Chat.hpp` に対して `qdbuscpp2xml` を実行して、次の出力を得る。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> </interface> </node>

この XML ファイルには Chat クラスのメソッドの中、Qt 標準の型を使用しているもの全てが含まれていることに注目してほしい。もしこの XML ファイルに基いてアダプタ・クラスやインターフェイス・クラスを生成するのであれば、「ユーザ」の追加と削除、「ユーザ」の一覧の取得、及びシグナル「userAdded」(ユーザが追加された)と「userRemoved」(ユーザが削除された)の受信が可能になるであろう。`QStringList` 型も自動で処理されている。(訳註：アダプタ・クラスは D-Bus 経由でオブジェクトを公開するアプリケーションが同オブジェクトを包むために使用するものであり、インターフェイス・クラスは D-Bus で公開されているオブジェクトを利用しようとするアプリケーションが同オブジェクトとやり取りするために使用するものである。)

しかしながら、Message 型を扱うメソッドは利用できない。Message 型を扱えるアダプタとインターフェイス(クラス)を生成するには、上記の XML ファイルを修正する必要がある。

豆知識：自動生成された XML だけを使用してアダプタとインターフェイスを生成することもできる。そうしたアダプタやインターフェイスを後ほど生成するものと比べてみるのは有益である。

## XML を編集する

ツール `qdbusxml2cpp` に独自型(custom type)について教えてやる必要があるので、XML ファイルに型の情報を書き加えなければならない。

これは、annotation (註釈)というタグを指定することで為される。

> <annotation name="org.qtproject.QtDBus.QtTypeName" value="*customType*"/>

このタグの構文は、シグナル/メソッドの中で使用するのか、それともプロパティの中で使用するのかによって少し異なるものになるが(プロパティの場合、`.In0` は省略できる)、どちらにせよそこそこ簡単で単純である。以下に、QRect 型を扱う XML の例を挙げる(但し、このチュートリアル本体の例とは関係が無い)。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <property name="rectangle" type="(iiii)" access="readwrite"> <annotation name="org.qtproject.QtDBus.QtTypeName" value="QRect"/> </property> <signal name="rectangleChanged"> <arg name="rect" type="(iiii)" direction="out"/> <!-- Qt < 5.6.3 & 5.7.0 に対応するには、代わりに "org.qtproject.QtDBus.QtTypeName.In0" を使用する --> <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QRect"/> </signal> <method name="changeRectangle"> <arg name="message" type="(iiii)" direction="in"/> <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="QRect"/> </method>

「type」に何を指定するかについては、それほど悩まなくてよい。複雑すぎてデフォルト・ハンドラで整形・再整形(marshal/unmarshal)するのが難しい型(type)は、独自型(custom type)を用いて処理されることになる。そのため、type に何を指定するのかは実際には問題にならない。全ての type に `"(iiii)"` を指定することもできる。

メソッドのいづれかが複雑な戻り値を返す場合、`org.qtproject.QtDBus.QtTypeName.Out0` についても annotation タグを書き加える必要がある。

シグナルの引数に複雑な型が含まれている場合、ここでも `org.qtproject.QtDBus.QtTypeName.Out0` について annotation タグを書き加える必要がある。但し、Qt < 5.6.3 & 5.7.0 との後方互換性を保つため、通常のメソッドの引数の場合と同様に `org.qtproject.QtDBus.QtTypeName.In0` を使用できるようになっている。`qdbusxml2cpp` は、5.6.3 & 5.7.0 より後の版でもこの機能に対応しているが、警告を発することになる。

註記：上記の XML は QRect を使用している。QRect はデフォルトで利用できるため、上記のコードは qdbuscpp2xml によって生成することができる。

このチュートリアルの例の中で Chat インターフェイスとして使用されるものの完全版は次の通り。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <signal name="messageSent"> <arg name="message" type="a(ii)" direction="out"/> <!-- Qt < 5.6.3 & 5.7.0 にも対応するには、代わりに "org.qtproject.QtDBus.QtTypeName.In0" を使用する --> <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Message"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> <method name="sendMessage"> <arg name="message" type="a(ii)" direction="in"/> <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="Message"/> </method> </interface> </node>

## アダプタ・クラスとインターフェイス・クラスを生成する

`qdbusxml2cpp` が必要とする情報全てを XML に入れることができたので、次はアダプタ・クラスとインターフェイス・クラスを生成する。

独自型(custom type)を使用するため、いくつかの文を付け加える必要がある。これによって、アダプタ・クラスとインターフェイス・クラスで全ての型情報を見つけられるようになる。

DBusChat の例(訳註：このチュートリアルで扱っているプログラムのこと)では、アダプタ・クラスとインターフェイス・クラスは次の命令を呼び出して作成されている。

> qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface

## Qt メタ・オブジェクトの魔法

オブジェクトを包む(wrap)アダプタと、同アダプタと対話するインターフェイスとを用意することができたけれども、まだそれらをコンパイルすることはできない。Qt メタ・オブジェクト・システムで独自型(custom type)を処理できるようにするためには、いくらか付け加えなければならないものがあるからである。

### 型を登録する

独自型(custom type)の定義が書かれているヘッダ・ファイルに次の指示文を書き加えることで、その型を Qt のメタ型として宣言する。

> Q_DECLARE_METATYPE

２つの呼び出し `qRegisterMetaType` と `qDBusRegisterMetaType` を追加し、それによって Qt フレームワークが独自型を処理できるようにする。

例の Message クラスにおいては、この２つの呼び出しを追加するために静的メソッドを１つ導入している。

> void Message::registerMetaType() { qRegisterMetaType<Message>("Message"); qDBusRegisterMetaType<Message>(); }

重要：オブジェクトを公開するアプリケーションとオブジェクトを利用するアプリケーションの両方で独自型を登録する必要がある。なぜかというと、両方のアプリケーションでその独自型を処理できる必要があるからである。また、独自型を必要とするアダプタやインターフェイスのメソッドを呼び出す前にその独自型を登録しておくよう気をつけなければならない。Qt は(まだ)処理できない型が使用された場合にエラー出力を表示するが、それでも読者は上記の点に注意を払っておかなければならない。

### QDBusArgument の入出演算子を用意する

独自型(custom type)を使用する DBus 呼び出しを実行する場合、Qt フレームワークはインスタンスを整形(marshal:serialize)したり再整形(unmarshal:unserialize)したりする必要がある。

この作業は QDBusArgument の入出演算子(ストリーム演算子)を用いて行われるので、以下で説明するように、独自型のための入出演算子を実装する必要がある。

例中の Message 型の場合、入出演算子の定義はとても単純である。Message クラスには２つの文字列しか入っていないからである。

> QDBusArgument &operator<<(QDBusArgument &argument, const Message& message) { argument.beginStructure(); argument << message.m_user; argument << message.m_text; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message) { argument.beginStructure(); argument >> message.m_user; argument >> message.m_text; argument.endStructure(); return argument; }

`QDBusArgument` には、もっと複雑な型を処理する関数も備わっている。

## アダプタ・クラスとインターフェイス・クラスを使用する

ここまでの説明で、アダプタ・クラスとインターフェイス・クラスを使い始めるための準備と、独自型が自動で処理されるようにするための準備が整った。

### アダプタを使用してオブジェクトを公開する

オブジェクトを公開するには、まずそのオブジェクトのためのアダプタをインスタンス化し、次いで使用している DBus に同オブジェクトを登録しなければならない。

DBusChat の例では、次のコードで Chat オブジェクトを公開する。

> Chat* pChat = new Chat(&a); ChatAdaptor* pChatAdaptor = new ChatAdaptor(pChat); if (!connection.registerService(CHAT_SERVICE)) { qFatal("Could not register service!"); } if (!connection.registerObject(CHAT_PATH, pChat)) { qFatal("Could not register Chat object!"); }

ChatAdaptor のインスタンスは、Chat オブジェクトへ送られてくる DBus リクエストを処理するものである。あるメソッドが呼び出された場合、ChatAdaptor のインスタンスは Chat オブジェクトの該当するスロットを呼び出す。Chat オブジェクトがシグナルを発する場合、ChatAdaptor が DBus を通して同シグナルを転送する。

### インターフェイスを使用して遠隔のオブジェクトと対話する

遠隔のオブジェクトと対話するには、対応するインターフェイスを実体化し(対応するインターフェイス・クラスをインスタンス化し)、正しいサービス名とオブジェクト経路(object path)を渡すだけでよい。

DBusChat の例では、遠隔の Chat オブジェクトとの接続を確立するには次のようなコードを書く。

> demo::Chat chatInterface(CHAT_SERVICE, CHAT_PATH, connection);

(訳註：「demo::Chat」等の定義は、チュートリアルの最後に掲載されている。)

### アプリケーションの中で遠隔のオブジェクトを使用する

一度インターフェイス・クラスのインスタンスを作成すれば、他の QObject とやり取りするのと同じようなやり方で遠隔のオブジェクトとやり取りできるようになる。

例えば、DBusChat の例の ChatWindow クラスでは、次の呼び出しを行うだけでユーザを追加することができる。

> m_chatInterface.addUser(m_userName);

また ChatWindow は、他の QObject に接続する(connect())のと同じやり方で Chat オブジェクトのインターフェイス・クラスに接続することによって、同オブジェクトが発したシグナルに応答することができる。

> connect(&m_chatInterface, SIGNAL(userAdded(QString)), SLOT(chat_userAdded(QString))); connect(&m_chatInterface, SIGNAL(userRemoved(QString)), SLOT(chat_userRemoved(QString))); connect(&m_chatInterface, SIGNAL(messageSent(Message)), SLOT(chat_messageSent(Message)));

## 雑録

### 自動化

こうした作業を手作業で行うのは望ましいことではない。けれども、dbus の呼び出しを直接構成し返答を直接処理するようなコードを書くよりも、アダプタとインターフェイスを利用する方がはるかに手軽である。

qdbuscpp2xml と qdbusxml2cpp が自分に渡されたヘッダ・ファイルだけを解析するのではなく、コード・ツリー全体を解析するよう拡張され、それによって独自型に対応するようになってほしいと考える人もいるかもしれない。そうなれば、qdbuscpp2xml と qdbusxml2cpp の２つは、独自型を認識できるようになり、それに応じたメソッド、シグナル、プロパティを XML に追加できるようになるからである。qdbuscpp2xml の機能を拡張するやり方としてはプラグインを用いるものが考えられる。このやり方を実装したのが Cpp2XmlPlugins である。

また、Qt Creator に機能を追加して、上のような手法を自動で行うようにするやり方もある。Qt Creator はもともとプロジェクトで使われている独自型を知っている必要があるので、このやり方は qdbus ツールを修正するやり方よりも現実的(少なくともより簡単)かもしれない。

### 列挙型を大胆に整形する(serialization)

列挙型を大量に使用し且つそれらを DBus 経由で公開する必要がある場合、全ての列挙型に対して QDBusArgument の入出演算子(stream operators)を使用することになるであろう。初歩的な実装としては、単に列挙型を int 型へ、int 型を列挙型へ変換(cast)するものが考えられる。そのコードは次のようなものになろう。

> QDBusArgument &operator<<(QDBusArgument &argument, const EnumerationType& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, EnumerationType &dest) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); dest = static_cast<EnumerationType>(a); return argument; }

この変換コードがほとんど同じコードのまま全ての列挙型に対して通用することに気づいたであろう。変える箇所は「EnumerationType」のところだけである。

ありがたいことに、C++ はテンプレートの扱い方を知っているので、テンプレートを使った実装を１つ書くだけで済むはずである。しかしながら、ここで我々が必要としているのは列挙型だけを扱う実装であり、DBus を通して送信しようと考えている他の独自型を扱う実装ではない。そうでなければ、いづれの独自型においても、独自型から整数型への変換、あるいは整数型から独自型への変換が行われてしまうであろう。全ての型が(とりわけ複雑な型が)そのように変換されてしまうのは、おそらく読者の望むところではなかろう。

ここで今どきの C++ 標準ライブラリが役に立つ。ちょっとした魔法によって、テンプレートの定義の中で条件式が使えるのである。

これを用いると、`QDBusArgument` の整形(marshaling)と再整形(unmarshalling)の手続きを、条件式が真の場合だけ使用される形で実装することができる。

実際の整形(marshaling)と再整形(unmarshalling)のコードは次のようなものになる。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | template<typename T, typename TEnum> class QDBusEnumMarshal; template<typename T> class QDBusEnumMarshal<T, std::true_type> { public: static QDBusArgument& marshal(QDBusArgument &argument, const T& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } static const QDBusArgument& unmarshal(const QDBusArgument &argument, T &source) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); source = static_cast<T>(a); return argument; } }

ここまでの作業によって、整形手続きを実装したものを以下のコードで呼び出せるようになった。

> QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source);

`T` には整形(marshal)したい型が入る。

この実装を QDbusArgument の入出演算子に結びつけるには、さらに接着剤となるコードが必要である。

> template<typename T> QDBusArgument& operator<<(QDBusArgument &argument, const T& source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source); } template<typename T> const QDBusArgument& operator>>(const QDBusArgument &argument, T &source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::unmarshal(argument, source); }

上記のもの全てを１つのヘッダ・ファイルに入れ、列挙型(DBus 経由で受け渡す必要があるもの)を宣言したファイルに同ヘッダ・ファイルを取り込む(include)。こうすることで、コンパイラは自力で入出演算子(の定義)を見つけることができるようになる。

但し、依然として列挙型をメタ型として宣言して Qt メタ・オブジェクト・システムに登録する必要はある。

### enumDBus.hpp

任意の列挙型を整形/再整形(marshal/unmarshal)するのに必要なヘッダ・ファイルの全体は次の通り。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #ifndef _ENUM_DBUS_HPP #define _ENUM_DBUS_HPP #include <QtDBus/QDBusArgument> #include <type_traits> using namespace std; template<typename T, typename TEnum> class QDBusEnumMarshal; template<typename T> class QDBusEnumMarshal<T, std::true_type> { public: static QDBusArgument& marshal(QDBusArgument &argument, const T& source) { argument.beginStructure(); argument << static_cast<int>(source); argument.endStructure(); return argument; } static const QDBusArgument& unmarshal(const QDBusArgument &argument, T &source) { int a; argument.beginStructure(); argument >> a; argument.endStructure(); source = static_cast<T>(a); return argument; } }; template<typename T> QDBusArgument& operator<<(QDBusArgument &argument, const T& source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::marshal(argument, source); } template<typename T> const QDBusArgument& operator>>(const QDBusArgument &argument, T &source) { return QDBusEnumMarshal<T, typename std::is_enum<T>::type>::unmarshal(argument, source); } #endif //_ENUM_DBUS_HPP

## 例

この節に例の chat のソースを載せる。この wiki では画像以外のファイルのアップロードが認められていないので、ソースをテキスト形式で投稿した。

Chat は、公開されるインターフェイス(の名前)である。ChatAdaptor と ChatInterface は Chat.xml を使用して生成されたものである。

これら全てのファイルを１つのディレクトリに入れ、次のコマンドを実行する。

> mkdir build pushd build cmake ../ -G Ninja ninja ./dbuschat

Chat.hpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | #ifndef CHAT_HPP #define CHAT_HPP #include <QObject> #include <QStringList> class Message; class Chat : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_PROPERTY( QStringList users READ users) signals: void userAdded(const QString& user); void userRemoved(const QString& user); void messageSent(const Message &message); public slots: void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(const Message &message); public: Chat(QObject* parent = nullptr); virtual ~Chat(); QStringList users() const; private: QStringList m_users; }; #endif // CHAT_HPP

Chat.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | #include "Chat.hpp" Chat::Chat(QObject* parent) : QObject(parent) { } Chat::~Chat() { } void Chat::addUser(const QString &user) { if (!m_users.contains(user)) { m_users.append(user); emit userAdded(user); } } void Chat::removeUser(const QString &user) { if (m_users.contains(user)) { m_users.removeOne(user); emit userRemoved(user); } } void Chat::sendMessage(const Message &message) { Q_EMIT messageSent(message); } QStringList Chat::users() const { return m_users; }

Chat.xml

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <!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="demo.Chat"> <property name="users" type="as" access="read"/> <signal name="userAdded"> <arg name="user" type="s" direction="out"/> </signal> <signal name="userRemoved"> <arg name="user" type="s" direction="out"/> </signal> <signal name="messageSent"> <arg name="message" type="a(ii)" direction="out"/> <!-- to support also Qt < 5.6.3 & 5.7.0, use instead "org.qtproject.QtDBus.QtTypeName.In0" --> <annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Message"/> </signal> <method name="addUser"> <arg name="user" type="s" direction="in"/> </method> <method name="removeUser"> <arg name="user" type="s" direction="in"/> </method> <method name="sendMessage"> <arg name="message" type="a(ii)" direction="in"/> <annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="Message"/> </method> </interface> </node>

ChatAdaptor.h

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #ifndef CHATADAPTOR_H_1270658274 #define CHATADAPTOR_H_1270658274 #include <QtCore/QObject> #include <QtDBus/QtDBus> #include "Message.hpp" class QByteArray; template<class T> class QList; template<class Key, class Value> class QMap; class QString; class QStringList; class QVariant; /* * Adaptor class for interface demo.Chat */ class ChatAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "demo.Chat") Q_CLASSINFO("D-Bus Introspection", "" " <interface name=\"demo.Chat\">\n" " <property access=\"read\" type=\"as\" name=\"users\"/>\n" " <signal name=\"userAdded\">\n" " <arg direction=\"out\" type=\"s\" name=\"user\"/>\n" " </signal>\n" " <signal name=\"userRemoved\">\n" " <arg direction=\"out\" type=\"s\" name=\"user\"/>\n" " </signal>\n" " <signal name=\"messageSent\">\n" " <arg direction=\"out\" type=\"a(ii)\" name=\"message\"/>\n" " <!-- to support also Qt < 5.6.3 & 5.7.0, use instead org.qtproject.QtDBus.QtTypeName.In0 -->\n" " <annotation value=\"Message\" name=\"com.trolltech.QtDBus.QtTypeName.Out0\"/>\n" " </signal>\n" " <method name=\"addUser\">\n" " <arg direction=\"in\" type=\"s\" name=\"user\"/>\n" " </method>\n" " <method name=\"removeUser\">\n" " <arg direction=\"in\" type=\"s\" name=\"user\"/>\n" " </method>\n" " <method name=\"sendMessage\">\n" " <arg direction=\"in\" type=\"a(ii)\" name=\"message\"/>\n" " <annotation value=\"Message\" name=\"com.trolltech.QtDBus.QtTypeName.In0\"/>\n" " </method>\n" " </interface>\n" "") public: ChatAdaptor(QObject *parent); virtual ~ChatAdaptor(); public: // PROPERTIES Q_PROPERTY(QStringList users READ users) QStringList users() const; public Q_SLOTS: // METHODS void addUser(const QString &user); void removeUser(const QString &user); void sendMessage(Message message); Q_SIGNALS: // SIGNALS void messageSent(Message message); void userAdded(const QString &user); void userRemoved(const QString &user); }; #endif

ChatAdaptor.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -a ChatAdaptor * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #include "ChatAdaptor.h" #include <QtCore/QMetaObject> #include <QtCore/QByteArray> #include <QtCore/QList> #include <QtCore/QMap> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QVariant> /* * Implementation of adaptor class ChatAdaptor */ ChatAdaptor::ChatAdaptor(QObject *parent) : QDBusAbstractAdaptor(parent) { // constructor setAutoRelaySignals(true); } ChatAdaptor::~ChatAdaptor() { // destructor } QStringList ChatAdaptor::users() const { // get the value of property users return qvariant_cast< QStringList >(parent()->property("users")); } void ChatAdaptor::addUser(const QString &user) { // handle method call demo.Chat.addUser QMetaObject::invokeMethod(parent(), "addUser", Q_ARG(QString, user)); } void ChatAdaptor::removeUser(const QString &user) { // handle method call demo.Chat.removeUser QMetaObject::invokeMethod(parent(), "removeUser", Q_ARG(QString, user)); } void ChatAdaptor::sendMessage(Message message) { // handle method call demo.Chat.sendMessage QMetaObject::invokeMethod(parent(), "sendMessage", Q_ARG(Message, message)); }

ChatInterface.h

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * Do not edit! All changes made to it will be lost. */ #ifndef CHATINTERFACE_H_1270658265 #define CHATINTERFACE_H_1270658265 #include <QtCore/QObject> #include <QtCore/QByteArray> #include <QtCore/QList> #include <QtCore/QMap> #include <QtCore/QString> #include <QtCore/QStringList> #include <QtCore/QVariant> #include <QtDBus/QtDBus> #include "Message.hpp" /* * Proxy class for interface demo.Chat */ class DemoChatInterface: public QDBusAbstractInterface { Q_OBJECT public: static inline const char *staticInterfaceName() { return "demo.Chat"; } public: DemoChatInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0); ~DemoChatInterface(); Q_PROPERTY(QStringList users READ users) inline QStringList users() const { return qvariant_cast< QStringList >(property("users")); } public Q_SLOTS: // METHODS inline QDBusPendingReply<> addUser(const QString &user) { QList<QVariant> argumentList; argumentList << qVariantFromValue(user); return asyncCallWithArgumentList(QLatin1String("addUser"), argumentList); } inline QDBusPendingReply<> removeUser(const QString &user) { QList<QVariant> argumentList; argumentList << qVariantFromValue(user); return asyncCallWithArgumentList(QLatin1String("removeUser"), argumentList); } inline QDBusPendingReply<> sendMessage(Message message) { QList<QVariant> argumentList; argumentList << qVariantFromValue(message); return asyncCallWithArgumentList(QLatin1String("sendMessage"), argumentList); } Q_SIGNALS: // SIGNALS void messageSent(Message message); void userAdded(const QString &user); void userRemoved(const QString &user); }; namespace demo { typedef ::DemoChatInterface Chat; } #endif

ChatInterface.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* * This file was generated by qdbusxml2cpp version 0.7 * Command line was: qdbusxml2cpp Chat.xml -i Message.hpp -p ChatInterface * * qdbusxml2cpp is Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * * This is an auto-generated file. * This file may have been hand-edited. Look for HAND-EDIT comments * before re-generating it. */ #include "ChatInterface.h" /* * Implementation of interface class DemoChatInterface */ DemoChatInterface::DemoChatInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) { } DemoChatInterface::~DemoChatInterface() { }

ChatWindow.hpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | #ifndef CHATWINDOW_HPP #define CHATWINDOW_HPP #include <QMainWindow> #include <QStringListModel> #include "ChatInterface.h" namespace Ui { class ChatWindow; } class QCloseEvent; class ChatWindow : public QMainWindow { Q_OBJECT public: ChatWindow(demo::Chat& chatInterface, QWidget *parent = nullptr); virtual ~ChatWindow(); protected: virtual void closeEvent(QCloseEvent *event); private: ChatWindow(const ChatWindow &other); ChatWindow& operator=(const ChatWindow &other); Ui::ChatWindow *ui; QString m_userName; QStringListModel m_users; demo::Chat &m_chatInterface; private slots: void sendMessage(); void chat_userAdded(const QString &user); void chat_userRemoved(const QString &user); void chat_messageSent(const Message &message); }; #endif // CHATWINDOW_HPP

ChatWindow.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | #include "ChatWindow.hpp" #include "ui_ChatWindow.h" #include <QtGui/QCloseEvent> #include <QInputDialog> #include <QMessageBox> #include "Message.hpp" ChatWindow::ChatWindow(demo::Chat& chatInterface, QWidget *parent) : QMainWindow(parent), ui(new Ui::ChatWindow), m_userName(), m_users(), m_chatInterface(chatInterface) { ui->setupUi(this); QStringList userList = chatInterface.users(); m_users.setStringList(userList); m_users.sort(0); connect(&m_chatInterface, SIGNAL(userAdded(QString)), SLOT(chat_userAdded(QString))); connect(&m_chatInterface, SIGNAL(userRemoved(QString)), SLOT(chat_userRemoved(QString))); connect(&m_chatInterface, SIGNAL(messageSent(Message)), SLOT(chat_messageSent(Message))); connect(ui->lneMessage, SIGNAL(returnPressed()), SLOT(sendMessage())); connect(ui->btnSend, SIGNAL(released()), SLOT(sendMessage())); bool ok; m_userName = QInputDialog::getText(this, "Username?", "User name:", QLineEdit::Normal, QDir::home().dirName(), &ok, Qt::Dialog); if (!ok) { QApplication::exit(); qFatal("Cancelled"); } if (userList.contains(m_userName)) { QMessageBox::critical(this, "Invalid Username", "Username " + m_userName + " is already taken"); QApplication::exit(); qFatal("Duplicate username"); } m_chatInterface.addUser(m_userName); setWindowTitle("Chat -- " + m_userName); ui->lstUsers->setModel(&m_users); } ChatWindow::~ChatWindow() { delete ui; } void ChatWindow::closeEvent(QCloseEvent *event) { QMainWindow::closeEvent(event); if (event->isAccepted()) { m_chatInterface.removeUser(m_userName); } } void ChatWindow::sendMessage() { Message message(m_userName, ui->lneMessage->text()); m_chatInterface.sendMessage(message); ui->lneMessage->clear(); } void ChatWindow::chat_userAdded(const QString &user) { QStringList users = m_users.stringList(); users.append(user); users.sort(); m_users.setStringList(users); } void ChatWindow::chat_userRemoved(const QString &user) { QStringList users = m_users.stringList(); users.removeOne(user); m_users.setStringList(users); } void ChatWindow::chat_messageSent(const Message &message) { ui->txtChat->appendPlainText(message.getUser() + " : " + message.getText()); }

ChatWindow.ui

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | <?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>ChatWindow</class> <widget class="QMainWindow" name="ChatWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>600</width> <height>400</height> </rect> </property> <property name="windowTitle"> <string>ChatWindow</string> </property> <widget class="QWidget" name="centralWidget"> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QWidget" name="widget_2" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_2"> <item> <widget class="QListView" name="lstUsers"> <property name="maximumSize"> <size> <width>150</width> <height>16777215</height> </size> </property> <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> <property name="editTriggers"> <set>QAbstractItemView::NoEditTriggers</set> </property> <property name="selectionMode"> <enum>QAbstractItemView::NoSelection</enum> </property> </widget> </item> <item> <widget class="QPlainTextEdit" name="txtChat"> <property name="focusPolicy"> <enum>Qt::NoFocus</enum> </property> </widget> </item> </layout> </widget> </item> <item> <widget class="QWidget" name="widget" native="true"> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLineEdit" name="lneMessage"/> </item> <item> <widget class="QPushButton" name="btnSend"> <property name="text"> <string>Send</string> </property> </widget> </item> </layout> </widget> </item> </layout> </widget> <widget class="QMenuBar" name="menuBar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>600</width> <height>25</height> </rect> </property> </widget> <widget class="QToolBar" name="mainToolBar"> <attribute name="toolBarArea"> <enum>TopToolBarArea</enum> </attribute> <attribute name="toolBarBreak"> <bool>false</bool> </attribute> </widget> <widget class="QStatusBar" name="statusBar"/> </widget> <layoutdefault spacing="6" margin="11"/> <resources/> <connections/> </ui>

Message.hpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #ifndef MESSAGE_HPP #define MESSAGE_HPP #include <QtDBus> class Message { public: Message(); Message(const QString& user, const QString &message); Message(const Message &other); Message& operator=(const Message &other); ~Message(); friend QDBusArgument &operator<<(QDBusArgument &argument, const Message &message); friend const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message); QString getUser() const; QString getText() const; //register Message with the Qt type system static void registerMetaType(); private: QString m_user; QString m_text; }; Q_DECLARE_METATYPE(Message) #endif // MESSAGE_HPP

Message.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | #include "Message.hpp" Message::Message() : m_user(), m_text() { } Message::Message(const QString &user, const QString &text) : m_user(user), m_text(text) { } Message::Message(const Message &other) : m_user(other.m_user), m_text(other.m_text) { } Message& Message::operator=(const Message &other) { m_user = other.m_user; m_text = other.m_text; return *this; } Message::~Message() { } QString Message::getUser() const { return m_user; } QString Message::getText() const { return m_text; } void Message::registerMetaType() { qRegisterMetaType<Message>("Message"); qDBusRegisterMetaType<Message>(); } QDBusArgument &operator<<(QDBusArgument &argument, const Message& message) { argument.beginStructure(); argument << message.m_user; argument << message.m_text; argument.endStructure(); return argument; } const QDBusArgument &operator>>(const QDBusArgument &argument, Message &message) { argument.beginStructure(); argument >> message.m_user; argument >> message.m_text; argument.endStructure(); return argument; }

main.cpp

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | #include <QtDBus/QDBusConnection> #include <QtDBus/QDBusConnectionInterface> #include <QApplication> #include "Chat.hpp" #include "ChatAdaptor.h" #include "ChatInterface.h" #include "ChatWindow.hpp" #include "Message.hpp" #define CHAT_SERVICE "demo.dbus.chat" #define CHAT_PATH "/chat" int main(int argc, char *argv[]) { /* Register the Message type first thing, so Qt knows how to handle it before an Adaptor/Interface is even constructed. It should be ok to register it further down, as long as no Message marshaling or unmarshaling takes place, but this is definitely the safest way of doing things. */ Message::registerMetaType(); QApplication a(argc, argv); Chat* pChat = NULL; ChatAdaptor* pChatAdaptor = NULL; /* Create a Chat instance and register it with the session bus only if the service isn't already available. */ QDBusConnection connection = QDBusConnection::sessionBus(); if (!connection.interface()->isServiceRegistered(CHAT_SERVICE)) { pChat = new Chat(&a); pChatAdaptor = new ChatAdaptor(pChat); if (!connection.registerService(CHAT_SERVICE)) { qFatal("Could not register service!"); } if (!connection.registerObject(CHAT_PATH, pChat)) { qFatal("Could not register Chat object!"); } } demo::Chat chatInterface(CHAT_SERVICE, CHAT_PATH, connection); ChatWindow w(chatInterface); w.show(); int ret = a.exec(); //cleanup if (pChat) { delete pChat; } if (pChatAdaptor) { delete pChatAdaptor; } return ret; }

CMakeLists.txt

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | project(dbuschat) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) cmake_minimum_required(VERSION 3.0) set(QT_MIN_VERSION "5.6.0") ################# Disallow in-source build ################# if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message(FATAL_ERROR "This application requires an out of source build. Please create a separate build directory.") endif() include(FeatureSummary) ################# Find dependencies ################# find_package(Qt5 COMPONENTS Widgets DBus REQUIRED) ################# build and install ################# set(dbuschat_SRCS ChatWindow.ui ChatWindow.cpp Chat.cpp Message.cpp Message.hpp ChatAdaptor.cpp ChatInterface.cpp ) add_executable(dbuschat main.cpp ${dbuschat_SRCS}) target_link_libraries(dbuschat Qt5::Widgets Qt5::DBus ) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)

前の記事

D-Bus のインターフェイスを作成する

次の記事

D-Bus 自動起動サービス

入り口に戻る