在传统的应用程序设计中,我们可以看到很多通过浏览器唤起本地应用的案例,比如百度网盘、迅雷等工具,他们在浏览器访问一个非 http/https 协议开头的地址时,会自动打开其自己的应用程序并传递一定的参数。该功能的实现方式网络上有很多示例,在 Windows 和 macOS 不同平台下他们分别需要如下设置:
使用 CMake + Git Submodule 方式管理项目三方库
在项目开发过程中,避免不了要使用一些开源的三方库,我参加过的一些团队有不同的管理三方库的方式。不同的方式都各有优缺点,我们先列举一下碰到过的管理方式,说一些他们的优缺点,最后再来讨论我们今天介绍的管理方式弥补了哪些缺点。
NSIS 打包 Electron 项目的注意事项(拖拽、权限、兼容性等)
我自己本人不做 Electron 的开发,但有一位合作伙伴在使用 NSIS 打包 Electron 应用的时候遇到了一些问题,主要问题有以下几个,先记录下来,然后追个击破。
- 打包后应用在 Windows 7 无法直接运行,需要修改兼容性为 Windows 7 才可以使用
- 打包后安装或者卸载时应用在运行会安装或者卸载失败,无法替换或删除应用(这个与 Electron 无关但也介绍一下)
- 打包后应用第一次启动无法使用拖拽功能
Qt for iOS 应用使用自定义动态库 Framework
本文内容适合当你想使用 Qt 调用 xcode 开发的动态库 framework 做 iOS 应用的场景,文中涉及到原始动态库 framework 文件的配置,以及 Qt 如何引入和打包动态库 framework 到自身程序中的方法。
动态库 framework 的配置
默认 xcode 创建的 framework 都已经是动态库形式了,所以这里不多介绍,请确保你的 Build Settings 中以下设置是无误的,特别是 Dynamic Library Install Name Base
,它决定了动态库能否被应用成功搜索到。
对于 @rpath 的说明,请看这里:https://www.cnblogs.com/csuftzzk/p/mac_run_path.html
Qt 项目配置引入 framework
Qt 引入 framework 只需要给 LIBS 追加 framework 的路径和要链接的文件就可以了,配置如下,请注意使用 framework 参数是 -F 和 -framework(注意大小写)
LIBS += -F$$PWD/../../build/Products/Release
LIBS += -framework my_dylib.framework
这样引入还是不行的,因为 iOS 应用部署上去以后动态库的 framework 文件是不会跟随打包进去的,所以你还需要做一件事情就是把 framework 打包到你的应用中,配置如下:
MY_DYLIB_FRAMEWORK.files = $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework
MY_DYLIB_FRAMEWORK.path = /Frameworks
QMAKE_BUNDLE_DATA += MY_DYLIB_FRAMEWORK
分别指定了 framework 文件的路径和要被打包进去的目标路径,这里是将 $$PWD/../../build/Products/Release-iphoneos/my_dylib.framework 这个 framework 打包到了应用的 /Framework 目录下。最终你看到的应用目录结构是这样的
├── Default-568h@2x.png
├── Frameworks
│ └── my_dylib.framework
│ ├── Info.plist
│ ├── ReadMe.txt
│ ├── _CodeSignature
│ │ └── CodeResources
│ └── my_dylib
├── Info.plist
├── LaunchScreen.nib
├── PkgInfo
├── _CodeSignature
│ └── CodeResources
└── cross-platform-demo
可以看到,mu_dylib.framework 文件已经在我们应用的 /Framework 目录下了,这样文件就被打包进去了,而且当你使用 Qt 编译程序的时候,在 Build Shadow 目录下会看到生成对应的 xcodeproj 文件,可以直接使用 xcode 打开,使用 xcode 打开项目后可以看到项目的 Build Phases 下面多了一条 Copy file to bundle 的项目:
但是当你尝试在 iOS 或者模拟机中运行这个应用时你会发现又有新的错误了,如下所示:
dyld: Library not loaded: @rpath/my_dylib.framework/my_dylib
Referenced from: /var/containers/Bundle/Application/D0143CDE-FFFE-4343-BFD9-D70DA66C831F/cross-platform-demo.app/cross-platform-demo
Reason: image not found
program received signal 6, thread:15362b
我明明已经把文件复制进去了,为什么还是会提示,还记得上面我们提到的 Dynamic Library Install Name Base
吗?这是动态库 framework 设置的,根据上面文章的资料,我们要在调用该模块的应用中设置 rpath 的搜索范围,让其能找到我们的动态库文件。Qt 项目中添加如下配置:
# 添加应用的 runpath 路径,因为 my_dylib 动态库 Framework 设置的 install path 为 rpath,所以应用使用时需要单独设置
QMAKE_LFLAGS += -Wl,-rpath,@loader_path/Frameworks
如此设置后,在 Qt 中就可以成功编译程序并运行在模拟器或真机上了,如果还有任何疑问欢迎留言我们一起讨论。
wordpress 异常访问 wp-login.php?action=lostpassword 导致站点流量异常无法访问
今早起来看了下博客的内容,发现站点打不开了,想想不对,昨天刚换好的服务器,怎么忽然就打不开了?ping 了一下服务器地址是通的,但是延迟非常高。随后 ssh 登录到服务器后查看站点的日志,发现有一个上海的 IP 大量的请求地址 wp-login.php?action=lostpassword
,对 wordpress 的代码不是很了解,但看这个地址应该是一些暴力破解用着相关漏洞利用。
通过日志可以看到是一个 210.52.224.* 的 IP 段,查了一些资料,在 /etc/rc.local
中添加整个 210.52.224.* 的 IP 段屏蔽他们的请求,随后站点恢复正常:
#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.
iptables -I INPUT -s 210.52.224.0/24 -j DROP
如果你也遇到类似问题,在文件末尾像我一样增加这个 IP 段,然后重启服务器就可以屏蔽他的连接了。
利用 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
的最后一个参数。写也是一样,我们可以用 GetOverlappedResult
或 WaitForSingleObject
来等待操作事件完成(记得要初始化 OVERLAPPED
否则会报错的)。两种方式读写我都做了演示,可以根据自己的需求改造。 读写操作的时候可以获取当前返回值判断是不是 ERROR_IO_PENDING
来确定是不是有数据还没有读取完成。
总结
串口的读写其实还是相对简单的,上面代码基本上把可能出现问题的点都体现出来了,最后再来罗列一下注意事项。
- 打开串口时要根据硬件情况初始化串口参数(在 WinBase.h 中有声明)
- 设置串口的缓冲区和超时
- 异步去读写串口通过返回值判断是否读写成功
- 不要忘记初始化 OVERLAPPAD 结构
- 读取完成后 PurgeComm 串口
利用 NvAPI 设置数字振动数值
开始是一位朋友有这个需求,他给了我一个英伟达官网的开发包,名字是:R410-developer.zip(诸位可以自己到英伟达官网下载),里面提供了一些示例,包含自定义分辨率、显示器颜色设置等,但是显示器色彩设置的例子一致没有跑通,而且我也没有找到哪个参数是可以设置数字振动值的,所以憋屈了很多天。但直到看到了一个 AHK 版本实现的设置工具通过代码发现,其实实现方法是通过 nvapi.dll 动态库导出的一个查询函数地址的方法,将指定接口导出来执行具体业务。再加上在 github 上搜索的各类示例,最终实现了这个功能,下面详细介绍实现步骤。
Cmder git 中文显示乱码解决方案
Cmder 是 Windows 下替换原有 cmd 的绝佳工具,但是与默认的 cmd 一样,都与 git 命令行存在一些兼容性问题,比如中文乱码问题。在 Cmder 安装目录下的 config/user-profile.cmd
中添加如下代码,可以解决中文乱码的问题。
set LANG=zh_CN.UTF8
添加后的配置文件
添加后重新打开 Cmder 的效果
将 Cmder 加入到右键菜单,到 Cmder 目录下执行如下命令即可:
Cmder.exe /REGISTER ALL
添加后的效果:
另外你还可以修改终端起始符号 λ 改成 $,修改 vendor/clink.lua
文件,将
"\x1b[1;32;40m{cwd} {git}{hg}{svn} \n\x1b[1;39;40m{lamb} \x1b[0m"
替换为 (实际就是将 {lamb} 替换为 $ )
"\x1b[1;32;40m{cwd} {git}{hg}{svn} \n\x1b[1;39;40m$ \x1b[0m"
参考:https://blog.csdn.net/qq_33304418/article/details/79918123
Windows 编译 openssl-1.0.2p(libeay32.lib、ssleay32.lib)静态库 32/64、debug/release、MT/MTd
最近要用到 libeay32.lib、ssleay32.lib 两个静态库文件,因为以前项目中其中一个文件在 64 位编译时选择的不是 MTd,而导致我引入该库以后提示运行时库和其他库声明冲突,其实实际原因就是生成选项不一样。最终我还是决定自己编译 openssl 的库来使用。
下载所需工具
- Perl:http://downloads.activestate.com/ActivePerl/releases/5.26.1.2601/ActivePerl-5.26.1.2601-MSWin32-x64-404865.exe
- NASM:https://www.nasm.us/pub/nasm/releasebuilds/2.14rc15/win64/nasm-2.14rc15-installer-x64.exe
- OpenSSL:https://www.openssl.org/source/openssl-1.0.2p.tar.gz
准备工作
安装 Perl 和 NASM,默认下一步下一步就可以了。Perl 安装的时候记得勾选将执行程序添加到系统环境变量中。NASM 安装时没有选项,需要在完成后要将执行程序添加到系统的环境变量中。如下图所示:
解压 openssl-1.0.2p.tar.gz 到任意目录,比如 D:\openssl-1.0.2p
开始编译
打开 VS 的命令行工具(我这里安装的是 VS2013),所以目录在 C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts
下,如果想编译 32 位的静态库就使用 VS2013 x86 本机工具命令提示
,如果想编译 64 位,就使用 VS2013 x64 本机工具命令提示
。
打开后切换到 D:\openssl-1.0.2p 目录,执行如下命令生成 makefile 文件。
perl configure VC-WIN32
如果是生成 64 位则执行
perl configure VC-WIN64A
成功后如下所示:
如果你要编译 debug 版本,则修改 ms/do_nasm.bat
文件,将原来
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl nasm VC-WIN32 >ms\nt.mak
perl util\mk1mf.pl dll nasm VC-WIN32 >ms\ntdll.mak
perl util\mk1mf.pl nasm BC-NT >ms\bcb.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
修改为:
perl util\mkfiles.pl >MINFO
perl util\mk1mf.pl nasm debug VC-WIN32 >ms\nt.mak
perl util\mk1mf.pl dll nasm debug VC-WIN32 >ms\ntdll.mak
perl util\mk1mf.pl nasm BC-NT >ms\bcb.mak
perl util\mkdef.pl 32 libeay > ms\libeay32.def
perl util\mkdef.pl 32 ssleay > ms\ssleay32.def
就是将第二行和第三行编译选项增加了 debug。修改完成后执行 ms/do_nasm.bat
。
ms\do_nasm.bat
运行后结果如下:
修改完成执行如下命令开始编译(如果想编译成 dll,则执行 nmake -f ms\ntdll.mak,编译前要修改 ms\ntdll.mak 将 CFLAG 的 /MD 属性修改为 /MT,与你调用项目匹配):
nmake -f ms\nt.mak
如果没有错误,几分钟后编译后的文件就会生成于 D:\openssl-1.0.2p\out32
目录下。
使用 docker 快速搭建一个 VPN 服务器
你还在一条条命令复制粘贴搭建 SVN 服务器?快别麻烦了,一个 docker 镜像就都帮你搞定了。如果你的服务器安装了 docker,直接使用以下命令(要修改你自己的账户和密码信息哦)拉取一个已经配置好的 VPN 镜像就可以直接使用了,不用那么那麻烦的自己又改配置文件又要安装软件。
docker run \
--name ipsec-vpn-server \
--restart=always \
-e VPN_IPSEC_PSK=你的PSK密码 \
-e VPN_USER=你的VPN用户名 \
-e VPN_PASSWORD=你的VPN密码 \
-p 500:500/udp \
-p 4500:4500/udp \
-v /lib/modules:/lib/modules:ro \
-d --privileged \
hwdsl2/ipsec-vpn-server
管理 macOS 启动项
macOS 启动项大家众所周知的是 系统偏好设置->用户与群组->登录项
里面的设置,但有时候我通过这里无法找到系统自动启动的应用程序,比如 Microsoft Office AutoUpdate 组件,它会在系统启动时自动加载,但登录项里面却看不见。所以网络上搜索一下,摘录了部分重要的内容,原文在此:
https://www.jianshu.com/p/49dabd8ec9bb