彻底使用 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 的类选择器没有那么强大,他不能给控件设置多个类标识。所以就有了属性选择器这么个东西。

继续阅读

git pull 代码的时候默认使用 rebase 而不是 merge

git pull 实际会有两个操作,一个是 git fetch,另外一个是 git merge。一般 merge 的情况下会产生一个新的提交名字为 Merge branch ****,如下图所示:

这个新的提交会导致提交记录中产生多余的提交信息,实际与解决问题相关的提交不符而且对于一些洁癖来说这种难以接受,所以 git 提供了一个 rebase 的方式来替代 merge,rebase 可以按顺序结构重新整合提交顺序而不是产生一个新的提交。具体的区别大家可到网络上搜索一下这里重点不是介绍他们两个的区别。

而如果你希望每次拉代码的时候不需要执行 git fetch 后再执行一次 git rebase,而是像以前一样直接执行 git pull 而是使用 rebase 来合并代码的话,那以下命令可以帮到你。

git config --global pull.rebase true

执行次命令后,每次 git pull 都将是一个 git fetch + git rebase 的过程了,而不是以前的那种方式。

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

CEF 设置页面缩放级别

现在很多高分屏在笔记本和家用市场逐步扩大,普通应用大小在一个 2K 或者 4K 屏幕下就像一个便利签一样贴在屏幕上面,看着很小。于是操作系统提供了对分辨率进行缩放的功能,比如我可以设置当前分辨率放大到 125%,这样就可以让字体或者窗口看着更大一些。同样基于 CEF 制作的客户端程序也会随之放大到 125%,但是内嵌的网页呢?当然也需要根据系统设置放大缩小,CEF 给我们提供了 SetZoomLevel 方法让我们来设置页面的缩放比例。

继续阅读