作者归档:myCode

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 方法让我们来设置页面的缩放比例。

继续阅读

CEF 拦截打开超链接事件

使用 CEF 加载指定页面后,如果你希望控制页面在打开超链接时根据自己预定义的一些行为来操作,比如在自己的 UI 框架中新建一个 Tab 页又或者阻止打开新的页面等。我们就需要通过 CEF 提供的两个接口来实现这个功能了,分别是 OnBeforeBrowser 和 OnBeforePopup,两个接口各有各的用处,下面分别来详细介绍两个接口的作用。

继续阅读

CEF 文件下载功能实现

CEF 下载功能非常容易拓展,它提供了丰富的接口和控制功能,比如对正在下载的文件实现暂停、继续、取消等操作。并且 CEF 还帮我们默认实现了一个另存为的对话框,如果不是必须你甚至都不需要去自己实现这个保存对话框。接下来我们来看 CEF 对于下载功能提供的两个接口(使用 cefclient 项目举例)

继续阅读

利用 WM_NCHITTEST 消息自定义窗口可拖动区域

最近接到一个比较个性化的需求,原来团队基于 Electron 开发的项目由于不支持 XP 系统,想完全移植到基于 CEF 2623 版本的空壳中,以此来兼容 XP 系统。而程序的界面则完全交由前端来处理了,比如标题栏、最小化、最大化等功能按钮。这其中就涉及到一些原生应用的功能比如拖动窗口的功能是需要原生窗口提供的,微软提供拖动窗口的机制如此文章所描述:https://www.cnblogs.com/GnagWang/archive/2010/09/12/1824394.html。为了保证内容不丢失,我特意截图保留一份。

继续阅读

VS2017 打开并编译 cef client 项目进行调试

最近接触 cef 非常多,有些功能没有做过就去 cef 官网的 cef_client demo 中查找示例,所以第一步是先把他给编译通过,网络上看过一些方法和例子需要单独下载 CMake,其实 VS2017 自带了这个功能。在你安装 VS2017 的时候默认勾选了 CMake 工具集。我们直接使用 CMake 工具打开项目就可以了,如下所示:

微软官方的教程:https://docs.microsoft.com/zh-cn/cpp/ide/cmake-tools-for-visual-cpp?view=vs-2017

用 VS2017 打开 CMakeLists.txt 文件

然后选择 cef_client 的客户端 CMakeLists.txt 文件:

随后项目会进行初始化:

点击上方生成的类型下拉框点击管理配置,在弹出的窗口中选择 x86 Debug

选择后 VS 会在 CMakeLists.txt 目录下创建一个 CMakeSettings.json 的配置文件,这个文件保存了项目配置信息以后可以自己手动随时修改。在选择启动项的下拉菜单中选择启动 cef_client 项目:

然后按下 F7 开始编译项目,生成完成后点击上面的绿色启动按钮就可以启动项目了,并且你也可以随时调试项目了。

VC++ 串口开、关、读、写操作及注意事项

最近帮朋友做一款工具,设计到对操作系统串口的操作,虽然这个东西已经是历史产物了,但是还有很多设备再用,索性从网络上找了一些代码最终完成这个小功能。下面资料将介绍串口在打开、关闭、读和写的时候一些注意事项以及参数的配置(代码中有详细注释。)

串口的开关

在串口打开的时候,我们要对串口做一些基础的初始化,比如波特率、数据位、校验位、停止位几个参数,他们分别被声明在 WinBase.h 头文件中。

打开串口的代码如下:

bool SerialPortManager::Open(ReceiveDataCallback cb/* = nullptr*/)
{
    if (serial_handle_ != NULL)
    {
        return false;
    }

    if (cb != nullptr)
    {
        cb_ = cb;
    }

    serial_handle_ = CreateFile(com_.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    if (serial_handle_ == INVALID_HANDLE_VALUE)
    {
        return false;
    }

    // 获取旧的 dcb 数据
    DCB dcb;
    dcb.DCBlength = sizeof(DCB);
    if (!GetCommState(serial_handle_, &dcb))
    {
        Close();
        return false;
    }

    // 修改 dcb 数据然后设置端口属性
    // CBR_115200;
    dcb.ByteSize = byte_size_;
    dcb.BaudRate = baud_rate_;
    dcb.StopBits = stop_bits_;
    dcb.Parity = parity_;
    dcb.fBinary = TRUE;
    dcb.fParity = TRUE;
    if (!SetCommState(serial_handle_, &dcb))
    {
        Close();
        return false;
    }

    // 设置读写缓冲区大小
    SetupComm(serial_handle_, 1024, 1024);

    // 清空数据
    PurgeComm(serial_handle_, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);

    // 设置超时 10 秒
    COMMTIMEOUTS to;
    memset(&to, 0, sizeof(to));
    to.ReadIntervalTimeout = 1000;
    to.ReadTotalTimeoutMultiplier = 500;
    to.ReadTotalTimeoutConstant = 5000; //设定写超时
    to.WriteTotalTimeoutMultiplier = 500;
    to.WriteTotalTimeoutConstant = 2000;
    SetCommTimeouts(serial_handle_, &to);

    PostReadThread();

    QLOG_APP(L"Serial port device is ready, serial: {0}, baud rate: {1}, byte size: {2}, parity: {3}, stop bits: {4}.")
        << com_ << baud_rate_ << byte_size_ << parity_ << stop_bits_;

    return true;
}

其中除了打开串口时传递的参数外,还包含了一些串口处理数据超时、读写缓冲区大小等属性,需要用到的根据自己的环境来配置。 串口的关闭很简单,只需要关闭掉 CreateFile 返回的句柄就可以了,这里不多介绍。

串口读写

串口的读写可以同步也可以异步,但是同步方式会造成一个问题就是当你调用了 ReadFile 在等待串口数据时,再去调用 WriteFile 就会被阻塞,因为 ReadFile 一直没有返回。所以我还是推荐大家用异步方式来读写串口,代码如下:

void SerialPortManager::ReadSerialPortThread()
{
    QLOG_APP(L"PostReadThread is running....");
    while (TRUE)
    {
        if (!serial_handle_)
        {
            QLOG_ERR(L"Failed to read data from serial port, serial port handle is null.");
            break;
        }

        // 计算最小需要读取的数据量
        DWORD read_size = 1024;
        // read_size = min(read_size, (DWORD)com_stat.cbInQue);

        // 开始异步读取
        OVERLAPPED over_lapped;
        memset(&over_lapped, 0, sizeof(OVERLAPPED));
        over_lapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        std::shared_ptr<BYTE> buffer;
        buffer.reset(new BYTE[read_size]);
        BOOL bReadStatus = ReadFile(serial_handle_, buffer.get(), read_size, &read_size, &over_lapped);
        if (!bReadStatus) // 如果 ReadFile 函数返回 FALSE
        {
            DWORD last_error = GetLastError();
            if (last_error == ERROR_IO_PENDING)
            {
                QLOG_APP(L"Read file return ERROR_IO_PENDING..");
                BOOL bRet = GetOverlappedResult(serial_handle_, &over_lapped, &read_size, TRUE);
                if (bRet)
                {
                    // 返回 true 代表读取到了数据
                    QLOG_APP(L"Read data {0}") << nbase::UTF8ToUTF16((char*)buffer.get());
                    cb_((char*)buffer.get());
                    PurgeComm(serial_handle_, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
                }
                else
                {
                    // 返回 false 可能是句柄已经被 close 了
                    QLOG_APP(L"GetOverlappedResult returned false");
                }

                continue;
            }

            QLOG_ERR(L"Failed to read data from serial prot, error code = {0}") << last_error;
        }
    }

    QLOG_APP(L"PostReadThread is quit....");
}
bool SerialPortManager::WriteData(const std::string& data)
{
    QLOG_APP(L"Begin to write data [{0}] to serial port.") << data;

    DWORD bytes_written = data.size() + 1;
    OVERLAPPED over_lapped;
    memset(&over_lapped, 0, sizeof(OVERLAPPED));
    over_lapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    BOOL write_stat = WriteFile(serial_handle_, data.c_str(), bytes_written, &bytes_written, &over_lapped);
    if (!write_stat)
    {
        DWORD last_error = GetLastError();
        if (last_error == ERROR_IO_PENDING)
        {
            WaitForSingleObject(over_lapped.hEvent, 2000);
            return true;
        }

        return false;
    }

    QLOG_APP(L"Finished to write data.");
    return true;
}

读因为是异步操作,我们需要传一个 OVERLAPPED 结构体到 ReadFile 的最后一个参数。写也是一样,我们可以用 GetOverlappedResultWaitForSingleObject 来等待操作事件完成(记得要初始化 OVERLAPPED 否则会报错的)。两种方式读写我都做了演示,可以根据自己的需求改造。 读写操作的时候可以获取当前返回值判断是不是 ERROR_IO_PENDING 来确定是不是有数据还没有读取完成。

总结

串口的读写其实还是相对简单的,上面代码基本上把可能出现问题的点都体现出来了,最后再来罗列一下注意事项。

  • 打开串口时要根据硬件情况初始化串口参数(在 WinBase.h 中有声明)
  • 设置串口的缓冲区和超时
  • 异步去读写串口通过返回值判断是否读写成功
  • 不要忘记初始化 OVERLAPPAD 结构
  • 读取完成后 PurgeComm 串口