分类目录归档:Language

Qt QML VideoOutput 显示自定义的 YUV420P 数据流

在一些传统应用中,如果想使用 Qt 在 QWidget 或者 QML 中显示自定义的视频数据流,需要引入 OpenGL 来实现。而实际 Qt 已经准备了 VideoOutput 类型可以很方便的调用系统摄像头和使用自定义数据流。在 Qt 官网中,VideoOutput 的介绍中说明,source 属性可以是一个自定义派生于 QObject 的子类,并提供一个类型为 QMediaObject 的属性命名为 mediaObject,或者是一个派生与 QObject 的子类并提供一个类型为 QAbstractVideoSurface 的属性命名为 videoSurface。其中任意一个方法都可以实现自定义视频数据流的播放,本文介绍第二种方法。参考资料:https://stackoverflow.com/questions/43854589/custom-source-property-for-videooutput-qml

继续阅读

C++ 和 QML 的正确交互方法

相关资料

继续阅读

Qt 模拟 HTTP 表单提交文字或文件到服务器

传统通过 HTTP 表单的方式来上传文件在 Web 中实现是非常简单的,一个表单中加几个域填写上对应的内容提交就可以了,但如果通过 Qt 来实现就相对麻烦一点,不过我都总结好了代码,直接使用就可以了。

需要用到的模块

  • QNetworkAccessManager 用来发起 GET/POST 请求
  • QNetworkReply 用来描述响应信息
  • QHttpMultiPart 用来模拟表单域
  • QNetworkRequest 用来构建请求地址等信息

继续阅读

彻底使用 Cmder 替换掉 Windows 系统默认 Cmd.exe

安装 Cmder 以后,默认的配置中是没有替换掉 Cmd.exe 的,一直以来我认为是没有这种方法可以替换的,包括什么镜像劫持等方式。但是最近看到一篇文章可以在注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths 下创建一个 cmd.exe 子项,然后在 Default 属性中设置要替换的程序。这样设置虽然可以替换到运行中输入 cmd.exe。但是无法替换直接运行批处理文件打开的 cmd.exe。即使你修改了 batfile 的关联程序,也没有特别好的方法去使用 Cmder 来启动一个批处理。

继续阅读

7z 自解压读取 config.txt 配置的代码实现

7z 自解压功能,实际是将三个文件连接在一起,第一个文件是 7z 的自解压模块(实际是一个通用的包含了界面界面的应用程序)+ config.txt(配置文件)+ 实际要解压的 7z 压缩包文件。三个文件通过 Windows 的 copy 命令拼接在一起,你也可以自己实现代码,将三个文件拼接在一起,因为第一个文件的首地址 PE 结构不变,所以当程序运行时相当于运行了 7z 的自解压模块。他们的组成如下图:

图中可以看的出来,使用 copy /b 将三个文件连接在了一起,我们需要在自解压的模块程序 7z_sfx.exe 中实现读取查找 config.txt 文件的位置和内容,从而也就可以得到自解压文件的起始位置。这样在解压文件的时候将包装在我们程序中的自解压程序起始地址传递进去就可以了。

前提条件

首先要在代码中找到被追加进自己程序的 config.txt 文件内容,config.txt 必须要有一个标识来记录文件的开头和结束,这样我们才知道这个文件中间的内容,参考 7z 自解压模块的代码,其在 config.txt 头部和尾部分别设计了两个标识,如下所示:

;!@Install@!UTF-8!
Title="SOFTWARE v1.0.0.0"
BeginPrompt="Do you want to install SOFTWARE v1.0.0.0?"
RunProgram="setup.exe"
;!@InstallEnd@!

在程序中只要将程序一块一块的读取到内存,对比每一个字节如果存在 ;!@Install@!UTF-8! 就是 config 文件的开头,存在 ;!@InstallEnd@! 就是 config 文件的结尾。这样中间的内容也就确定了,文件结尾的位置就是 7z 压缩包文件的开头。

实现代码

代码实现起来要考虑的内容还是比较多的,我参考了 7z 的代码从头实现了一遍,对每一个变量都做了作用注释,因为 7z 官方的代码一个注释都没有,看起来很难懂,索性就参考他的思路一点一点重写了一遍。调用 FindSignature 方法就可以查找到 config.txt 中的内容了,用 strOutput 参数将内容传出。

程序编译完成后,使用 copy /b 程序名 + 带有标记的 config.txt 就可以测试出效果,自己再加上解压的代码你就可以实现一个属于自己的自解压模块了。

#include <iostream>
#include <fstream>
#include <windows.h>

static char kBeginSignature[] = { ';','!','@','I','n','s','t','a','l','l','@','!','U','T','F','-','8','!', 0 };
static char kEndSignature[] = { ';','!','@','I','n','s','t','a','l','l','E','n','d','@','!', 0 };

bool FindSignature(const std::string& strBeginSignature, const std::string& strEndSignature, std::string& strOutput)
{
    const size_t nFixedBufferSize = (1 << 12);
    char szApplication[MAX_PATH] = { 0 };
    GetModuleFileNameA(NULL, szApplication, MAX_PATH);

    FILE* hFile = NULL;
    fopen_s(&hFile, szApplication, "rb");

    // 标记是否找到头部
    bool bFoundBegin = false;

    // 记录需要跳过多少个字节(上一次读取长度不足的内容会被填充到当前 buffer 中)
    size_t nBytesPrev = 0;

    BYTE szBuffer[nFixedBufferSize] = { 0 };

    for (;;)
    {
        size_t nReadSize = nFixedBufferSize - nBytesPrev;
        size_t nProcessedSize = fread(szBuffer + nBytesPrev, 1, nReadSize, hFile);
        if (nProcessedSize == 0)
            return false;

        // 上一次读取剩余的字节 + 本次读取到的字节总数
        size_t nTotalSize = nBytesPrev + nProcessedSize;

        // 标记读取出来的内存块中已经对比的数据位置
        size_t nPos = 0;

        for (;;)
        {
            if (!bFoundBegin)
            {
                // 剩余长度不足头部内容,直接跳出
                if (nPos > nTotalSize - strBeginSignature.size())
                    break;

                // 标记已经找到头部,找到头部后将读取指针移动到头部关键字末尾
                if (memcmp(szBuffer + nPos, strBeginSignature.c_str(), strBeginSignature.size()) == 0)
                {
                    bFoundBegin = true;
                    nPos += strBeginSignature.size();
                }
                else
                {
                    nPos++;
                }
            }
            else
            {
                // 剩余长度不足尾部内容,直接跳出
                if (nPos > nTotalSize - strEndSignature.size())
                    break;

                // 如果找到末尾则直接返回
                if (memcmp(szBuffer + nPos, strEndSignature.c_str(), strEndSignature.size()) == 0)
                    return true;

                // 将不是末尾标记的数据追加给传出参数
                BYTE pByte = szBuffer[nPos];

                // 程序中常量字符串末尾是 0,但文件中不是 0,如果读到 0 证明是程序中的常量,而不是文件中的
                if (pByte == 0)
                {
                    bFoundBegin = false;
                    break;
                }
                strOutput += pByte;

                // 向后步进
                nPos++;
            }
        }

        // 记录下次需要跳过的字节数量
        nBytesPrev = nTotalSize - nPos;

        // 将不足以对比的剩余内容拷贝到 buffer 的首位,下次读取的新数据衔接在该数据后面
        memmove(szBuffer, szBuffer + nPos, nBytesPrev);
    }

    if (hFile)
    {
        fclose(hFile);
    }

    return false;
}

int main()
{
    std::string strOutput;
    if (FindSignature(kBeginSignature, kEndSignature, strOutput))
        std::cout << strOutput.c_str() << std::endl;
    else
        std::cout << "Not found..." << std::endl;

}

CEF 修改请求 header 与单独处理 header 中的 referer

有些时候利用 CEF 内嵌的页面加载某些资源的时候需要附带一些头信息,比如里面的图片需要携带一些校验和信息才能正常访问的,这个时候就需要在发起请求前对 HTTP Request 的 Header 部分进行修改。CEF 提供了两个接口用于读写 Request Header,分别是 CefLifeSpanHandler::OnAfterCreatedCefRequestHandler::OnBeforeResourceLoad 方法。两个都是虚函数,需要继承并重写两个方法来实现对 Request Header 的读取和修改。

继续阅读

Qt QSS 属性选择器使用详解

Qt 的属性选择器是其独有的,非常类似 CSS 的类选择器,但是由于 CSS 的类选择器可以设置多个,所以一个标签只要设置多个的 class 就可以实现不同的效果了甚至重叠效果。但是 Qt 的类选择器没有那么强大,他不能给控件设置多个类标识。所以就有了属性选择器这么个东西。

继续阅读

Qt Creator 项目属性配置常用设置

设置编译后目标保存目录

DESTDIR     = bin

设置一些编译过程中临时文件目录

MOC_DIR     = tmp/moc
OBJECTS_DIR = tmp/obj
UI_DIR      = tmp/ui
RCC_DIR     = tmp/rcc

设置头文件的拓展目录

INCLUDEPATH += $$PWD/libs/

根据不同属性链接不同的静态库

win32:CONFIG(release, debug|release):       LIBS += -L$$PWD/libs/ -ljsoncpp
else:win32:CONFIG(debug, debug|release):    LIBS += -L$$PWD/libs/ -ljsoncpp_d

或者

LIBS        += -L$$PWD/libs/
win32 {
    CONFIG(debug, debug|release) {
        LIBS += -ljsoncpp_d
        LIBS += -lnim_cpp_sdk_d
    } else {
        LIBS += -ljsoncpp
        LIBS += -lnim_cpp_sdk
    }
}

根据不同操作系统设置平台化差异属性

unix {
    target.path = /usr/local/lib
}

win32 {
    target.path = C:\Windows
}

INSTALLS    = target

更多请参考:http://doc.qt.io/qt-5/qmake-variable-reference.html