2018-11-15

QT

N_QT 6 QT with ZLMediaKit N_QT N_QT 6 QT with 海康SDK QT with FFmpeg QT with Interception 驱动键鼠 QT with librtp QT with libVLC QT with MySQL QT with OP QT with OpenCV QT with Visual Studio

QT 官页下载

QT archive 下载页
QT Creator archive 下载页
官方 API reference 页面 Wiki Main

Qt是一种更快, 更智能的方式来为多个屏幕创建创新的设备, 现代用户界面和应用程序; 它是一个跨平台的C++应用程序开发框架; 它提供给开发者建立图形用户界面所需的功能, 广泛用于开发图形用户界面程序, 也可用于开发非图形用户界面(比如命令行界面)程序;

in short 本质上可以说是个 大而全的 C++ lib库

QT 安装目录结构

  • ./5.11.2/5.11.2/ 这个目录下存放, 安装的qt sdk版本 (发程序依赖的dll可从这复制)
  • ./5.11.2/Tools/ 这个目录下存放 qt qtcreator 和 mingw编译器

主目录结构

类库目录(SDK)

QT 项目管理 for .pro

QT6 默认使用CMake替代qmake 也就是统一使用CMakeLists.txt管理项目, 不建议使用 参考 N_Cmake

.pro就是工程文件, 它是 qmake 自动生成的用于生产makefile的配置文件, 对于app工程或者lib工程, 有以下这些经常使用的变量:

# 基本配置
QT += core gui
TARGET = MyApp
CONFIG += c++11
 
# 文件配置
SOURCES += main.cpp
HEADERS += widget.h
FORMS += widget.ui
 
# 模块管理
QT += network sql
 
# 路径配置
INCLUDEPATH += /path/to/include
LIBS += -L/path/to/lib -lmylib

HEADERS: 项目包含的C++头文件 SOURCES: 项目包含的源文件 INCLUDEPATH: 指定C++编译器搜索头文件路径 LIBS: 指定工程要链接的库 QT: 指定工程所要使用的Qt模块

指定工程配置和编译参数

CONFIG: 指定工程配置和编译参数

FORMS: 指定需要uic处理的ui文件 RESOURCES: 指定需要rcc处理的qrc文件 DEFINES: 指定预定义预处理器符号 VERSION: 指定目标库版本号 TARGET: 指定可执行文件或库的基本文件名, 默认为当前目录名 DESTDIR: 指定可执行文件放置的目录 DLLDESTDIR: 指定目标库文件放置的目录

# 当我们写CONFIG 变量时, 可以使用
# 如: CONFIG = qt release warn_off
# 或者
CONFIG = qt
CONFIG += release
CONFIG += warn_off

当想要移除某个设置时:

CONFIG -= warn_off

CONFIG 主要变量:

  • qt - 应用程序是一个Qt应用程序, 并且Qt库将会被连接;
  • debug: 编译有调试信息的可执行文件或则库
  • release: 编译不具有调试信息可执行文件或者库(如果同时指定debug release时, 只有debug有效)
  • warn_on - 编译器会输出尽可能多的警告信息; 如果”warn_off”被指定, 它将被忽略;
  • warn_off - 编译器会输出尽可能少的警告信息;
  • dll:动态编译库
  • staticlib: 静态编译库
  • plugin: 编译一个插件

QT 项目管理 for cmake

QT6 以后默认从qmake改为cmake 管理项目, 换句话说 .pro项目文件没了,改为CMakeLists.txt..

参考 N_Cmake

Getting started with CMake

A CMake project is defined by files written in the CMake language. The main file is called CMakeLists.txt, and is usually placed in the same directory as the actual program sources.

模块管理

引入QT模块

https://doc.qt.io/archives/qt-6.2/qtmodules.html

在安装目录的 ./QT\6.2.1\mingw81_64\modules 也能找到

find_package(Qt6 COMPONENTS Network REQUIRED) 
target_link_libraries(mytarget Qt6::Network)

链接项目内的子库

# 添加子目录(包含库的CMakeLists.txt)
add_subdirectory(math)
 
# 链接子库
add_library(MathFunctions math.cpp)  # 子目录中的CMakeLists.txt定义
target_link_libraries(my_target PRIVATE MathFunctions)

手动指定库路径

find_library(MY_LIB mylib PATHS /custom/path/lib)
find_path(MY_INCLUDE mylib.h PATHS /custom/path/include)
 
if (MY_LIB AND MY_INCLUDE)
    target_include_directories(my_target PRIVATE ${MY_INCLUDE})
    target_link_libraries(my_target PRIVATE ${MY_LIB})
else()
    message(FATAL_ERROR "库未找到")
endif()

项目资源使用及路径

右键项目目录 new Qt Qt Resource File html

cmakelist

qt_add_executable(my_app
    html.qrc
    main.cpp
)

关于路径可到编译到目录查看编译后的[name].qrc资源文件, 文本形式查看可看到注释路径

qrc:{qec的前缀}/{相对项目前缀}/{文件名}

QImage image;
image.load(":/res/img0.jpg");

实例 链接一个相机库

# X5_LIB 是变量名, toupnam 是dll
find_library(X5_LIB toupnam E:/test/toupnamsdk20250112/win/x64)
find_path(X5_INCLUDE toupnam.h E:/test/toupnamsdk20250112/inc)
if(NOT X5_LIB)
    message(FATAL_ERROR "toupnam library not found")
endif()
 
if(NOT X5_INCLUDE)
    message(FATAL_ERROR "toupnam.h not found")
endif()
 
# X5fCam 是项目名
add_executable(X5fCam
  main.cpp
)
target_include_directories(X5fCam PRIVATE ${X5_INCLUDE})
 
target_link_libraries(X5fCam PRIVATE Qt${QT_VERSION_MAJOR}::Core)
 
target_link_libraries(X5fCam PRIVATE ${X5_LIB})
 
  • 查找库:优先使用find_package,支持主流库(如Boost、OpenSSL)。
  • 链接目标:使用target_link_libraries,避免全局设置(如link_directories)。
  • 作用域
    • PRIVATE:仅当前目标使用。
    • PUBLIC:当前目标及依赖它的目标使用。
    • INTERFACE:仅依赖它的目标使用。
  • 子项目:通过add_subdirectory引入,直接链接库名。
  • 非标准路径:使用find_libraryfind_path手动定位。

实例 链接 ffmpeg 库

CMakeLists.txt

# cmake版本
cmake_minimum_required(VERSION 3.14)
# 项目名称
project(ffmpeg-test LANGUAGES CXX)
 
set(CMAKE_INCLUDE_CURRENT_DIR ON)
....
# C++ 17 support 
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 
# 引入qt5 core模块
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)
 
# 设置ffmpeg 头文件 目录变量
set(FFMPEG_HEAD_DIR D:/libarry/ffmpeg-n5.0-latest-win64-gpl-shared-5.0/include)
# 头文件查找目录
include_directories(${FFMPEG_HEAD_DIR} )
# 链接库查找目录
link_directories(D:/libarry/ffmpeg-n5.0-latest-win64-gpl-shared-5.0/lib)
# 可执行文件, QT create只能识别添加在这里的 头文件, 源文件
add_executable(ffmpeg-test
 main.cpp
)
 
#可执行文件链接库 target_link_libraries(可执行文件名 库名)
target_link_libraries(ffmpeg-test Qt${QT_VERSION_MAJOR}::Core)
target_link_libraries(ffmpeg-test avcodec )
target_link_libraries(ffmpeg-test avformat )
target_link_libraries(ffmpeg-test avutil )
#target_link_libraries(ffmpeg-test avfilter )
#target_link_libraries(ffmpeg-test swresample )
#target_link_libraries(ffmpeg-test swscale )
#target_link_libraries(ffmpeg-test postproc )

测试代码

extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
//#include <libavdevice/avdevice.h>
//#include <libavfilter/avfilter.h>
//#include <libswscale/swscale.h>
//#include <libavutil/log.h>
}
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qInfo()<<"hello ffmpeg  av_version_info = "<<av_version_info();
    return a.exec();
}

QT 信号与槽 机制

QT 元对象

Qt 的元对象系统(Meta-Object System)提供了对象之间通信的信号与槽机制, 运行时类型信息和动态属性系统;

元对象系统由以下三个基础组成:

  1. QObject 类是所有使用元对象系统的类的基类;
  2. 在一个类的 private 部分声明 Q_OBJECT 宏, 使得类可以使用元对象的特性, 如动态属性, 信号与槽;
  3. MOC(元对象编译器)为每个 QObject 的子类提供必要的代码来实现元对象系统的特性;
class SessionServer :public QObject
{
    Q_OBJECT
}

另: undefined reference to vtable for的错误, 原因是手动继承 Q_OBJECT 要移除重新加载, 或者删除编译缓存Makefile文件 即使派生自QT的对象已经继承了, 也需要Q_OBJECT` 宏定义!

in short 只有继承了QObject 类的类, 才具有信号和槽的能力!!!

自定义信号与槽

信号与槽(Signal & Slot)是 Qt 编程的基础, 也是 Qt 的一大创新; 因为有了信号与槽的编程机制, 在 Qt 中处理界面各个组件的交互操作时变得更加直观和简单;

信号(Signal)就是在特定情况下被发射的事件, 例如PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号, 一个 ComboBox 最常见的信号是选择的列表项变化时发射的 CurrentIndexChanged() 信号; 注意的是: 信号类的成员函数,但只需声明不需实现

GUI 程序设计的主要内容就是对界面上各组件的信号的响应, 只需要知道什么情况下发射哪些信号, 合理地去响应和处理这些信号就可以了;

槽(Slot)就是对信号响应的函数; 槽就是一个函数, 与一般的C++函数是一样的, 可以定义在类的任何部分(public, private 或 protected), 可以具有任何参数, 也可以被直接调用; 槽函数与一般的函数不同的是: 槽函数可以与一个信号关联, 当信号被发射时, 关联的槽函数被自动执行;

信号定义

signals:
class MyWidget: public QWidget
{
 signals: //QT信号关键词
    void ClickBut();//信号 以函数的形式定义, 但是不能当做函数使用
 
    omitt...
}

槽定义

class Manager:public QWidget
{
public:
    Manager();
 
private:
    QList<MyWidget *> widgets;
 
public slots://槽函数定义 
    void closeAll();//槽函数

槽函数定义:

  1. 目标方法必须public: slots closeAll()声明为槽函数
  2. 每一个槽函数定义必须在访问修饰符 后面加slots
  3. 它可以被当作普通的方法, 所以访问修饰符是有意义的

发射/触发

//在信号定义访问 调用一次, 就发射一次
emit ClickBut();

自定义发射参数

在另如果是自定义参数需要调用qRegisterMetaType注册支持

qRegisterMetaType<Request>("Request");//注册自定义参数
QObject::connect(session, SIGNAL(onNewRequest(Session *, Request) ),this,  SLOT(onSessionNewRequest(Session *, Request )) );
QObject::connect(session, SIGNAL(onDisconnect(Session *) ),this,  SLOT(onSessionClose(Session *)) );

信号连接

  • signals: (信号)关键词不是C++的标准.是qt内建的.

信号不等于事件! 信号不等于事件!信号不等于事件!

信号与槽, 作用是把每个对象之间的通讯抽象化出一个桥梁, 信号类似于声明接口(信号名与槽函数名可以不一致,但是参数必须一致), 而槽是真正实现处理的, 槽函数可以作为一个普通的对象成员

QObject 连接函数

首先来看看老版本的 connect 写法, 比较复杂些, 需要将信号和槽进行明确的指定, 包括形参;

QObject::connect(   发送方,    SIGNAL(...),    接收方,    SLOT(..)    );

在 Qt5.0 以后推出一种新的写法 如下:

QObject::connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);
 
QObject::connect(ui->butPlayerSource,&QPushButton::clicked, this, &ActionDialog::butPlayerSource);

连接类型

Qt::ConnectionType::AutoConnection0自动连接: 默认的方式; 信号发出的线程和糟的对象在一个线程的时候相当于: DirectConnection, 如果是在不同线程, 则相当于QueuedConnection
Qt::ConnectionType::DirectConnection1直接连接: 相当于直接调用槽函数, 但是当信号发出的线程和槽的对象不在一个线程的时候, 则槽函数是在发出的信号中执行的;
Qt::ConnectionType::QueuedConnection2队列连接: 内部通过postEvent实现的; 不是实时调用的, 槽函数永远在槽函数对象所在的线程中执行; 如果信号参数是引用类型, 则会另外复制一份; 线程安全的;
Qt::ConnectionType::BlockingQueuedConnection3阻塞连接: 此连接方式只能用于信号发出的线程(一般是先创建好对象的线程) 和 槽函数的对象不在一个线程中才能用; 通过信号量+postEvent实现的; 不是实时调用的, 槽函数永远在槽函数对象所在的线程中执行; 但是发出信号后, 当前线程会阻塞, 等待槽函数执行完毕后才继续执行;
UniqueConnection0x80防止重复连接

信号和槽 对值传递参数 和 引用传递参数

在同一个线程中 当信号和槽都在同一个线程中时, 值传递参数和引用传递参数有区别: 值传递会复制对象; (测试时, 打印传递前后的地址不同) 引用传递不会复制对象; (测试时, 打印传递前后的地址相同)

不在同一个线程中 当信号和槽不在同一个线程中时, 分两种情况;
1, connect时使用AutoConnection(跨线程默认是QueuedConnection): 值传递参数和引用传递参数没有区别, 都会复制对象; (测试时, 打印传递前后的地址不同) 2, connect时使用DirectConnection, 测试结果和在同一线程中的结果相同

参考

emit发射信号 在信号中以&引用作为参数, 以引用作为参数一定要注意, 在第二次发射信号的时候, 引用的实体已经不存在了; 所以, 如果想让每一次发射的信号中参数的值都保存下来, 不能是&引用和*指针作为参数, 而应该使用值传递; 这样每次发射信号的值都能够保存下来;

QT 事件机制

https://doc.qt.io/qt-6/eventsandfilters.html

Qt事件系统基于事件驱动模型,是GUI应用程序的核心机制。当用户操作(如鼠标点击、键盘输入)或系统事件(如定时器触发、窗口重绘)发生时,Qt会创建相应的事件对象并将其发送到目标对象进行处理。

Qt事件处理遵循以下基本流程:

  1. 事件产生​​:由窗口系统、用户输入或应用程序内部产生
  2. 事件派发​​: 事件进入事件队列 事件循环获取事件 确定目标对象 调用目标对象的event()方法
  3. ​​事件处理​​:目标对象根据事件类型调用相应的事件处理函数 事件传递基本原则是:若事件未被目标对象处理,则传递给其父对象处理,重复此过程直至事件被处理或到达顶级对象

信号不等于事件! 虽然事件系统和信号槽机制都是Qt中处理通信的方式,但它们有重要区别:

特性事件系统信号槽机制
处理方式重写事件处理器或事件过滤器连接信号和槽函数
传播机制可以向上传播到父对象一对一或多对多通信
使用场景低级别输入处理、系统事件高级别对象间通信
性能更高效,直接调用需要查找连接
注意继承重写, 头文件还是需要定义这个虚函数..

QObject基类有一个 QObject::event() 虚函数, 该函数处理事件分发, 更多的时候只是关注某一类事件, 它分发到又是一系列的虚函数方法中, 所以只需重写对应的分发到的基类函数即可.

比如 QWindow 类提供的虚方法

Header:#include <QWindow>
CMake:find_package(Qt6 REQUIRED COMPONENTS Gui)
target_link_libraries(mytarget PRIVATE Qt6::Gui)
qmake:QT += gui
Inherits:QObject and QSurface
Inherited By:QPaintDeviceWindowQQuickWindow, and QVulkanWindow

Protected Functions

virtual voidcloseEvent(QCloseEvent *ev)
virtual voidexposeEvent(QExposeEvent *ev)
virtual voidfocusInEvent(QFocusEvent *ev)
virtual voidfocusOutEvent(QFocusEvent *ev)
virtual voidhideEvent(QHideEvent *ev)
virtual voidkeyPressEvent(QKeyEvent *ev)
virtual voidkeyReleaseEvent(QKeyEvent *ev)
virtual voidmouseDoubleClickEvent(QMouseEvent *ev)
virtual voidmouseMoveEvent(QMouseEvent *ev)
virtual voidmousePressEvent(QMouseEvent *ev)
virtual voidmouseReleaseEvent(QMouseEvent *ev)
virtual voidmoveEvent(QMoveEvent *ev)
virtual boolnativeEvent(const QByteArray &eventType, void *message, qintptr *result)
(since 6.0) virtual voidpaintEvent(QPaintEvent *ev)
virtual voidresizeEvent(QResizeEvent *ev)
virtual voidshowEvent(QShowEvent *ev)
virtual voidtabletEvent(QTabletEvent *ev)
virtual voidtouchEvent(QTouchEvent *ev)
virtual voidwheelEvent(QWheelEvent *ev)

QT 常用组件

QT 多线程

继承 QThread 方式

WorkerThread.h

#include <QThread>
 
class WorkerThread : public QThread
{
    Q_OBJECT
protected:
    void run() override {
        // 在这里执行耗时操作
        for(int i = 0; i < 5; i++) {
            qDebug() << "Worker thread running" << i;
            sleep(1);
        }
    }
};
 
// 使用
WorkerThread *thread = new WorkerThread;
thread->start(); // 启动线程

移动 QObject 到线程 ( 类似Runable)

更安全的线程工作方式 Worker.h

#include <QObject>
 
class Worker : public QObject
{
    Q_OBJECT
public slots:
    void doWork() {
        // 耗时操作
        for(int i = 0; i < 5; i++) {
            emit progress(i);
            QThread::sleep(1);
        }
        emit finished();
    }
signals:
    void progress(int value);
    void finished();
};
 
// 使用
QThread *thread = new QThread;
Worker *worker = new Worker;
 
worker->moveToThread(thread); // 关键步骤
 
// 连接信号槽
connect(thread, &QThread::started, worker, &Worker::doWork);
connect(worker, &Worker::finished, thread, &QThread::quit);
connect(worker, &Worker::finished, worker, &Worker::deleteLater);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
 
thread->start();

线程间通信

使用信号槽(自动跨线程)

// 在主线程创建
Worker *worker = new Worker;
QThread *thread = new QThread;
worker->moveToThread(thread);
 
// 跨线程连接(自动排队)
connect(this, &MainWindow::startWork, worker, &Worker::doWork);
connect(worker, &Worker::progress, this, [](int value){
    // 在主线程更新UI
    progressBar->setValue(value);
});
 
thread->start();
emit startWork(); // 触发工作

QT 线程锁

互斥锁 QMutex

Qt 提供了 QMutex 类来实现互斥锁功能 qmutex

#include <QMutex>
 
QMutex mutex;
int sharedData = 0;
 
void ThreadClass::incrementData()
{
    mutex.lock();   // 加锁
    sharedData++;   // 临界区代码
    mutex.unlock(); // 解锁
}
  1. ​lock()​​ - 获取锁。如果锁已被其他线程持有,则阻塞当前线程
  2. ​unlock()​​ - 释放锁
  3. ​tryLock()​​ - 尝试获取锁,不阻塞线程
    • 成功获取返回 true
    • 锁已被其他线程持有则返回 false
  4. ​tryLock(int timeout)​​ - 尝试在指定时间内获取锁

读写锁 QReadWriteLock

对于读多写少的场景,Qt 提供了 QReadWriteLock,允许多个线程同时读取,但写入时独占:

QReadWriteLock rwLock;
 
// 读取数据
int readData()
{
    QReadLocker locker(&rwLock); // 获取读锁
    return sharedData;
}
 
// 写入数据
void writeData(int value)
{
    QWriteLocker locker(&rwLock); // 获取写锁
    sharedData = value;
}
 

其他同步机制

同步机制适用场景特点
QMutex通用互斥场景简单直接
QRecursiveMutex需要递归加锁的场景允许同一线程多次获取锁
QReadWriteLock读多写少的场景读共享,写独占
QSemaphore控制对多个相同资源的访问可以控制多个资源的访问
QWaitCondition线程间条件等待需要与互斥锁配合使用

QT QTimer

(QTimer) 计时器 每隔一定的时间 会触发timeout() 信号

#include <QTimer>
 //(QTimer) 计时器 每隔timer->start(20000)时间 会触发timeout() 信号
timer = new QTimer(this);
timer->start(20000)
 
//连接 update()必须是槽函数
connect(timer, SIGNAL(timeout()), this, SLOT(update()));

QT 异步编程

#include <QtConcurrent/QtConcurrent>
 
QFuture  future = QtConcurrent::run([=](){
        if(vlc_mediaPlayer){
            //TODO 暂以异步解决
            //内嵌窗的形式会造成死锁, 可能回调hwnd的某个事件, 在QT主线处理,造成的;
            qInfo()<<"MyPlayer Stop vlc_mediaPlayer";
            libvlc_media_player_stop(vlc_mediaPlayer);
            qInfo()<<"MyPlayer Release vlc_mediaPlayer";
            libvlc_media_player_release(vlc_mediaPlayer);//释放旧的
        }
        this->vlc_mediaPlayer = nullptr;
        if(fun ){   fun(this); }
});

QT 日志输出到文件

#ifndef CUSTOMLOG_H
#define CUSTOMLOG_H
 
#include <QApplication>
#include <QDir>
#include <QString>
#include <qdatetime.h>
#include <QTextStream>
#include <iostream>
void WriteLog(QString str, QString LogType)
{
 
    QString fileFolder= QApplication::applicationDirPath()+"/log/";
    QDir dir(fileFolder);
    if(!dir.exists())
    {
        dir.mkpath(fileFolder);
    }
    QString filePath=QString("%1 %2_%3.log").arg(fileFolder).arg(LogType).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd"));
    QString strToWrite="time: "+QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");
    strToWrite.append(QString(", log: %1").arg(str));
    QFile file(filePath);//可以打开一次锁定文件
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream text_stream(&file);
    text_stream <<strToWrite;
    file.flush();
    file.close();
    //打印到控制台
    std::cout << strToWrite.toStdString() << std::endl;
}
//注册函数
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QString txtMessage = "";
    QString messageType = "";
    switch (type)    {
    case QtDebugMsg:   //调试信息提示
        messageType = "Debug";
        txtMessage = QString("Debug: %1 (%2:%3, %4)\n").arg(msg).arg(context.file).arg(QString::number(context.line)).arg(context.function);
        break;
    case QtInfoMsg:
        messageType = "Info";
        txtMessage = QString("Warning: %1 (%2:%3, %4)\n").arg(msg).arg(context.file).arg(QString::number(context.line)).arg(context.function);
        break;
    case QtWarningMsg:   //一般的warning提示
        messageType = "Waring";
        txtMessage = QString("Warning: %1 (%2:%3, %4)\n").arg(msg).arg(context.file).arg(QString::number(context.line)).arg(context.function);
        break;
    case QtCriticalMsg:   //严重错误提示
        messageType = "Critical";
        txtMessage = QString("Critical: %1 (%2:%3, %4)\n").arg(msg).arg(context.file).arg(QString::number(context.line)).arg(context.function);
        break;
    case QtFatalMsg:   //致命错误提示
        messageType = "Fatal";
        txtMessage = QString("Fatal: %1 (%2:%3, %4)\n").arg(msg).arg(context.file).arg(QString::number(context.line)).arg(context.function);
        abort();
    }
    WriteLog(txtMessage, messageType);
}
///
/// 使用
/// qInstallMessageHandler(myMessageOutput);
#endif // CUSTOMLOG_H
 

注意: release模式下无法打印 QMessageLogContext 的具体内容, 因为qt默认把release给取消了, 需要在 .pro 文件里面加上DEFINES += QT_MESSAGELOGCONTEXT https://www.cnblogs.com/xupeidong/p/9510553.html

QT 混合开发

参考 N_QT 6

QT JavaScript 引擎

QJSEngine 则是从Qt5.0开始提供,基于谷歌的V8引擎,是官方建议使用的版本。 QJSEngine Class

js 引擎是在 QML模块的..


find_package(Qt6 COMPONENTS Qml REQUIRED)

...
target_link_libraries(js-op Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Qml
 "op_x64"
)

示例

//Evaluating Scripts
QJSEngine myEngine;
QJSValue three = myEngine.evaluate("1 + 2");
 
//The following code snippet shows how a script function
QJSValue fun = myEngine.evaluate("(function(a, b) { return a + b; })");
QJSValueList args;
args << 1 << 2;
QJSValue threeAgain = fun.call(args);
 
//form file
QString fileName = "helloworld.qs";
QFile scriptFile(fileName);
if (!scriptFile.open(QIODevice::ReadOnly)){
    // handle error
}
    
QTextStream stream(&scriptFile);
QString contents = stream.readAll();
scriptFile.close();
myEngine.evaluate(contents, fileName);

对象集成 (QObject Integration)

QObject Integration

QJSValue QJSEngine::globalObject() const

// Constructors exposed to the meta-object system
class MyObject : public QObject
{
    Q_OBJECT
 
public:
    Q_INVOKABLE MyObject() {}
};
 
QJSValue jsMetaObject = engine.newQMetaObject(&MyObject::staticMetaObject);
engine.globalObject().setProperty("MyObject", jsMetaObject);
 
//Instances of the class can then be created in JavaScript:
engine.evaluate("var myObject = new MyObject()");
 

绑定C++对象, 还需考虑对象所有权的问题 QJSEngine::ObjectOwnership

扩展 (Console)

extensions

QJSEngine provides a compliant ECMAScript implementation. By default, familiar utilities like logging are not available, but they can be installed via the installExtensions() function.

扩展枚举值-参考: https://doc.qt.io/qt-6/qjsengine.html#Extension-enum

in short 引擎有个方法 engine.installExtensions() 可以安装扩展

如何方便调试?

看官文 https://doc.qt.io/qt-6/qjsengine.html

QT with libpng 库

APNG(Animated Portable Network Graphics)是一种支持动画的图像格式,类似于 GIF,但具有更好的图像质量和支持更多颜色。要在Qt中读取APNG文件并进行压缩,你需要使用一些第三方库来处理APNG格式。一个常用的库是 “libpng”,它支持PNG和APNG格式。

已编译为QT插件

QT with 大漠插件

导出头文件 && 注册 dm.dll

E:\software\QT\6.2.1\msvc2019_64\bin\dumpcpp.exe E:\projects-cpp\dm-call\lib\dm.dll

把生成的 dm.h 头文件和 dm.cpp 源文件移动并添加到自己的工程里

.pro

SOURCES += \
        main.cpp\
        dm.cpp
HEADERS += \
        dm.h

注册

cd E:\projects-cpp\dll-call\lib
regsvr32 dm.dll

axcontainer

因为大漠插件是采用COM接口编写的,所以要添加axcontainer模块

QT QAxContainer

.pro

QT += axcontainer
#include <QCoreApplication>
#include <dm.h>
#include <iostream>
#include "qt_windows.h"
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    HRESULT result = OleInitialize(0);   //换成CoInitialize似乎也是一样的
    Dm::Idmsoft dm;
   if (dm.setControl("dm.dmsoft"))  //初始化COM对象
   {
       std::cout<<"DM  大漠插件版本:"<<dm.Ver().toStdString()<<std::endl;
   }else {
       std::cout<<"DM 大漠插件初始化失败,请先注册"<<std::endl;
   }
    return a.exec();
}
 

https://blog.csdn.net/wujizhishang/article/details/70175487

编译器需32bit的 …

qt 项目归档

QT & MSVC编译器

for qmake

下载

  • 单独下载 msvc 编译器

官页 All downloads Tools for Visual Studio 2019 Build Tools for Visual Studio 2019

补充: 貌似最低 2017 没有2015的.. 然而, 页面最下面有个Older downloads button, 还需要登陆,MMP的..

搜索 tools 2015 选择Visual C++ Build Tools 2015 Update 3 下载安装之 默认在C:\Program Files (x86)\Microsoft Visual C++ Build Tools都是快捷方式; qt x86编译器选vcbuildtools.bat 实际位置在C:\Program Files (x86)\Microsoft Visual Studio 14.0

编译器是有了, QT kits 仍然不可用 no debugger set up, 没有 CDB, 还需安装Windown SDK!

CDB

  • 下载安装 Windown SDK

官页

运行 选择 Debugging Tools for windows

然后找到下载的安装包, 安装, 例X86 :X86 Debuggers And Tools-x86_en-us.msi

安装在C盘, CDB路径C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\cdb.exe

安装完重启下, qt 也能自动探测到CDB..

QT 内存泄漏检测 for win

crtdbg 库

需要使用MSVC 编译器

#define _CRTDBG_MAP_ALLOC
#include<stdlib.h>
 
#include<crtdbg.h>

_CrtDumpMemoryLeaks() 就是显示当前的内存泄漏;
注意是”当前”, 也就是说当它执行时, 所有未销毁的对象均会报内存泄漏; 因此尽量让这条语句在程序的最后执行; 它所反映的是检测到泄漏的地方;

{212} normal block at 0x01421460, 32 bytes long.
 Data: <            h A > CC A7 B3 00 06 00 00 00 00 00 00 00 68 DF 41 01 
{211} normal block at 0x0141DC50, 40 bytes long.
 Data: <            ` A > EC 8D B3 00 12 00 00 00 00 00 00 00 60 98 41 01 
{210} normal block at 0x0140B1F8, 24 bytes long.

{xx}; 这个整数值代表了什么意思呢?其实, 它代表了第几次内存分配操作; 有没有可能, 我们让程序运行到第52次内存分配操作的时候, 自动停下来, 进入调试状态?所幸, crtdbg确实提供了这样的函数: 即 long _CrtSetBreakAlloc(long nAllocID)

1.normal block(普通块): 这是由你的程序分配的内存; 2.client block(客户块): 这是一种特殊类型的内存块, 专门用于 MFC 程序中需要析构函数的对象; MFC new 操作符视具体情况既可以为所创建的对象建立普通块, 也可以为之建立客户块; 3.CRT block(CRT 块): 是由 C RunTime Library 供自己使用而分配的内存块; 由 CRT 库自己来管理这些内存的分配与释放, 我们一般不会在内存泄漏报告中发现 CRT 内存泄漏, 除非程序发生了严重的错误(例如 CRT 库崩溃);

除了上述的类型外, 还有下面这两种类型的内存块, 它们不会出现在内存泄漏报告中: 1.free block(空闲块): 已经被释放(free)的内存块; 2.Ignore block(忽略块): 这是程序员显式声明过不要在内存泄漏报告中出现的内存块;

若要确定某段代码中是否发生了内存泄漏, 可以通过获取该段代码之前和之后的内存状态快照, 然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemState s1, s2, s3;
_CrtMemCheckpoint( &s1 );// 获取第一个内存状态快照
// 在这里进行内存分配
 
_CrtMemCheckpoint( &s2 );// 获取第二个内存状态快照
 
// 比较两个内存快照的差异
if ( _CrtMemDifference( &s3, &s1, &s2) )
_CrtMemDumpStatistics( &s3 );// dump 差异结果

顾名思义, _CrtMemDifference 比较两个内存状态(前两个参数), 生成这两个状态之间差异的结果(第三个参数); 在程序的开始和结尾放置 _CrtMemCheckpoint 调用, 并使用 _CrtMemDifference 比较结果, 是检查内存泄漏的另一种方法; 如果检测到泄漏, 则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来分割程序和定位泄漏;

#define _CRTDBG_MAP_ALLOC 的作用 如果不定义这个宏, C方式的malloc泄露不会被记录下来

另外: 其他文件 要显示行号的话, 要定义下面 new的替代宏 简单粗暴

#ifdef _DEBUG
    #define new  new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

这样 _CrtSetBreakAlloc(long nAllocID) 也有作用

但是 crtdbg库也有缺点, 当你使用一个别人提供的lib或者dll库的时候, 你调用这个函数, 这个函数分配了内存, 需要你去调用另外一个函数才能把内存释放掉, 但是你不知道这个函数需要调用另外一个函数才能释放掉内存, 这个是无法通过crtdbg库检测出来的, 这个函数包括c++的new函数, 所以这个库实际上不适用C++

Windbg 调试内存问题

同需要使用MSVC 编译器; Windows Kits 自带 WinDbg (X64).exe

进行windbg环境的配置: 1)配置windows符号服务器SRVd:\symbols http://msdl.microsoft.com/download/symbols 2)将当前程序的pdb文件路径加入到Windbg的Symbol File Path路径中 3)配置操作系统的标志, 以便为内存泄漏的进程启用用户堆栈跟踪; 可以通过安装Windbg自带的gflags.exe程序, 通过命令”gflags.exe -i 20190718.exe +ust”来完成, 20190718.exe是程序名(不用带路径, 只用输入程序名即可)

大佬的博客 (Windbg调试七)c++内存泄漏问题定位

踩坑指南

’byte’引用不明确

error: reference to ‘byte’ is ambiguous(‘byte’引用不明确) error: ‘byte’ has not been declared(‘byte’未声明)

c++17 std命名空间增加了byte声明, 造成了引用歧义, 改源码 麻烦的一批, 换个mingw 不是最新版的, 一了百了..

C printf 函数 不实时输出?

设置缓存区, 为0, 这样就会直接输出了 setbuf(stdout, NULL); (https://blog.csdn.net/qq21497936/article/details/108546564)

QT 编译成功 运行失败?

奇怪的问题QT控制台程序, 引用了lib库 后run无效,只输出编译的二进制文件,

原因是引用了lib库 后编译可通过, 但是没有运行库的环境 造成运行错误 (注意一点 依赖库,可分为编译依赖(lib, lib.a), 和运行时依赖(dll) )

解决: 加入运行依赖dll库路径 到run 环境变量

主界面 Project > build&Run run 找到Run Environment detail 选择 clean Environment 把添加dll路径

This application failed to start because no Qt platform plugin

将对应qt版本的 ./plugins/ 下的 platforms文件夹复制到程序目录

QT 程序发布, 复制DLL依赖

编译需要发布的程序如: rtsp_server.exe 将这个exe 复制到一个新的单独的文件夹里用于发布

Qt提供了一个名为 QT6.9\6.9.1\msvc2022_64\bin\windeployqt的实用工具,可以自动收集所有必需的DLL文件:

进入目录 使用命令.. windeployqt rtsp_server.exe

另外 项目自己引入的 lib 还是需要自己复制..

程序发布时 Qt5Core.dll 等依赖的dll 是复制自 ./5.11.2/5.11.2/msvc2015/bin 目录下面的dll 在 Kits -> QT Versions 也可以看到已安装的qt sdk 版本 在 Kits -> Kits 那里除了选择对应版本的编译器, 还要有对应版本的 QT SDK (在Qt version 一般自动探测的到)



QT Creator

Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境; Qt Creator可带来两大关键益处: 提供首个专为支持跨平台开发而设计的集成开发环境, 并确保首次接触Qt框架的开发人员能迅速上手和操作; 即使不开发Qt应用程序, Qt Creator也是一个简单易用且功能强大的集成开发环境;

in short 就是IDE

Qt 跨平台的C++应用和UI开发库

QT Creator 注释模版

tools -> options -> text editor -> snippets -> add trigger comment trigger variant custom 内容:

/** 
* 
* @brief     
* @author  yang <fish58bf@gmail.com>
* @date     %{CurrentDate:yyyy-MM-dd} 
*/

然后在编辑输入 comment 即可快捷输入代码模版

QT Creator 代码提示

TextEditor.CompleteThis 其快捷键是Ctrl+Space; 添加Alt+/ //Eclipse的习惯