【QML 与 C++ 之间的通讯机制】QML 与 Qt 通讯:讲解如何在QML 中使用C++类,以及如何在C++ 中获取QML的内容

简介: 【QML 与 C++ 之间的通讯机制】QML 与 Qt 通讯:讲解如何在QML 中使用C++类,以及如何在C++ 中获取QML的内容

C++和QML之间的分布关系

特点/比较维度 QML C++ Qt
语言和语法 基于JavaScript和JSON的声明性语言 基于C++的面向对象编程库
适用领域 丰富的图形用户界面(GUI) 复杂业务逻辑和底层功能
性能 相对较低,因为基于解释型语言 较高,因为基于编译型语言
开发效率 较高,语法简洁且易于学习 较低,适用于复杂功能开发
社区和支持 较新,可能资源和支持相对较少 庞大的开发者社区和丰富的资源
集成 可与C++ Qt共同使用 可与QML共同使用

在同一可执行文件中,C++和QML共享同一个进程,但是它们运行在不同的线程上。
在Qt中,C++和QML都是在同一个进程中运行的,因为QML只是一种声明式的UI语言,它通过Qt Quick框架解析和渲染,最终也是由C++代码实现的。因此,当我们编译并运行一个Qt应用程序时,只会生成一个可执行文件,在该可执行文件运行时,C++和QML都是在同一个进程中运行的。
当一个Qt应用程序启动时,其主线程会负责初始化QML引擎、创建C++对象、加载QML文件等操作。在此过程中,QML引擎会解析QML文件,将其转换为C++对象,并通过C++与QML之间的绑定实现交互。因此,虽然C++和QML的代码是分离的,但它们是在同一个进程中运行的,可以通过信号和槽、属性绑定、函数调用等方式进行通信。
在Qt中,GUI线程是Qt的主线程,负责处理GUI事件和更新UI界面。因此,QML中的所有UI操作都必须在GUI线程中执行,否则会引发线程安全问题。而C++代码可以在任何线程中执行,但是需要注意线程安全问题。
当我们在C++中调用QML中的方法或属性时,Qt会自动将该调用转发到GUI线程中执行。同时,当我们在QML中调用C++中的方法或属性时,Qt也会自动将该调用转发到C++所在的线程中执行。这种线程切换是由Qt自动完成的,我们不需要手动干预。
总之,虽然C++和QML运行在不同的线程中,但是它们之间的交互是由Qt自动完成的,我们只需要遵守Qt的线程安全规则即可。


C++和QML之间的通讯方式


属性绑定(Property Binding)
信号与槽:可以通过信号与槽:进行双向通讯(Signals and Slots)
直接调用(Direct Call){基于_INVOKABLE}
上下文属性(Context Property){基于Q_PROPERTY}
Qt Remote Objects
标准IPC通讯(Inter-Process Communication)


几种通讯方式的对比

方式 优点 缺点 适用场景
Property绑定 简单易用、代码量少、实时响应自动更新属性值,适合实现简单的交互。 只能在QML中读取和写入C++对象的属性,无法直接调用C++对象的函数。 适用于只需要传递属性值的场景
Signal/Slot 可以传递复杂类型的参数,
可以在QML中直接调用C++对象的函数,同时也可以实现C++对象向QML发送消息。
需要编写一些额外的代码来处理信号和槽,代码量相对较多,不够自动化 数据量小,通讯频繁的场景,
实时交互的场景,如UI元素状态变化的反馈等
Q_INVOKABLE 可以实现双向通信,可以直接调用C++函数 只能读取和写入属性,无法调用C++对象的函数 需要频繁调用C++函数的场景,
快速调用C++函数的场景,如计算等
Q_PROPERTY 支持属性自动同步,线程安全 无法直接在QML中修改复杂数据类型,不支持多线程异步调用 属性数据量较小,需要频繁同步的场景
Context属性 可以实现双向通信,可以像JavaScript对象一样使用C++对象 需要手动编写C++类并注册,不够自动化 适用于需要在QML中使用C++对象的场景,如复杂业务逻辑的处理等
Qt Remote Objects 支持远程通讯,支持多线程异步调用 实现较为复杂,需要对网络编程有一定了解 分布式应用场景

Qt Remote Objects (QtRO) 和标准 IPC (Inter-Process Communication) 之间的对比

优点:

  • QtRO:
    高度可扩展性和灵活性,支持跨进程、跨计算机的通信。
    通过元对象系统进行数据序列化和远程方法调用,可以在不同的平台和编程语言之间进行通信。
    支持 Qt Quick 的集成和信号-槽机制,简化了开发人员的工作。
    提供了方便的工具和库,使得开发和调试更加容易。
  • 标准IPC:
    比较通用,可以使用多种不同的通信协议和方式,例如管道、套接字、共享内存等。
    相对简单,可以用于小规模应用或快速原型开发。
    比较成熟,有大量的第三方库和工具可供使用。

缺点:

  • QtRO:
    学习曲线较陡峭,需要掌握元对象系统和远程方法调用等概念。
    不支持非 Qt 平台和语言,可能存在一定的局限性。
    在跨网络较远的计算机之间通信时,可能存在较高的延迟和网络负载。
  • 标准IPC:
    通信协议和方式的选择可能存在困难,需要根据具体需求进行选择和调整。
    不支持 Qt Quick 的集成和信号-槽机制,需要手动实现通信和数据传递。
    缺乏一些高级功能和工具,例如自动序列化、远程对象代理等。

Q_INVOKABLE和Q_PROPERTY的区别

  • Q_INVOKABLE宏用于声明一个成员函数可以从外部调用,即可以通过Qt元对象系统调用,类似于C++中的public成员函数,但是可以通过元对象系统跨线程和跨进程调用。Q_INVOKABLE与QMetaObject::invokeMethod均由元对象系统唤起。
    Q_INVOKABLE是个空宏,目的在于让moc识别。 使用Q_INVOKABLE来修饰成员函数,目的在于被修饰的成员函数能够被元对象系统所唤起。
    例如,你可以使用Q_INVOKABLE宏声明一个槽函数,然后使用connect函数将该槽函数与信号连接起来。
  • Q_PROPERTY宏用于声明一个类的属性,并提供读写函数和通知函数(可选)。属性是一个类的状态或特性,可以被设置和读取。通过Q_PROPERTY,可以使用元对象系统来访问这些属性,并且可以将它们与Qt框架中的其他类进行交互。例如,你可以使用Q_PROPERTY宏声明一个类的颜色属性,并实现一个读取和设置该属性的函数。

这两个宏通常用于不同的情况,但是可以同时使用。如果你希望从外部调用一个类的函数并且需要将该类的状态作为属性访问,则可以使用Q_INVOKABLE和Q_PROPERTY宏来实现这两个功能。

因此,Q_PROPERTYQ_INVOKABLE的作用不同,前者是用于定义C++类的属性,后者是用于将C++类的函数暴露给QML。
需要注意的是,Q_PROPERTY也可以使用READ、WRITE、NOTIFY等参数来指定属性的读写方式和通知机制,而Q_INVOKABLE没有这些参数。
总的来说,Q_PROPERTYQ_INVOKABLE是Qt框架中用于定义C++类成员的宏,它们的用途不同,分别用于定义属性和函数,并且都可以在QML中使用。


属性绑定

可以在 C++ 代码中使用 Q_PROPERTY 宏定义一个属性,并且通过 QObject::setProperty() 方法设置属性的值,而在 QML 中,您可以使用 Binding 来绑定 QML 中的属性和 C++ 中的属性。这样,当 C++ 中的属性发生变化时,QML 中的属性也会相应地发生变化;反过来,当 QML 中的属性发生变化时,C++ 中的属性也会相应地发生变化。


需要注意的是,实现双向绑定,需要在 C++ 代码中使用 QQmlProperty 实例对象来设置和获取 QML 中的属性,而不是使用 QObject::setProperty() QObject::property() 方法。


因为QObject::setProperty()QObject::property() 方法只能设置和获取 C++ 对象自身的属性,而无法访问到 QML 中的属性。

信号和槽

通过定义 C++ 对象的信号和槽,可以在 QML 中监听并处理这些信号。这种方式实现了双向通信,即 C++ 对象可以向 QML 发送信号,而 QML 也可以向 C++ 对象发送信号。但需要注意的是,信号和槽的参数类型必须在 QML 中可识别,否则会报错。


直接调用

在 QML 中通过 C++ 对象的方法名直接调用 C++ 对象的方法是基于_INVOKABLE。在 C++ 中,使用_Q_INVOKABLE宏声明的成员函数可以在 QML 中使用方法名直接调用。
这些成员函数必须是公共的,并且它们必须符合一定的函数签名规则,才能在 QML 中使用。这种方法比较直接,但需要注意的是,只有在 C++ 对象已经被实例化并在 QML 中注册后才能进行调用。


Qt Remote Objects

Qt Remote Objects是Qt提供的一种远程通信机制,它基于Qt的信号槽机制和RPC(Remote Procedure Call)技术实现了跨进程、跨网络的通信。
Qt Remote Objects的通信方式可以分为两种:
信号槽机制:与本地信号槽机制类似,Qt Remote Objects也支持跨进程、跨网络的信号槽连接,可以实现异步通信和事件驱动。
远程调用:Qt Remote Objects还支持远程过程调用(RPC),可以实现跨进程、跨网络的同步调用和返回结果。
在使用Qt Remote Objects时,需要先定义一个远程对象(Remote Object),并将其注册到Qt远程对象服务器(Remote Object Host)上。远程对象可以包含信号和槽,也可以包含远程方法(Remote Method),通过远程方法可以实现跨进程、跨网络的函数调用。
Qt Remote Objects支持多种通信协议,如纯TCP、SSL/TLS、WebSocket等。可以根据需要选择不同的协议进行通信。
总之,Qt Remote Objects是一种强大的跨进程、跨网络通信机制,可以方便地实现分布式应用程序的开发。


上下文属性

在QML和C++之间通讯时,利用上下文属性(Context Property)是基于Q_PROPERTY,通过将 C++ 对象注册为 QML 引擎的上下文属性,可以在 QML 中访问该对象的属性和方法。这种方法实现了双向通信,但需要注意的是,上下文属性只能在 QML 引擎的主线程中使用,否则会导致线程错误。


代码示例:

  • 属性绑定:使用Qt的QML C++绑定功能来将C++代码与QML界面进行交互

首先,您需要在C++代码中定义一个QObject派生类,然后将其注册到QML引擎中。
接下来可以在QML中使用该类的实例,并调用其公共槽和属性。
具体步骤如下:


  1. 在C++代码中定义一个QObject派生类,例如:
class MyObject : public QObject {
    Q_OBJECT public:
    Q_INVOKABLE void myMethod(QString param);
    Q_PROPERTY(QString myProperty READ myProperty WRITE setMyProperty NOTIFY myPropertyChanged)
    QString myProperty() const;
    void setMyProperty(const QString& value); signals:
    void mySignal(QString message);
    void myPropertyChanged(); private:
    QString m_myProperty; 
};

  1. 在main函数中注册该类到QML引擎中,例如:
QQmlApplicationEngine engine;
qmlRegisterType<MyObject>("com.mycompany", 1, 0, "MyObject");
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

  1. 在QML中使用该类的实例,并调用其公共槽和属性,例如:
import com.mycompany 1.0 MyObject {
      id: myObject
      myProperty: "Hello, World!"
      onMyPropertyChanged: console.log("myProperty changed to", myProperty)
      Component.onCompleted: myMethod("Hello from QML!")
     }


  • Q_INVOKABLE :使用Qt的QML中的JavaScript API来调用C++的Qt Quick模块提供的接口
  1. 在C++中定义一个QObject子类,该子类包含一个或多个Q_INVOKABLE方法,这些方法将成为QML中可调用的接口。
  2. 在QML文件中导入C++模块并创建一个C++对象。
  3. 使用JavaScript API中的call()方法来调用C++对象的方法。

C++代码:

class MyObject : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE void myMethod(QString arg);
};
void MyObject::myMethod(QString arg)
{
qDebug() << "Called with argument:" << arg;
}

QML代码:

import MyModule 1.0
MyObject {
id: myObject
}
Button {
text: "Call C++ method"
onClicked: {
myObject.myMethod("Hello, world!");
}
}

在这个示例中,我们定义了一个名为MyObject的C++类,并将其导入到QML中。我们还在QML中创建了一个名为myObject的MyObject对象,并在按钮的点击事件中调用了myMethod()方法。
当按钮被点击时,myMethod()方法将被调用,并将字符串“Hello, world!”作为参数传递给它。该方法将打印出“Called with argument: Hello, world!”的消息。


  • 信号和槽机制 :在 QML 中,可以使用 Qt 的信号和槽机制来实现与 C++ 的交互

在 C++ 中定义一个槽函数:

class MyObject : public QObject 
 {
    Q_OBJECT public slots:
    void mySlot() {
        qDebug() << "My slot is called";
    } 
 };

在 QML 中定义一个信号:

import QtQuick 2.0
Rectangle {
    signal mySignal()
}

在 QML 中连接信号和槽:

import QtQuick 2.0
Rectangle {
    signal mySignal()
    Connections {
        target: myObject // myObject 是在 C++ 中创建的对象
        onMySignal: myObject.mySlot()
    }
    MouseArea {
        anchors.fill: parent
        onClicked: mySignal()
    }
}

当点击 MouseArea 时,会触发 mySignal() 信号,然后在 Connections 中将该信号连接到 myObject 对象的 mySlot() 槽函数中。


  • 上下文属性(Context Property):基于Q_PROPERTY进行上下文属性在QML和C++之间通讯.

我们可以在C++中定义一个QObject子类,并使用Q_PROPERTY宏定义一个属性:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) 
 public:
    MyObject(QObject *parent = nullptr) : QObject(parent), m_value(0) {}
    int value() const { return m_value; }
    void setValue(int value) { m_value = value; emit valueChanged(); 
    } 
 signals:
    void valueChanged(); 
 private:
    int m_value;
};

然后,我们可以将MyObject类注册为QML类型,并将其实例化并绑定到QML中的一个上下文属性上:

qmlRegisterType<MyObject>("MyLib", 1, 0, "MyObject");
MyObject myObject; QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myObject", &myObject);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

在QML中,我们就可以访问myObject的属性和方法

import MyLib 1.0
Text {
    text: "My object value: " + myObject.value
    MouseArea {
        anchors.fill: parent
        onClicked: myObject.setValue(myObject.value + 1)
    } 
 }

  • Qt Remote Objects(QRO):需要分为服务端和客户端两部分

服务端:

#include <QObject>
#include <QtRemoteObjects>
#include <QCoreApplication>
class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)
public:
    MyObject(QObject *parent = nullptr) : QObject(parent), m_value(0)
    {
        m_replica.setData(m_value);
        m_replica.setParent(this);
        connect(this, &MyObject::valueChanged, [this](){
            m_replica.changeData(m_value);
        });
    }
    int value() const { return m_value; }
    void setValue(int value) { m_value = value; emit valueChanged(); }
signals:
    void valueChanged();
private:
    int m_value;
    QRemoteObjectReplica<MyObject> m_replica;
};
int main(int argc, char **argv)
{
    QCoreApplication app(argc, argv);
    // 注册MyObject为远程对象
    QRemoteObjectHost host;
    host.enableRemoting(new MyObject(&app), "MyObject");
    return app.exec();
}

服务端:

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtRemoteObjects 1.0
ApplicationWindow {
    width: 400
    height: 400
    visible: true
    title: "My Object Viewer"
    RemoteObject {
        id: myObject
        node: "tcp://localhost:5555"
        typeName: "MyObject"
    }
    Column {
        anchors.centerIn: parent
        spacing: 20
        Text {
            text: "Value: " + myObject.value
        }
        Button {
            text: "Increase"
            onClicked: myObject.setValue(myObject.value + 1)
        }
    }
}

结语

在我们的编程学习之旅中,理解是我们迈向更高层次的重要一步。然而,掌握新技能、新理念,始终需要时间和坚持。从心理学的角度看,学习往往伴随着不断的试错和调整,这就像是我们的大脑在逐渐优化其解决问题的“算法”。

这就是为什么当我们遇到错误,我们应该将其视为学习和进步的机会,而不仅仅是困扰。通过理解和解决这些问题,我们不仅可以修复当前的代码,更可以提升我们的编程能力,防止在未来的项目中犯相同的错误。

我鼓励大家积极参与进来,不断提升自己的编程技术。无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。

目录
相关文章
|
14天前
|
存储 监控 算法
基于 C++ 哈希表算法实现局域网监控电脑屏幕的数据加速机制研究
企业网络安全与办公管理需求日益复杂的学术语境下,局域网监控电脑屏幕作为保障信息安全、规范员工操作的重要手段,已然成为网络安全领域的关键研究对象。其作用类似网络空间中的 “电子眼”,实时捕获每台电脑屏幕上的操作动态。然而,面对海量监控数据,实现高效数据存储与快速检索,已成为提升监控系统性能的核心挑战。本文聚焦于 C++ 语言中的哈希表算法,深入探究其如何成为局域网监控电脑屏幕数据处理的 “加速引擎”,并通过详尽的代码示例,展现其强大功能与应用价值。
39 1
|
3月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
1月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
58 12
|
1月前
|
SQL 数据库连接 数据库
在C++的QT框架中实现SQLite数据库的连接与操作
以上就是在C++的QT框架中实现SQLite数据库的连接与操作的基本步骤。这些步骤包括创建数据库连接、执行SQL命令、处理查询结果和关闭数据库连接。在实际使用中,你可能需要根据具体的需求来修改这些代码。
147 14
|
2月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
60 16
|
2月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
2月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
2月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
156 6
|
3月前
|
存储 监控 算法
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
在数字化办公时代,公司监控上网软件成为企业管理网络资源和保障信息安全的关键工具。本文深入剖析C++中的链表数据结构及其在该软件中的应用。链表通过节点存储网络访问记录,具备高效插入、删除操作及节省内存的优势,助力企业实时追踪员工上网行为,提升运营效率并降低安全风险。示例代码展示了如何用C++实现链表记录上网行为,并模拟发送至服务器。链表为公司监控上网软件提供了灵活高效的数据管理方式,但实际开发还需考虑安全性、隐私保护等多方面因素。
53 0
公司监控上网软件架构:基于 C++ 链表算法的数据关联机制探讨
|
2月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!

推荐镜像

更多