Qt源码分析:Qt程序是怎么运行起来的?

2024-01-09

一、从 exec() 谈起

一个标准的Qt-gui程序,在启动时我们会coding如下几行简洁的代码:

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

在这里我们首先考虑第一个问题,如果主程序中没有调用 a.exec() ,在编译运行时会发生什么?
~~ one thousand years later~~
对,也许你已经非常清楚了,你定义的 widget 窗口竟然没有显示,或者你以自己如同高速摄像机的神器双眼,炯炯侠般捕获到窗口突然的闪现然后消失。这时候你明白, a.exec() 是保证窗口程序不立即退出的重要保证。
你的猜想很对,同时我要告诉你的是, a.exec() 做的工作显然比这要多很多。

二、源码分析

    QObject
		|__QCoreApplication					   源码路径:qtbase\src\corelib\kernel\qcoreapplication.cpp
					|__QGuiApplication            源码路径:qtbase\src\gui\kernel\qguiapplication.cpp
								|_QApplication       源码路径: qtbase\src\widgets\kernel\qapplication.cpp

		        图2.1   QApplication 继承类图

我们采用源码调试的方式,倒推来看我们的关注点。

1、 QApplication a(argc, argv);

对象实例化,构造函数如下:

#ifdef Q_QDOC
QApplication::QApplication(int &argc, char **argv)
#else
QApplication::QApplication(int &argc, char **argv, int _internal)
#endif
    : QGuiApplication(*new QApplicationPrivate(argc, argv, _internal))
{
    Q_D(QApplication);
    d->init();	/// 资源初始化
}

2、 a.exec()

int QCoreApplication::exec()
{
	// [1]
    if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;

	// [2]
    QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }
	// [3]
    if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

	// [4]
    threadData->quitNow = false;
    QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

	// [5]
    if (self)
        self->d_func()->execCleanup();

    return returnCode;
}

下面掰开了,揉碎了,逐行扒光了解读一番:


[1]

if (!QCoreApplicationPrivate::checkInstance("exec"))
        return -1;

判断 QCoreApplication 对象是否已经实例化,否则打印输出错误信息并退出。

bool QCoreApplicationPrivate::checkInstance(const char *function)
{
    bool b = (QCoreApplication::self != nullptr);
    if (!b)
        qWarning("QApplication::%s: Please instantiate the QApplication object first", function);
    return b;
}

[2]


 QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

self->d_func() ,有点眼熟啊,这不就是Qt的孪生指针刺客 : q 指针和 d 指针 中 d 指针么?对这一块内容和知识不太熟悉的朋友,回头去看看博主曾经写过的相关内容 《Qt : d指针和q指针?》

我们回过头看看 QCoreApplication 是如何申明的:

class Q_CORE_EXPORT QCoreApplication
#ifndef QT_NO_QOBJECT
    : public QObject
#endif
{
 Q_DECLARE_PRIVATE(QCoreApplication)
 protected:
    QCoreApplication(QCoreApplicationPrivate &p);
	// ...
#ifdef QT_NO_QOBJECT
    QScopedPointer<QCoreApplicationPrivate> d_ptr;
#endif

// ...
}
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
    friend class Class##Private;

#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(Dptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(Dptr));) } \
    friend class Class##Private;

ok,读到这儿,我们先收起更深的好奇之心,我们拿到了我们暂时需要知道的, self->d_func() 获取到了 QCoreApplicationPrivate 的成员变量指针,即 QScopedPointer<QCoreApplicationPrivate> d_ptr;

class Q_CORE_EXPORT QCoreApplicationPrivate
#ifndef QT_NO_QOBJECT
    : public QObjectPrivate
#endif
{
 // ...
};
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
    Q_DECLARE_PUBLIC(QObject)
public:
	// ....
    ExtraData *extraData;    // extra data set by the user
    QThreadData *threadData; // id of the thread that owns the object
};

喔嚯.回过头来再看这一段.

  QThreadData *threadData = self->d_func()->threadData;
    if (threadData != QThreadData::current()) {
        qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
        return -1;
    }

这是是干啥啊,获取当前对象所在线程的id,哪个对象? QCoreApplication 对象或者它的子类对象 QApplication a; 并判断当前线程id与对象所在线程id是否相同,不同则会给出错误提示并退出。

所以,这里有个知识点我们必须记住: QCoreApplication 及其子类,仅且只能在主线程中实例化!!!


[3]

我们先熟悉下 QThreadData 类

class QThreadData
{
public:
    QThreadData(int initialRefCount = 1);
    ~QThreadData();

    static Q_AUTOTEST_EXPORT QThreadData *current(bool createIfNecessary = true);
#ifdef Q_OS_WINRT
    static void setMainThread();
#endif
    static void clearCurrentThreadData();
    static QThreadData *get2(QThread *thread)
    { Q_ASSERT_X(thread != nullptr, "QThread", "internal error"); return thread->d_func()->data; }


    void ref();
    void deref();
    inline bool hasEventDispatcher() const
    { return eventDispatcher.loadRelaxed() != nullptr; }
    QAbstractEventDispatcher *createEventDispatcher();
    QAbstractEventDispatcher *ensureEventDispatcher()
    {
        QAbstractEventDispatcher *ed = eventDispatcher.loadRelaxed();
        if (Q_LIKELY(ed))
            return ed;
        return createEventDispatcher();
    }

    bool canWaitLocked()
    {
        QMutexLocker locker(&postEventList.mutex);
        return canWait;
    }

    // This class provides per-thread (by way of being a QThreadData
    // member) storage for qFlagLocation()
    class FlaggedDebugSignatures
    {
        static const uint Count = 2;

        uint idx;
        const char* locations[Count];

    public:
        FlaggedDebugSignatures() : idx(0)
        { std::fill_n(locations, Count, static_cast<char*>(nullptr)); }

        void store(const char* method)
        { locations[idx++ % Count] = method; }

        bool contains(const char *method) const
        { return std::find(locations, locations + Count, method) != locations + Count; }
    };

private:
    QAtomicInt _ref;

public:
    int loopLevel;
    int scopeLevel;

    QStack<QEventLoop *> eventLoops;    事件循环的主角来了
    QPostEventList postEventList;
    QAtomicPointer<QThread> thread;
    QAtomicPointer<void> threadId;
    QAtomicPointer<QAbstractEventDispatcher> eventDispatcher;
    QVector<void *> tls;
    FlaggedDebugSignatures flaggedSignatures;

    bool quitNow;
    bool canWait;
    bool isAdopted;
    bool requiresCoreApplication;
};

QStack<QEventLoop *> eventLoops; QThreadData 类 中的一个成员变量,它是一个栈,用于存储 QEventLoop 对象的指针。

Qt 中,每个 QThread 都有一个与之关联的事件循环(QEventLoop)。事件循环是 GUI 程序的核心,它用于接收和处理各种事件,如用户输入、定时器事件、网络事件等。

然而,一个线程不仅可以有一个主事件循环,还可以有一个或多个嵌套的事件循环。例如,当你在一个线程中调用 QEventLoop::exec() 时,你就创建了一个新的事件循环,并将其推入到 eventLoops 栈中。当 QEventLoop::exit() 被调用时,当前的事件循环会结束,并从 eventLoops 栈中弹出。

eventLoops 栈的顶部始终是当前线程的当前事件循环。这意味着,当你在一个线程中调用 QCoreApplication::processEvents() 时,Qt 会处理 eventLoops 栈顶的事件循环中的事件。

总的来说, QStack<QEventLoop *> eventLoops 成员变量用于存储和管理一个线程中的所有事件循环。

if (!threadData->eventLoops.isEmpty()) {
        qWarning("QCoreApplication::exec: The event loop is already running");
        return -1;
    }

这段,如果栈容器中的事件数量为空,则说明 QCoreApplication 事件循环还没有运行,否则,说明已经入事件循环状态。如果没有进入,那么继续往下走。


	/// 将几个标识状态对应初始化 
	threadData->quitNow = false;
    
	QEventLoop eventLoop;
    self->d_func()->in_exec = true;
    self->d_func()->aboutToQuitEmitted = false;
    
    /// 执行主线程的事件循环
    int returnCode = eventLoop.exec();
    threadData->quitNow = false;

    if (self)
        self->d_func()->execCleanup();
int QEventLoop::exec(ProcessEventsFlags flags)
{
    Q_D(QEventLoop);
    //we need to protect from race condition with QThread::exit
    QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread.loadAcquire()))->mutex);
    if (d->threadData->quitNow) // 已经设置为false
        return -1;

    if (d->inExec) {  // 已经设置为true
        qWarning("QEventLoop::exec: instance %p has already called exec()", this);
        return -1;
    }

    struct LoopReference {
        QEventLoopPrivate *d;
        QMutexLocker &locker;

        bool exceptionCaught;
        LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
        {
            d->inExec = true;
            d->exit.storeRelease(false);
            ++d->threadData->loopLevel;   /// 线程等级提升+1
            d->threadData->eventLoops.push(d->q_func()); /// 事件入栈
            locker.unlock();
        }

        ~LoopReference()
        {
            if (exceptionCaught) {
                qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
                         "exceptions from an event handler is not supported in Qt.\n"
                         "You must not let any exception whatsoever propagate through Qt code.\n"
                         "If that is not possible, in Qt 5 you must at least reimplement\n"
                         "QCoreApplication::notify() and catch all exceptions there.\n");
            }
            locker.relock();
            QEventLoop *eventLoop = d->threadData->eventLoops.pop();
            Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
            Q_UNUSED(eventLoop); // --release warning
            d->inExec = false;
            --d->threadData->loopLevel;
        }
    };
    LoopReference ref(d, locker);

    // remove posted quit events when entering a new event loop
    QCoreApplication *app = QCoreApplication::instance();
    if (app && app->thread() == thread())
        QCoreApplication::removePostedEvents(app, QEvent::Quit);

#ifdef Q_OS_WASM
    // Partial support for nested event loops: Make the runtime throw a JavaSrcript
    // exception, which returns control to the browser while preserving the C++ stack.
    // Event processing then continues as normal. The sleep call below never returns.
    // QTBUG-70185
    if (d->threadData->loopLevel > 1)
        emscripten_sleep(1);
#endif

	/**************************************************************************/
    while (!d->exit.loadAcquire())
        processEvents(flags | WaitForMoreEvents | EventLoopExec);  /// 加入到事件循环stack中
   /**************************************************************************/
    ref.exceptionCaught = false;
    return d->returnCode.loadRelaxed();
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt源码分析:Qt程序是怎么运行起来的? 的相关文章

  • 如何设置 Xcode 来代替 Qt Creator 工作?

    我不使用 Qt Creator 的 UI 设计功能 对于一个新项目 我想体验一下使用 Xcode 的工作 这将是一个常规的 Qt 项目 使用 C 和 Qt 库开发 就像在 Qt Creator 中一样 我没有使用 OS X 尤其是 Xcod
  • 异步设计中如何知道哪个QNetworkReply属于QNetworkRequest?

    我可以轻松地用 C 进行异步设计 HttpResponseMessage response await httpClient GetAsync InputAddress Text run when request finished And
  • QGroupBox边框

    经过一段时间的搜索后 我发现在组框上设置可见边框的方法是使用 StyleSheet 属性 我补充道 border 2px solid gray 但有几个问题 1 组框内的所有内容也继承此设置 2 边框在标题附近有一个小洞 碎片缺失 Here
  • 将项目添加到自定义组件的布局

    我有一个习惯Footer Component我想在 QML 应用程序的不同位置重用它 Rectangle color gold height 50 anchors bottom parent bottom left parent left
  • 通过对 XmlHttpRequest (REST) 的响应在 QML 中显示图像

    我需要从 REST API 调用中获取 jpeg 图像 我使用 XMLHttpRequest 因为请求需要身份验证标头 即我不能只创建一个图像并将源设置为带有 user passwd url 的 URL 我认为我可以通过将 REST 数据设
  • 对齐坐标系

    Let s say I have 2 coordinate systems as it is shown in image attached 如何对齐这个坐标系 我知道我需要将第二个坐标系围绕 X 平移 180 度 然后将其平移到第一个坐标
  • QToolBar 的菜单延迟

    我通过制作 QAction 并向其添加 QMenu 在 QToolBar 上有一个菜单 如何消除单击图标时出现菜单之前的延迟 QToolBar myToolBar new QToolBar this QAction myAction new
  • Windows 消息循环而不是 QApplication::exec() / QApplication::processEvents()

    我是否想念任何一个Qt如果我替换功能QApplication exec 使用标准 Windows 消息循环实现 这应该可以澄清我的意思 运行事件处理的常用 Qt 方式 int main int argc char argv QApplica
  • 调整 QML 图像显示尺寸

    我有一个带有嵌套的 QML 窗口RowLayout 在内排我有两个图像 来源 png这些图像的文件 故意 相当大 当我尝试设置height这些图像上的属性使它们变小 但它们仍然被画得很大 Desired Appearance Actual
  • 安装多个版本的 Qt 库

    我在windows中安装了QtSDK 它的Qt库版本是4 7 0 现在我想为 mingw 和 VS2008 安装 Qt 库版本 4 8 2 我怎样才能做到这一点 如何向QtCreator引入多个版本 注意 我已经从以下位置下载了库http
  • Qt 支持在 QIcon 中为 SVG 着色

    看来 Qt 不支持 SVG 中路径标签上的描边 填充选项
  • 如何在 Qt-Embedded 中(正确)输出多语言文本?

    我的目标系统是 linux 3 3 7 Qt Embedded 开源版 4 8 Droid 字体 取自 fonts droid 20111207 git 1 all deb Debian 软件包并复制到 usr lib fonts目录 主要
  • 如何在 C++ 运行时更改 QML 对象的属性?

    我想在运行时更改 QML 对象的文本 我尝试如下 但文本仍然为空 这是后端类 class BackEnd public QObject Q OBJECT Q PROPERTY QString userFieldText READ userF
  • 如何在按下托盘图标菜单操作时执行功能?

    int main int argc char argv QApplication oApp argc argv QAction action1 QMenu menu QSystemTrayIcon TrayIcon QIcon favico
  • QDesktopServices::openUrl 在资源管理器中选择指定文件

    在大多数编码程序中 您可以右键单击该项目 然后单击 在资源管理器中显示 它会在资源管理器中显示选定项目的文件 在 Qt 中使用 QDesktopServices 如何做到这一点 或在 QT 中执行此操作的任何方式 您可以使用此方法在 Win
  • GoQt 致命错误:QAbstractAnimation:没有这样的文件或目录

    我尝试编译 Qt 来开发桌面应用程序 我按照 Qt 网站上的官方 wiki 指南的说明进行操作 当我尝试go run示例文件夹中的示例 我收到错误 去运行 home pinkya rabbit workspace go1programs s
  • Qt WinRT 应用程序无法访问文件权限被拒绝

    我需要使用 Qt 和 FFMPEG 开发 WinRT 应用程序 我根据指令构建了 WinRT 的 ffmpeghere https github com Microsoft FFmpegInterop我可以将库与我的项目链接起来 现在我需要
  • QTextEdit.find() 在 Python 中不起作用

    演示问题的简单代码 usr bin env python import sys from PyQt4 QtCore import QObject SIGNAL from PyQt4 QtGui import QApplication QTe
  • 如何在 Qt 中以编程方式制作一条水平线

    我想弄清楚如何在 Qt 中制作一条水平线 这很容易在设计器中创建 但我想以编程方式创建一个 我已经做了一些谷歌搜索并查看了 ui 文件中的 xml 但无法弄清楚任何内容 ui 文件中的 xml 如下所示
  • 退出 Qt 程序的正确方法?

    我应该如何退出 Qt 程序 例如在加载数据文件时 发现文件损坏 并且用户需要退出该应用程序或重新启动数据文件 我是不是该 call exit EXIT FAILURE call QApplication quit call QCoreApp

随机推荐

  • rknn加载onnx时报错 GLIBC=2.29 no found librknnc.so

    rknn 中onnx转rknn在虚拟机中运行时发现报错 GLIBC 2 29 no found librknnc so 昨天还正常的 今天装了个ftp 和宝塔面板就出错了 我估计根据报错地址 找到了librknnc so文件 权限也给了77
  • Java毕业设计基于springboot企业车辆管理系统设计与实现

    一 项目介绍 随着时代在飞速进步 每个行业都在努力发展现在先进技术 通过这些先进的技术来提高自己的水平和优势 企业车辆管理系统当然不能排除在外 企业车辆管理系统是在实际应用和软件工程的开发原理之上 运用Java语言以及SpringBoot框
  • 全功能tgbot/Telegram机器人多功能有后台版源码

    全功能tgbot telegram机器人多功能有后台版源码 打包好的可以直接上手 trx兑换 闪兑 关键字监控 群管 usdt监控 余额查询 推广分享 等几乎常见的tg机器人功能都有
  • 期权怎么开户:期权开户免费吗,需要什么样的门槛?

    期权开户是免费的 只有交易才会产生费用 开通期权账户需要满足50万的资金 以及融资融券交易经验或者金融期货交易经验 当然也有免50万门槛的开户方式 下文为大家科普期权怎么开户啊 期权开户免费么 一般情况下 期权是可以通过在营业部网点进行开户
  • 深入浅出《Delta-Sigma Data Converters》(可下载)

    在数字信号处理领域 数据转换器是实现模拟与数字世界之间无缝转换的关键组件 而在这个子领域中 Delta Sigma Data Converter s 一书以其全面和深入的内容 为工程师 学者甚至爱好者们提供了一个极其宝贵的资源 今天将为大家
  • go cannot find package “github.com/gorilla/websocket“解读

    Go无法找到包 github com gorilla websocket 的解决方案 在Go开发过程中 我们经常会依赖第三方库来简化开发工作 而使用 go get 命令安装这些库时 有时候我们可能会遇到类似于以下错误的情况 plaintex
  • java.io.IOException: Broken pipe

    做1个接口 处理前端请求图片跨域的问题 由于前端拿图片的时候 有跨域问题 所以让后台先拿到图片 然后再写给前台 本来下面的代码没什么太大的问题 但是如果前台请求的图片一多 1个页面中有很多图片 有些请求就会报错 java io IOExce
  • 数据采集才是MES系统的核心内容

    一 数据采集在MES管理系统中的应用 1 设备数据采集 MES管理系统通过与生产设备的连接 可以实时采集设备运行状态 产量 质量等相关数据 这有助于企业及时掌握设备运行状况 优化设备资源配置 提高设备利用率 2 工艺数据采集 MES管理系统
  • Typecho 最新XC主题 去除域名授权全解密源码

    简介 Typecho 最新XC主题 去除域名授权全解密源码 这是一款多样式主题 首页支持六种主题样式 支持Pjax优化访问速度 多种单页 如友链 说说等 评论支持表情 自定义编辑器 支持其他样式功能 该主题功能性挺高 比较花里胡哨 感觉有一
  • Java毕业设计基于springboot汽车服务管理系统

    一 项目介绍 随着社会的发展 汽车服务的管理形势越来越严峻 越来越多的用户利用互联网获得信息 但汽车服务信息鱼龙混杂 信息真假难以辨别 为了方便用户更好的获得汽车服务信息 因此 设计一种安全高效的汽车服务管理系统极为重要 为设计一个安全便捷
  • 适用于任何公司的网络安全架构

    1 第一等级 基础级 优势 可防范基本有针对性的攻击 使攻击者难以在网络上推进 将生产环境与企业环境进行基本隔离 劣势 默认的企业网络应被视为潜在受损 普通员工的工作站以及管理员的工作站可能受到潜在威胁 因为它们在生产网络中具有基本和管理员
  • 【OCR】实战使用 - 如何提高识别文字的精准度?

    实战使用 如何提高文字识别的精准度 我们在平常使用OCR的时候 经常会出现文字识别不精准的情况 我们改如何提高文字识别的精度呢 以下是一些提高OCR Optical Character Recognition 光学字符识别 文字识别精准度的
  • CMAKE_MAKE_PROGRAM is not set 解读

    目录 CMAKE MAKE PROGRAM 未设置 错误原因 解决方案 示例1 GNU Make 示例2 Ninja CMakeLists txt 的结构 示例 CMakeLists txt 文件 总结 CMAKE MAKE PROGRAM
  • 【每日论文阅读】Do Perceptually Aligned Gradients Imply Robustness?

    近似人眼梯度 https icml cc virtual 2023 oral 25482 对抗性鲁棒分类器具有非鲁棒模型所没有的特征 感知对齐梯度 PAG 它们相对于 输入的梯度与人类的感知非常一致 一些研究已将 PAG 确定为稳健训练的副
  • 黑豹程序员-字符串中查找出重复的字符串

    Collections frequency codeList element 字符串element 在codeList集合中重复的次数 List
  • 字节码指令例子分析

    什么是字节码指令 字节码指令就是由一个字节长度的操作吗和操作数组成 有些只有操作码 没有操作数 例如 bipush 10 第一个参数就是操作码 第二个是操作数 a 和i 有什么区别 public static void main Strin
  • Linux搭建测试环境详细步骤

    本文讲解如何在Linux CentOS下部署Java Web项目的步骤 环境准备 1 Linux系统 2 JDK 3 Tomcat 4 MySQL 工具下载 一 Linux系统 本文主要是Linux CentOS7为例 自己在家练习小项目的
  • 浏览器缓存相关面试题一网打尽,理论结合实践,用代码学习缓存问题,建议关注+收藏,(含项目源代码)

    前言 浏览器缓存的问题是面试中关于浏览器知识的重要组成部分 也是性能优化题目的一部分 但是不要被吓到 我话放到这里 就那么点东西 我这一篇文章基本上就涵盖了所有相关的知识点 认真看一遍 所有的问题都是纸老虎 一 准备工作 1 1 拉取仓库
  • “单项突出”的赢双科技IPO加速,比亚迪是最强助力?

    近日 新能源汽车核心部件供应商赢双科技首次递表科创板 其凭借旋转变压器产品就坐稳了新能源车企主要供应商的地位 从核心业务及业绩情况来看 赢双科技不愧为 单项冠军 据悉 赢双科技本次IPO拟募资8 47亿元 主要将用于年产旋转变压器910万台
  • Qt源码分析:Qt程序是怎么运行起来的?

    一 从 exec 谈起 一个标准的Qt gui程序 在启动时我们会coding如下几行简洁的代码 include widget h include