QGC源码剖析-main.cc

796 阅读4分钟

main.cc文件剖析

本节是QGC源码剖析的第一节,从main.cc开始。main.cc文件中包含了main函数,是程序的入口。

image.png

代码剖析

main.cc文件中有两个函数,一个是WindowsCrtReportHook钩子,一个是main主函数。

WindowsCrtReportHook钩子

/*main.cc*/

/// @brief CRT Report Hook installed using _CrtSetReportHook. We install this hook when
/// we don't want asserts to pop a dialog on windows.
/// CRT报告挂钩使用 _CrtSetReportHook安装。不让断言在 Windows上弹出对话框。
int WindowsCrtReportHook(int reportType, char* message, int* returnValue)
{
    Q_UNUSED(reportType);

    std::cerr << message << std::endl;  // Output message to stderr
    *returnValue = 0;                   // Don't break into debugger
    return true;                        // We handled this fully ourselves
}

main主函数

主函数流程图如下:

graph TD;
    A[Start] --> B{Is guard running?};
    B -- Yes --> C[Show error message and exit];
    B -- No --> D[Initialize timer];
    D --> E[Install message handler];
    E --> F[Set OpenGL buglist];
    F --> G[Check command line arguments];
    G -- UseOpenGLES --> H[Set attribute to use OpenGLES];
    G -- UseSoftwareOpenGL --> I[Set attribute to use software OpenGL];
    H --> J[Register QSerialPort::SerialPortError];
    J --> K[Register QAbstractSocket::SocketError];
    K --> L[Register QGCSerialPortInfo];
    L --> M[Import QGeoServiceProviderFactoryQGC];
    M --> N[Set attribute to use high DPI pixmaps];
    N --> O[Create QGCApplication];
    O -- Error state --> P[Execute error state and exit];
    O -- No error state --> Q[Register problematic type];
    Q --> R[Initialize common QML objects];
    R --> S[Initialize cache system];
    S --> T[Initialize for normal app boot];
    T -- Initialization failed --> U[Exit with error code];
    T -- Initialization succeeded --> V[Execute application];
    V --> W[Shutdown all QML];
    W --> X[Delete application];
    X --> Y[Shutdown cache system];
    Y --> Z[End];

代码及关键注释:

/*main.cc*/

int main(int argc, char *argv[])
{
#ifndef __mobile__
    //RunGuard类保证同一时间只运行一个程序
    RunGuard guard("QGroundControlRunGuardKey");
    if (!guard.tryToRun()) {
        // QApplication is necessary to use QMessageBox
        QApplication errorApp(argc, argv);
        QMessageBox::critical(nullptr, QObject::tr("Error"),
            QObject::tr("A second instance of %1 is already running. Please close the other instance and try again.").arg(QGC_APPLICATION_NAME)
        );
        return -1;
    }
#endif

    //-- Record boot time:记录启动时间
    QGC::initTimer();
    
    // install the message handler:安装消息处理回调函数
    AppMessages::installHandler();
    
    // Set our own OpenGL buglist:设置我们自己的 OpenGL 错误列表
    qputenv("QT_OPENGL_BUGLIST", ":/opengl/resources/opengl/buglist.json");
    
    // Allow for command line override of renderer:允许命令行覆盖渲染器
    for (int i = 0; i < argc; i++) {
        const QString arg(argv[i]);
        if (arg == QStringLiteral("-angle")) {
            QCoreApplication::setAttribute(Qt::AA_UseOpenGLES);
            break;
        } else if (arg == QStringLiteral("-swrast")) {
            QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
            break;
        }
    }
    
    // The following calls to qRegisterMetaType are done to silence debug output which warns
    // that we use these types in signals, and without calling qRegisterMetaType we can't queue
    // these signals. In general we don't queue these signals, but we do what the warning says
    // anyway to silence the debug output.
    //
    qRegisterMetaType<QSerialPort::SerialPortError>();
    qRegisterMetaType<QAbstractSocket::SocketError>();
    qRegisterMetaType<QGCSerialPortInfo>();
    
    // We statically link our own QtLocation plugin
    // 静态链接QtLocation 插件
    
    //这一块代码是一个预处理器指令,用于禁用 Windows 编译器中的两个特定警告。 
    //具体来说,它禁用警告 4930(警告函数调用中的参数数量与函数定义中的参数数量之间可能不匹配)和警告 4101(警告未使用的局部变量)。
    //只有在 Windows 操作系统上编译代码时才会执行此代码块,如 Q_OS_WIN 宏所示。
#ifdef Q_OS_WIN
    // In Windows, the compiler doesn't see the use of the class created by Q_IMPORT_PLUGIN
#pragma warning( disable : 4930 4101 )
#endif

    Q_IMPORT_PLUGIN(QGeoServiceProviderFactoryQGC)

    bool runUnitTests = false;          // Run unit tests:运行单元测试
    
    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
    
    //继承自QApplication
    QGCApplication* app = new QGCApplication(argc, argv, runUnitTests);
    Q_CHECK_PTR(app);
    if(app->isErrorState()) {
        app->exec();
        return -1;
    }
    
    // There appears to be a threading issue in qRegisterMetaType which can cause it to throw a qWarning
    // about duplicate type converters. This is caused by a race condition in the Qt code. Still working
    // with them on tracking down the bug. For now we register the type which is giving us problems here
    // while we only have the main thread. That should prevent it from hitting the race condition later
    // on in the code.
    //qRegisterMetaType 中似乎存在线程问题,这可能导致它抛出有关重复类型转换器的 qWarning。 
    //这是由 Qt 代码中的竞争条件引起的。 仍在与他们合作追踪错误。 
    //现在我们注册了给我们带来问题的类型,而我们只有主线程。 
    //这应该可以防止它稍后在代码中遇到竞争条件。
    qRegisterMetaType<QList<QPair<QByteArray,QByteArray> > >();
    
    app->_initCommon();//注册Qml对象
    //-- Initialize Cache System:初始化缓存系统
    getQGCMapEngine()->init();
    
    int exitCode = 0;
    
    if (!app->_initForNormalAppBoot()) {//程序正常启动初始化
        return -1;
    }
    exitCode = app->exec();
    
    app->_shutdown();//关闭所有的qml
    delete app;
    //-- Shutdown Cache System:关闭缓存系统
    destroyMapEngine();

    qDebug() << "After app delete";

    return exitCode;
}

要点功能

主要用到了qRegisterMetaType自定义类型和QtPlugin插件系统

qRegisterMetaType

如果想要我们自己自定义的类型也可以有 Qt 自己类型的功能的话,就必须注册我们的类型到 Qt 中,这样才可以在信号和槽的通讯机制中使用我们的自定义的类型。

不跨线程的话,使用自定义的类型使用signal/slot来传递,没有什么问题。但如果是跨线程的使用,则没有这么简单。

直接使用的话,会产生下面这种错误:(假定自定义类为MyClass) QObject::connect: Cannot queue arguments of type 'MyClass' (Make sure 'MyClass' is registed using qRegisterMetaType().) 实际运行中也会发现,该信号槽没有起作用。其实解决方法在错误提示中已经给出了:Make sure 'MyClass' is registed using qRegisterMetaType().
即使用qRegisterMetaType()将自定义类型进行注册。

在Qt中,想要使用signal/slot来传递自定义的类型时,需要使用qRegisterMetaType来注册。其原因是:当一个signal被放到队列中(queued)时,它的参数(arguments)也会被一起一起放到队列中(queued起来),这就意味着参数在被传送到slot之前需要被拷贝、存储在队列中(queue)中;为了能够在队列中存储这些参数(argument),Qt需要去construct、destruct、copy这些对象。用qRegisterMetaType对自定义的类型进行注册,就是为了告诉Qt如何去做这些事情。

步骤:(以自定义MyDataType类型为例)

  1. 自定MyDataType 类型,在这个类型的顶部包含:#include
  2. 在类型定义完成后,加入声明:Q_DECLARE_METATYPE(MyDataType);
  3. 在main()函数中注册这种类型:qRegisterMetaType("MyDataType");
  4. 如果还希望使用这种类型的引用,可同样要注册:qRegisterMetaType("MyDataType&");
#include <QMetaType>       
class MyDataType
  {     
   public:     
   MyDataType();     
   MyDataType(int, double);        
private:      
};     
Q_DECLARE_METATYPE(MyDataType);     
  
 
int main(int argc, char *argv[])     
{     
    QApplication app(argc, argv); 
    qRegisterMetaType<MyDataType>("MyDataType");     
    qRegisterMetaType<MyDataType>("MyDataType&");     
}

以代码中的qRegisterMetaType() 为例,可以看到在main.cc中进行了注册。

#ifndef __mobile__
#ifndef NO_SERIAL_LINK
    Q_DECLARE_METATYPE(QGCSerialPortInfo)
#endif
#endif

Q_IMPORT_PLUGIN

Q_IMPORT_PLUGIN 宏通常在 Qt 应用程序中用于为特定类导入插件。 导入插件时,它会为其关联的类提供附加功能或自定义。

Q_IMPORT_PLUGIN 的一些常见用例包括为与多媒体、位置服务和数据库连接相关的类导入插件。 例如,在提供的代码块中,Q_IMPORT_PLUGIN(QGeoServiceProviderFactoryQGC) 正在为 QGeoServiceProviderFactoryQGC 类导入一个插件,该插件用于在 Qt 应用程序中提供位置服务。

要使用 Q_IMPORT_PLUGIN,您必须首先为所需的类创建一个插件。 这可以通过子类化适当的插件接口类并实现必要的功能来完成。 创建插件后,可以使用 Q_IMPORT_PLUGIN 将其加载到应用程序中。

需要注意的是,插件必须与主应用程序分开构建,并放置在特定目录中以便应用程序找到它们。 插件的目录结构和命名约定由 Qt 定义,可以在 Qt 文档中找到。doc.qt.io/qt-5/plugin…

制作应用程序插件的扩展可以通过如下的步骤:

  1. 定义接口的集合(类的纯虚函数)跟随这个插件plugins.
  2. 使用这个Q_DECLARE_INTERFACE()宏来告诉Qt的元对象系统关于这个接口。
  3. 使用QPluginLoader在应用程序中来加载这些插件.
  4. 使用qobject_cast()来告诉插件实现给定的接口。

写一个插件包括下面的步骤:

  1. 声明一个插件类继承自QObject,或者从这个插件想提供的接口;
  2. 使用Q_INTERFACE()宏来告诉Qt的元对象系统关于这个这个插件;
  3. 导出这个插件通过使用Q_PLUGIN_METADATA()宏;
  4. 编译插件通过使用合适的.pro  文件。

例如,这有一个定义的接口类:

class FilterInterface
{
public:
    virtual ~FilterInterface() {}
 
    virtual QStringList filters() const = 0;
    virtual QImage filterImage(const QString &filter, const QImage &image,
                               QWidget *parent) = 0;
};

这定义的插件类的实现接口:

#include <QObject>
#include <QtPlugin>
#include <QStringList>
#include <QImage>
 
#include <plugandpaint/interfaces.h>
 
class ExtraFiltersPlugin : public QObject, public FilterInterface
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.qt-project.Qt.Examples.PlugAndPaint.FilterInterface" FILE "extrafilters.json")
    Q_INTERFACES(FilterInterface)
public:
    QStringList filters() const;
    QImage filterImage(const QString &filter, const QImage &image,
                       QWidget *parent);
};

setAttribute

设置 Qt::AA_UseHighDpiPixmaps 属性,为应用程序启用高 DPI 像素图支持。 这意味着应用程序将在高 DPI 显示器上使用高分辨率图像作为图标和其他图形。此属性通常在应用程序的主要功能的开头设置。

使用到的自定义类

  • RunGuard
  • QGC
  • AppMessages
  • QGCSerialPortInfo
  • QGeoServiceProviderFactoryQGC
  • QGCApplication
  • QGCMapEngine