Qt浅谈之二十七进程间通信之QtDBus good
一、简介
DBus的出现,使得Linux进程间通信更加便捷,不仅可以和用户空间应用程序进行通信,而且还可以和内核的程序进行通信,DBus使得Linux变得更加智能,更加具有交互性。
DBus分为两种类型:system bus(系统总线),用于系统(Linux)和用户程序之间进行通信和消息的传递;session bus(回话总线),用于桌面(GNOME, KDE等)用户程序之间进行通信。
二、详解之Qt代码
1、代码一
(1)test.h
- #ifndef TEST_H
- #define TEST_H
- #include <QtCore>
- #include <QTimer>
- class Test : public QObject
- {
- Q_OBJECT
- public:
- Test();
- public slots:
- QString testStart();
- void changeTest();
- signals:
- void stateChange(QString str);
- private:
- QTimer *timer;
- };
- #endif /*TEST_H*/
(2)test.cpp
- #include "test.h"
- Test::Test()
- {
- qDebug() << "===========test init===========";
- timer = new QTimer;
- connect(timer, SIGNAL(timeout()), this, SLOT(changeTest()));
- }
- QString Test::testStart()
- {
- qDebug() << "+++++++QtDBus test++++++++++";
- QString tmp;
- tmp = QString("OFF");
- qDebug() << tmp;
- if (timer->isActive()) {
- timer->stop();
- }
- timer->start(2000);
- return tmp;
- }
- void Test::changeTest()
- {
- QString tmp;
- tmp = QString("ON");
- qDebug() << "+++++++++++++++++++" << tmp;
- emit stateChange(tmp);
- }
(3)test_adaptor.h
- #include "test_adaptor.h"
- #include <QtCore>
- TestInterfaceAdaptor::TestInterfaceAdaptor(QObject *parent)
- :QDBusAbstractAdaptor(parent)
- {
- qDebug() << "***************TestInterfaceAdaptor::TestInterfaceAdaptor**************";
- setAutoRelaySignals(true); //connection of the signals on the parent
- }
- TestInterfaceAdaptor::~TestInterfaceAdaptor()
- {
- }
- QString TestInterfaceAdaptor::test()
- {
- QString out;
- QMetaObject::invokeMethod(parent(), "testStart",Q_RETURN_ARG(QString, out));
- qDebug() << "==========" << out;
- return out;
- }
(4)test_adaptor.cpp
- #ifndef TEST_ADAPTOR_H
- #define TEST_ADAPTOR_H
- #include <QtCore/QObject>
- #include <QtDBus/QtDBus>
- /****
- **dbus-send --session --print-reply --dest=com.asianux.btagent / com.asianux.btagent.adaptor.test
- **dbus-monitor --session \ "type='signal',interface='com.asianux.btagent.adaptor',member='stateChange'"
- *****/
- class TestInterfaceAdaptor : public QDBusAbstractAdaptor
- {
- Q_OBJECT
- Q_CLASSINFO("D-Bus Interface", "com.asianux.btagent.adaptor")
- Q_CLASSINFO("D-Bus Introspection", ""
- " <interface name=\"com.asianux.btagent.adaptor\">\n"
- " <method name=\"test\">\n"
- " <arg name=\"state\" type=\"s\" direction=\"out\"/>\n"
- " </method> \n"
- " <signal name=\"stateChange\"> \n"
- " <arg type=\"s\" direction=\"out\"/>\n"
- " </signal> \n"
- " </interface>\n"
- "")
- public:
- TestInterfaceAdaptor(QObject *parent);
- virtual ~TestInterfaceAdaptor();
- public:
- public slots:
- QString test();
- signals:
- void stateChange(QString str);
- };
- #endif /*TEST_ADAPTOR_H*/
(5)main.cpp
- #include <QApplication>
- #include <QtDBus>
- #include <QDebug>
- #include "test_adaptor.h"
- #include "test.h"
- int main(int argc,char *argv[])
- {
- QApplication app(argc,argv);
- Test *test = new Test();
- new TestInterfaceAdaptor(test);
- QDBusConnection conn = QDBusConnection::sessionBus();
- conn.registerObject("/",test);
- conn.registerService("com.asianux.btagent");
- return app.exec();
- }
(6)运行
可以在linux终端发送(dbus-send)和监控dbus(dbus-monitor)的信息。 dbus-send调用远程方法的一般形式是:$ dbus-send [--system | --session] --type=method_call --print-reply --dest=连接名 对象路径 接口名.方法名 参数类型:参数值 参数类型:参数值,dbus-send支持的参数类型包括:string, int32, uint32, double, byte, boolean。
启动程序后,先执行:dbus-send --session --print-reply --dest=com.asianux.btagent / com.asianux.btagent.adaptor.test,发送dbus信号,得到输出结果:
然后输入:dbus-monitor --session \ "type='signal',interface='com.asianux.btagent.adaptor',member='stateChange'",监控,从应用程序发出的DBus信号:
也可以通过qt自带的工具qdbusviewer查看和操作相应的DBus信号:
(7)除了上述方法,也可以使用glib的程序进行DBus通信。
main.c:
- #include <stdio.h>
- #include <stdlib.h>
- #include <glib.h>
- #include <dbus/dbus-glib.h>
- #include <dbus/dbus.h>
- static void bt_manager_changed_cb (DBusGProxy *proxy,
- const gchar *state,
- gpointer user_data)
- {
- printf("state = %s\n",state);
- }
- int main(int argc,char *argv[])
- {
- GMainLoop *loop = g_main_loop_new(NULL,TRUE);
- g_type_init();
- GError * error = NULL;
- DBusGConnection *gconn = dbus_g_bus_get(DBUS_BUS_SESSION, NULL);
- DBusGProxy *m_proxy = dbus_g_proxy_new_for_name(gconn, "com.asianux.btagent","/","com.asianux.btagent.adaptor");
- char *str = NULL;
- dbus_g_proxy_call(m_proxy, "test", &error,
- G_TYPE_INVALID,
- G_TYPE_STRING,&str,
- G_TYPE_INVALID);
- dbus_g_proxy_add_signal (m_proxy,
- "stateChange",
- G_TYPE_STRING,
- G_TYPE_INVALID);
- dbus_g_proxy_connect_signal (m_proxy,
- "stateChange",
- G_CALLBACK (bt_manager_changed_cb),
- NULL,
- NULL);
- printf("str = %s\n",str);
- g_main_loop_run(loop);
- }
makefile:
- all:
- gcc -g main.c -o test `pkg-config --cflags --libs dbus-1 gthread-2.0 glib-2.0 dbus-glib-1`
- clean:
- rm -rf *.o test
运行结果(先启动最上的服务器qt程序):
2、代码二
(1)qdbus.h
- #ifndef QDBUS
- #define QDBUS
- #include <QtCore>
- /*dbus-send --session --print-reply --dest=com.asianux.btagent2 / com.asianux.btagent2.interface.slotInterface string:"helloworld"*/
- class DeviceManager : public QObject
- {
- Q_OBJECT
- Q_CLASSINFO("D-Bus Interface", "com.asianux.btagent2.interface")
- public:
- // DeviceManager(){}
- // ~DeviceManager(){}
- public slots:
- void slotInterface(QString);
- };
- #endif // QDBUS
(2)main.cpp
- #include <QCoreApplication>
- #include <QObject>
- #include <QtDBus>
- #include "qdbus.h"
- void DeviceManager::slotInterface(QString str)
- {
- qDebug() << "D-Bus Interface(com.asianux.btagent2.interface):" << str;
- }
- int main(int argc, char *argv[])
- {
- QCoreApplication a(argc, argv);
- QDBusConnection bus = QDBusConnection::sessionBus();
- if(!bus.registerService("com.asianux.btagent2")){
- qDebug() << bus.lastError().message();
- exit(1);
- }
- DeviceManager *deviceManager = new DeviceManager();
- bus.registerObject("/", deviceManager, QDBusConnection::ExportAllSlots);
- return a.exec();
- }
运行结果:
3、其他网上代码
D-Bus的QT4绑定
创建Service并且注册Object:
// 用于建立到session bus的连接 // 在session bus上注册名为"com.test.hotel"的service // 注册名为"/hotel/registry"的object。 // 表示把类Hotel的所有Slot都导出为这个Object的method |
再看一下Hotel类的定义。
class Hotel:public QObject // 定义Interface名称为"com.test.hotel.registry" |
运行这个程序,我们可以使用qdbusviewer查看和操作这个Object。
// 用来构造一个在D-Bus上传递的Message // 发送Message |
通过QDBusInterface 访问Service
// 创建QDBusInterface QDBusConnection::sessionBus()); qPrintable(QDBusConnection::sessionBus(). lastError().message()); // 呼叫远程的checkIn,参数为num_room |
从D-Bus XML自动生成Proxy类
// 初始化自动生成的Proxy类com::test::hotel::registry |
使用Adapter注册Object
<method name="query">
int main(int argc,char*argv[]) |
运行这个应用程序,我们从qdbusviewer上可以看到,只有checkIn和checkOut两个method被发布。
4、其他细节
(1)如果想使用system bus(自启动服务参考上述),需要在/etc/dbus-1/system.d/目录下创建一个配置文件my.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">
- <allow own="com.asianux.btagent2"/>
- <allow send_destination="com.asianux.btagent2"/>
- <allow receive_sender="com.asianux.btagent2"/>
- </policy>
- </busconfig>
(2)可以参考通过QDbus实现的Qt检测U盘的例子:http://download.csdn.net/detail/taiyang1987912/8686677
(3)除了DBus,也可使用SIGHUP信号用于进程间通信,比如重写了配置文件,又不想重启程序就让配置生效,可以往该程序的进程发送一个SIGHUP信号:killall -HUP <进程名称>或kill -HUP <进程号>,可能因以前的系统没有提供用户自定义信号 SIGUSR1 和 SIGUSR1 ,而对于一个没有终端的守护进程来说是不可能收到 SIGHUP 信号的,所以就把 SIGHUP 当用户自定义信号使用。
- #include<stdio.h>
- #include <stdlib.h>
- #include<signal.h>
- char**args;
- void exithandle(int sig)
- {
- printf("%s:(%d)sighup received\n", args[0], sig);
- exit(0);
- }
- int main(int argc,char **argv)
- {
- args = argv;
- signal(SIGHUP,exithandle);
- while(1) sleep(1);
- return 0;
- }
运行程序,打开另终端发送killall -HUP ./sighupcode,则会处理SIGHUP信号:
三、详解之C代码
1、代码
使用C语言调用dbus的底层函数编写一个远程调用的示例代码,代码很简单,没使用GObject等一些复杂的库。
(1)method_send.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <dbus/dbus-glib.h>
- #include <dbus/dbus.h>
- #include <unistd.h>
- /*gcc -o method_send method_send.c -I/usr/include/glib-2.0 -I/usr/include/dbus-1.0 -I/usr/lib64/glib-2.0/include -I/usr/lib64/dbus-1.0/include -lglib-2.0 -ldbus-glib-1*/
- void reply_to_method_call(DBusMessage * msg, DBusConnection * conn)
- {
- DBusMessage * reply;
- DBusMessageIter arg;
- char * param = NULL;
- dbus_bool_t stat = TRUE;
- dbus_uint32_t level = 2010;
- dbus_uint32_t serial = 0;
- //从msg中读取参数,这个在上一次学习中学过
- if(!dbus_message_iter_init(msg,&arg))
- printf("Message has noargs\n");
- else if(dbus_message_iter_get_arg_type(&arg)!= DBUS_TYPE_STRING)
- printf("Arg is notstring!\n");
- else
- dbus_message_iter_get_basic(&arg,& param);
- if(param == NULL) return;
- //创建返回消息reply
- reply = dbus_message_new_method_return(msg);
- //在返回消息中填入两个参数,和信号加入参数的方式是一样的。这次我们将加入两个参数。
- dbus_message_iter_init_append(reply,&arg);
- if(!dbus_message_iter_append_basic(&arg,DBUS_TYPE_BOOLEAN,&stat)){
- printf("Out ofMemory!\n");
- exit(1);
- }
- if(!dbus_message_iter_append_basic(&arg,DBUS_TYPE_UINT32,&level)){
- printf("Out ofMemory!\n");
- exit(1);
- }
- //发送返回消息
- if( !dbus_connection_send(conn, reply,&serial)){
- printf("Out of Memory\n");
- exit(1);
- }
- dbus_connection_flush (conn);
- dbus_message_unref (reply);
- }
- void listen_dbus()
- {
- DBusMessage * msg;
- DBusMessageIter arg;
- DBusConnection * connection;
- DBusError err;
- int ret;
- char * sigvalue;
- dbus_error_init(&err);
- //创建于session D-Bus的连接
- connection =dbus_bus_get(DBUS_BUS_SESSION, &err);
- if(dbus_error_is_set(&err)){
- fprintf(stderr,"ConnectionError %s\n",err.message);
- dbus_error_free(&err);
- }
- if(connection == NULL)
- return;
- //设置一个BUS name:test.wei.dest
- ret =dbus_bus_request_name(connection,"test.wei.dest",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
- if(dbus_error_is_set(&err)){
- fprintf(stderr,"Name Error%s\n",err.message);
- dbus_error_free(&err);
- }
- if(ret !=DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
- return;
- //要求监听某个singal:来自接口test.signal.Type的信号
- dbus_bus_add_match(connection,"type='signal',interface='test.signal.Type'",&err);
- dbus_connection_flush(connection);
- if(dbus_error_is_set(&err)){
- fprintf(stderr,"Match Error%s\n",err.message);
- dbus_error_free(&err);
- }
- while(1){
- dbus_connection_read_write(connection,0);
- msg =dbus_connection_pop_message (connection);
- if(msg == NULL){
- sleep(1);
- continue;
- }
- if(dbus_message_is_signal(msg,"test.signal.Type","Test")){
- if(!dbus_message_iter_init(msg,&arg))
- fprintf(stderr,"Message Has no Param");
- else if(dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_STRING)
- g_printerr("Param isnot string");
- else
- dbus_message_iter_get_basic(&arg,&sigvalue);
- }else if(dbus_message_is_method_call(msg,"test.method.Type","Method")){
- //我们这里面先比较了接口名字和方法名字,实际上应当现比较路径
- if(strcmp(dbus_message_get_path(msg),"/test/method/Object") == 0)
- reply_to_method_call(msg,connection);
- }
- dbus_message_unref(msg);
- }
- }
- int main( int argc , char ** argv)
- {
- listen_dbus();
- return 0;
- }
(2)method_recv.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <dbus/dbus-glib.h>
- #include <dbus/dbus.h>
- #include <unistd.h>
- /*gcc -o method_recv method_recv.c -I/usr/include/glib-2.0 -I/usr/include/dbus-1.0 -I/usr/lib64/glib-2.0/include -I/usr/lib64/dbus-1.0/include -lglib-2.0 -ldbus-glib-1*/
- //建立与session D-Bus daemo的连接,并设定连接的名字,相关的代码已经多次使用过了
- DBusConnection * connect_dbus()
- {
- DBusError err;
- DBusConnection * connection;
- int ret;
- //Step 1: connecting session bus
- dbus_error_init(&err);
- connection =dbus_bus_get(DBUS_BUS_SESSION, &err);
- if(dbus_error_is_set(&err)){
- fprintf(stderr,"ConnectionErr : %s\n",err.message);
- dbus_error_free(&err);
- }
- if(connection == NULL)
- return NULL;
- //step 2: 设置BUS name,也即连接的名字。
- ret =dbus_bus_request_name(connection,"test.wei.source",DBUS_NAME_FLAG_REPLACE_EXISTING,&err);
- if(dbus_error_is_set(&err)){
- fprintf(stderr,"Name Err :%s\n",err.message);
- dbus_error_free(&err);
- }
- if(ret !=DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
- return NULL;
- return connection;
- }
- void send_a_method_call(DBusConnection * connection,char * param)
- {
- DBusError err;
- DBusMessage * msg;
- DBusMessageIter arg;
- DBusPendingCall * pending;
- dbus_bool_t * stat;
- dbus_uint32_t * level;
- dbus_error_init(&err);
- //针对目的地地址,请参考图,创建一个method call消息。Constructs a new message to invoke a method on a remote object.
- msg =dbus_message_new_method_call ("test.wei.dest","/test/method/Object","test.method.Type","Method");
- if(msg == NULL){
- g_printerr("MessageNULL");
- return;
- }
- //为消息添加参数。Appendarguments
- dbus_message_iter_init_append(msg, &arg);
- if(!dbus_message_iter_append_basic(&arg, DBUS_TYPE_STRING,&stat)){
- g_printerr("Out of Memory!");
- exit(1);
- }
- //发送消息并获得reply的handle。Queues amessage to send, as withdbus_connection_send() , but also returns aDBusPendingCall used to receive a reply to the message.
- if(!dbus_connection_send_with_reply (connection, msg,&pending, -1)){
- g_printerr("Out of Memory!");
- exit(1);
- }
- if(pending == NULL){
- g_printerr("Pending CallNULL: connection is disconnected ");
- dbus_message_unref(msg);
- return;
- }
- dbus_connection_flush(connection);
- dbus_message_unref(msg);
- //waiting a reply,在发送的时候,已经获取了methodreply的handle,类型为DBusPendingCall。
- // block until we recieve a reply, Block until the pendingcall is completed.
- dbus_pending_call_block (pending);
- //get the reply message,Gets thereply, or returns NULL if none has been received yet.
- msg =dbus_pending_call_steal_reply (pending);
- if (msg == NULL) {
- fprintf(stderr, "ReplyNull\n");
- exit(1);
- }
- // free the pendingmessage handle
- dbus_pending_call_unref(pending);
- // read the parameters
- if(!dbus_message_iter_init(msg, &arg))
- fprintf(stderr, "Message hasno arguments!\n");
- else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_BOOLEAN)
- fprintf(stderr, "Argument isnot boolean!\n");
- else
- dbus_message_iter_get_basic(&arg, &stat);
- if (!dbus_message_iter_next(&arg))
- fprintf(stderr, "Message hastoo few arguments!\n");
- else if (dbus_message_iter_get_arg_type(&arg) != DBUS_TYPE_UINT32 )
- fprintf(stderr, "Argument isnot int!\n");
- else
- dbus_message_iter_get_basic(&arg, &level);
- printf("Got Reply: %d,%d\n", stat, level);
- dbus_message_unref(msg);
- }
- int main( int argc , char ** argv)
- {
- DBusConnection * connection;
- connection = connect_dbus();
- if(connection == NULL)
- return -1;
- send_a_method_call(connection,"Hello, D-Bus");
- return 0;
- }
(3)编译运行(先运行method_send)
编译method_send.c:
编译method_recv.c(得到运行结果):
四、总结
(1)本文仅提供代码的测试,其他的具体函数的意义请查阅相应的帮助文档。
(2)源码已经打包上传到csdn上,可登录下载(http://download.csdn.net/detail/taiyang1987912/8687183)。
(3)若有建议,请留言,在此先感谢!
http://blog.csdn.net/taiyang1987912/article/details/45642079
Qt浅谈之二十七进程间通信之QtDBus good的更多相关文章
- Qt浅谈之二十七进程间通信之QtDBus
一.简介 DBus的出现,使得Linux进程间通信更加便捷,不仅可以和用户空间应用程序进行通信,而且还可以和内核的程序进行通信,DBus使得Linux变得更加智能,更加具有交互性. DB ...
- Qt浅谈之二十App自动重启及关闭子窗口
一.简介 最近因项目需求,Qt程序一旦检测到错误,要重新启动,自己是每次关闭主窗口的所有子窗口但有些模态框会出现问题,因此从网上总结了一些知识点,以备以后的应用. 二.详解 1.Qt结构 int ma ...
- Qt浅谈之二十App自动重启及关闭子窗口(六种方法)
一.简介 最近因项目需求,Qt程序一旦检测到错误,要重新启动,自己是每次关闭主窗口的所有子窗口但有些模态框会出现问题,因此从网上总结了一些知识点,以备以后的应用. 二.详解 1.Qt结构 int ma ...
- Qt浅谈之二十六图片滑动效果
一.简介 博客中发现有作者写的仿360的代码,觉得其中图片滑动的效果很有意思,特提取其中的代码.并加上类似mac的画面移动的动画效果. 二.详解 1.代码一:界面滑动(QWidget) (1)slid ...
- Qt浅谈之二十一log调试日志
一.简单介绍 近期因调试code时,想了解程序的流程,但苦于没有一个简易的日志记录,不停使用qDebug打印输出,而终于提交代码时得去多次删除信息打印,有时还会出现新改动的代码分不清是哪些部分.而使用 ...
- Qt浅谈之二:钟表(时分秒针)
一.简介 QT编写的模拟时钟,demo里的时钟只有时针和分针,在其基础上添加了秒针,构成了一个完整的时钟.能对2D绘图中坐标系统.平移变换(translate).比例变换(scale).旋转变换(ro ...
- Qt浅谈内存泄露(总结)
Qt浅谈内存泄露(总结) 来源 http://blog.csdn.net/taiyang1987912/article/details/29271549 一.简介 Qt内存管理机制:Qt 在内部能够维 ...
- Qt浅谈之总结(整理)
Qt浅谈之总结(整理) 来源 http://blog.csdn.net/taiyang1987912/article/details/32713781 一.简介 QT的一些知识点总结,方便以后查阅. ...
- Android开发-浅谈架构(二)
写在前面的话 我记得有一期罗胖的<罗辑思维>中他提到 我们在这个碎片化 充满焦虑的时代该怎么学习--用30%的时间 了解70%该领域的知识然后迅速转移芳草鲜美的地方 像游牧民族那样.原话应 ...
随机推荐
- cordova通过指纹插件进行指纹验证
原文:cordova通过指纹插件进行指纹验证 版权声明:本文为博主原创文章,转载须注明出处,博客地址:https://blog.csdn.net/wx13227855087 https://blog. ...
- error: openssl/md5.h: No such file or directory
出现:error: openssl/md5.h: No such file or directory 原因是openssl-devel没有安装,运行: yum install libssl-dev 就 ...
- flex新的心得
主要是对于flex: 与width: 的心得,先看代码. <head> <meta charset="UTF-8"> <meta name=" ...
- CUDA一维纹理内存
纹理一词来源于GPU图形世界,GPU通用并行计算"盗用"了纹理一词,定义了一个纹理内存的概念.纹理内存缓存在 设备上,在某些情况下能减少对内存的请求并降低内存带宽的使用,是专门为那 ...
- linux 静态库和动态库(共享库)的制作与使用(注意覆盖问题)
一.linux操作系统支持的函数库分支 静态库:libxxx.a,在编译时就将库编译进可执行程序 优点:程序的运行环境中不需要外部的函数库 缺点:可执行程序大 动态库:又称共享库,libxxx.so, ...
- 通用javascript脚本函数库
/* 名字:Common.js 功能:通用javascript脚本函数库 包括: 1.Trim(str)--去除字符串两边的空格 2.XMLEncode(str)--对字符串进行XML编码 3.Sho ...
- 机器学习:scikit-learn 做笑脸识别 (SVM, KNN, Logisitc regression)
scikit-learn 是 Python 非常强大的一个做机器学习的包,今天介绍scikit-learn 里几个常用的分类器 SVM, KNN 和 logistic regression,用来做笑脸 ...
- WPF中制作立体效果的文字或LOGO图形
原文:WPF中制作立体效果的文字或LOGO图形 较久之前,我曾写过一篇:"WPF绘制党徽(立体效果,Cool) "的博文.有感兴趣的朋友来EMAIL问是怎么制作的?本文解决此类问题 ...
- QSplitter实现自由伸缩滑动窗口部件(要在m_pSplitter中加入frame_4之前,给frame_4设置样式;之后设置无效)
实现代码如下: #include <QSplitter> QSplitter *m_pSplitter; m_pSplitter = new QSplitter(ui->frame_ ...
- ISO9000 质量管理和质量保证系列国际标准
(一)ISO9000认证的背景国际标准化组织(ISO)是世界上最主要的非政府间国际标准化机构,成立于二次世界大战以后,总部位于瑞士日内瓦.该组织的目的是在世界范围内促进标准化及有关工作的发展,以利于国 ...