支持跨平台:已验证可运行在的系统 ヾ(≧▽≦*)o;
① Windows 7/10/11+
②Ubuntu 22.04+,Deepin/UOS V20, openkylin 1.0
③MacOS: 10.14 / 11 / 12 / 13 / 14+
现已上架 Microsoft Store 和 Linux 多个应用商城,撒花🎊🎊🎊🎉🎉🎉;
开发完之后一直也没有宣传过,寂静的在的一隅仓库呆着着; 直到连续几天发现涌入大量流量,Star 也随之猛长;蛤?着什么情况,我啥也没干了。问了下新人,才知 Sunny 已被很多大佬和知名软件网站给翻牌子了,属实意外惊喜╰(°▽°)╯。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
Sunny 截图是使用纯 C++ 开发的一款简洁且漂亮的 “截图&拼图” 的软件工具,亦支持图片翻译和OCR 等功能;支持 Windows,MacOS,Linux 平台。
本项目从 2023 年起,至少会持续开发和维护十年,再更久的事情,那就彼时再说~;可以放心使用,因为我自身也在使用;
**官网: ** https://sunny.xmuli.tech | 作者: 偕臧
GitHub | Gitee | Releases | Issues | App Store : Microsoft Store / Deepin Store / UOS Store / Spark Store /
反馈 Bug, 提出建议或者想法,任何帮助改进,亦可加入交流群:
👉 Sunny 截图是一款简洁且漂亮的截图的软件工具,亦支持【图片翻译】和【OCR】
Ubuntu 22.04
Deepin V20.9 / UOS
openkylin V1.0.1
极力推荐从 GirHub Release 下载,更新最快最稳定。下载文件后,文件右键属性校验其唯一性,确保文件没被篡改;
Keys | Description | Mode |
---|---|---|
F6 | 窗口激活截图图 | 全局 |
Ctrl + F6 | 仅延时截图 | 全局 |
Shift + F6 | 自定义截图(起点位置 + 截图区域 + 延迟时间) | 全局 |
🐱🐉 | ||
Esc | 退出截图 | 本地 |
Tab | 切换挡位,检测的子窗口矩形的深度 | 本地 |
` / ~ | 显示捕捉窗口的属性信息 (Path/Size/PID/HWnd …) | 本地 |
Ctrl + Shift + S | 快捷键快速保存到到指定的路径 | 本地 |
←, ↓, ↑, → ( A, S, W, D ) | 移动选中区域 1px | 本地 |
Ctrl + ←, ↓, ↑, → | 外扩选中区域 10 px | 本地 |
Shift + ←, ↓, ↑, → | 内缩选中区域 10 px | 本地 |
在 Window 10/11+ 系统,也在微软商店直接下载
深度社区、 统信操作系统商店中,已可直接下载
以及三方的星火商店,也可直接下载
很久之前就想些一个软件截图的软件,目前一共写如下三个层级的难度作品,提供大家参考
项目 | 描述 | 开发经验 |
---|---|---|
ShotX | 功能极简的截图工具 | 简易,新手级的截图,适合初学 Qt/C++ 入门 |
FLIPPED | 简洁且漂亮,功能完整的截图软件;隐私安全,无任何联网功能 | 高级难度,属 Qt/C++ 数年经验的进阶作品,在借鉴同类作品的代码时,可于探索中独立完成的一个大的软件 |
Sunny | 一款简洁且漂亮的截图的软件工具。亦支持图片翻译和OCR;已上架微软商店,深度/统信商店,及三方的星火商店等 | 专业级作品,适合已多年沉浸研究 Qt/C++ 经验,随心所欲写任意所需功能,属于商业级的成熟作品,是本截图系列的最高水准之作 |
注: ShotX,FLIPPED,Sunny 这三款均支持跨平台 Windows / MacOS / Linux。
笔记: Sunny = FLIPPED的功能重构 + 代码重构 + UI重构 + 网络功能(图片翻译+OCR)+ 上架应用商店 + 后续新功能;而 ShotX 是最早的练手探索
在写和发布的后的期间,也遇到很多私聊请教 、 邮件沟通某个功能实现?反馈 Bug 和给出使用心得和建议;都给答疑了,但想来可写为一整篇,中间遇到的困难点都写出来,公开出来提供后来者参考。
编译环境
💻 MacOS 13
📎 Qt 5.15.2
📎 gcc/g++ 9.2
📎 gdb8.3
💻 Ubuntu 22.04
📎Deepin 20.9-23+
📎 Qt 5.15.2
📎 gcc/g++ 9.0
📎 gdb8.0
💻 win10 22H2
📎 Qt 5.15.2
📎 Visual Studio 2022
📎 C++17
『问题』写的 EXE 如何进行代码签名?如何购买代码签名,怎么买最便宜?EV / Standard / Open Source Signing Certificates 的区别是什么?
详细解答这些问题 《分享如何拥有一份私人的『开源代码签名证书』》,并且指导最后如何签名。
『问题』如何上架到微软的 Microsoft Store?如何上架 Linux 的深度/统信/麒麟商城,以及如三方的星火商店呢?
篇幅太长,单写了一篇,包含详细上架 Windows Store, Deepin/UOS Store, 三方 星火商店等。
👉 《Sunny截图上架Microsoft Store及Linux商店流程的指北》
Note:
个人作品上架微软商店的流程很折磨,最后上架成功后也是拨开云雾;
若是文章对你有价值,亦可帮我积累Sunny的微软信誉,或者在深度商店的好评,甚至感谢🙇 ;
Windows 用户推荐的下载 👉 Sunny_setup_msvc_1.3.0_x64.exe
介绍多种平台和格式:①Windows:绿色便携版和安装包 .exe
②MacOS:.app
和 .img
③Linux: 绿色版、 .deb
和 .AppImage
『问题』Windows 如何构建打包为 .exe 文件?如何生成构绿色版和安装版?
『问题』MacOS 如何构建打包为 .dmg 文件?
『问题』Linux 如何构建打包为 .deb 文件、如何打包为通用的 .AppImage 格式?
Linux下又多种打包 .deb
打包方法:
〖方法一〗通过 ldd.sh + Sunny.sh 两个脚本打包依赖,参考《QT 项目在 Linux 平台上面发布成可执行程序》
〖方法二〗通过 dh_make
+ dpkg-buildpackage
命令《Linux 中用 dh_make 将 Qt + CMake 项目打包为 deb 文件》
〖方法三,最推荐〗通过 CMake 的 cpack
命令,那样就不需要填写 debian 文件夹下的 control 等文件,直接拷贝相关资源文件过去。 CMakeLists.txt 底部加上 CPack 的相关代码,核心如下:
# CPACK: General Settingsset (CPACK_GENERATOR "TBZ2")set (CPACK_PACKAGE_NAME "${project_name}")set (CPACK_PACKAGE_VERSION "${project_version}")set (CPACK_PACKAGE_VENDOR "https://github.com/XMuli")set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Simple and beautiful screenshot software tool for Windows, MacOS and Linux")set (CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}")set (CPACK_PACKAGE_CONTACT "https://sunny.xmuli.tech")# 设置Debian软件包的依赖关系set (CPACK_DEBIAN_PACKAGE_DEPENDS "libqt5x11extras5, libqt5svg5")set (CPACK_SYSTEM_NAME "${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")set (CPACK_DEBIAN_PACKAGE_SHILIBDEPS ON)include(CPack)
Linux下有多种打包 .AppImage
打包方法
#【方式四】使用 linuxdeployqt 方式打包,在 Ubuntu 22.04 打包,不可以使用 -----------------------------####linuxdeployqt-continuous-x86_64.AppImage 方案可在 Ubuntu 22.04 上面不可行####$ ../linuxdeployqt-continuous-x86_64.AppImage Sunny -appimage$ sudo apt install libfuse2但是由于过于作者的固执坚守旧的版本,所以无法使用,理由和可能的解决如下:https://github.com/probonopd/linuxdeployqt/issues/340#issuecomment-932712016即:使用linuxdeploy和linuxdeploy-plugin-qt#####linuxdeploy-x86_64.AppImage + linuxdeploy-plugin-qt-x86_64.AppImage 下面方案可行#####https://github.com/BearKidsTeam/thplayer/blob/master/.github/workflows/linux.yml#L54$ sudo apt update$ sudo apt install qtbase5-dev qtmultimedia5-dev libqt5multimedia5-plugins$ sudo add-apt-repository universe$ sudo apt install libfuse2$ wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage$ wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage$ chmod +x linuxdeploy*.AppImage$ mkdir build && cd build$ cmake ..$ cmake --build . -j$(nproc)$ cd ..$ ../linuxdeploy-x86_64.AppImage --appdir AppDir -e bin/Sunny -d bin/resources/cpack/tech.xmuli.sunny.desktop -i bin/resources/logo/logo.svg --icon-filename tech.xmuli.sunny -p qt -o appimage$ ./linuxdeploy-x86_64.AppImage --appdir AppDir -e build/thplayer -d assets/thplayer.desktop -i assets/thplayer.svg --icon-filename thplayer -p qt -o appimage$ mv TouHou_Player*.AppImage thplayer-linux.AppImage
『问题』如何书写 .yml 的脚本,通过 GitHub 的Action 资源,自动打包构建生成 Release 呢?
通过写三个系统的 .yml 脚本,路径必须是 .github/workflows
,随着时间的流逝⌛,想要持续构建对应的云系统和 Kit 也必须更新,文档和版本参见 images ,直接往 .yml 修改;这是一个实际可跑的脚本 *.yml 都是可以编译成功的,失败可能是额度时间不够了,如某次成功的示例,可看到头像是 GitHub 的头像发布的 Release-v6.1 ;
『问题』截图的一级二级的菜单工具栏,如何实现 Windows7 的透明磨砂 / Windows 的亚克力的效果,且能够支持 Windows / Linux / MacOS?
单纯实现亚克力效果不难,难在Linux和 MacOS 上也能实现这个效果?这是当时的一些探索和经验,总结了四种方法放置于 GitHub - AcrylicWindow
『问题』如何实现一个完美的无边框窗口跨平台,且还要占用低,没有瑕疵BUG,还能白嫖?
也折腾过,难度也很大,后来发现对于截图,费力可以实现,但是没必要,成本太大;结论:这样现成的没有,目前效果和跨平台都最佳的方案是framelesshelper, 有时放弃也是一种解决方案。
『问题』如何实现国际化多语言的切换?尤其是未使用 Qt Designer 来创建 .ui 文件,遇到无 ui->retranslateUi(this)
函数?不重启软件便可以切换语言
对于有有 .ui 的部分,可以通过 《Qt 项目(CMake)设置国际化支持》来解决。对于存手写的控件实现的,且大致实现的思路是:
下拉框中切换语言时,发射信号 → 全局单例 → 信号和槽函 → 到主窗口的槽函数,在里面进行重新加载语言,所有相关的控件的默认文本,都写在这个函数里面,便可以不重启软件,直接实现语言切换成功
『问题』是否需要管理员权限才能运行?
全程不会弹 UAC 弹窗,不需要管理员权限就可以使用所有功能,也不会中途提权,仅普通用户权限即可,包括导向安装,静默安装,使用卸载;
『问题』Windows 和 Linux 支持一次截多个屏幕,MacOS 仅只能截图单个屏幕,如何实现呢?
MacOS 除了系统自带的截图支持外,至今没有任何一个三方软件可以做到这点,包括大厂等某企鹅的截图的,无解。根原是属于此苹果接口没公开,至少没人能够发现。
『问题』如何确保 MacOS 上的效果和 Window上面保持外观的一致?
一个难点是再 MacOS 上也要和再 Windows 的效果保持一致,于是对不同风格进行对比,但 Fusion 又会倒是 Setting 窗口非原生的样式,但好在十分接近;选取一个平衡点。另外还手绘画了一个二十多个自定义或者复杂控件。
『问题』如何解决使用 ESC 取消截图后的内存泄露问题?QPointer 、智能指针、还是单例?
也花了大力气来探究,在完成一次截图之后,内存的占用会在合适时机自动释放出来;这是定位在消耗内存的变量;开发环境显示器为 4K 27 寸 + 3K笔记本双屏;
『问题』对于使用单例模式不止一处时,有序需要多个单例用来传递或者保存数值时候,重复写很容易
可采用奇异递归模板的方式,然后添加一个宏展开为友元类;多个单例都只用写一份,而前提是需要对 《C++ 类的六个特殊成员函数》 很熟悉,才能理解,属于优雅的一种实现。
『问题』日志和崩溃生成 DUMP 记录?
日志可以通过配置文件修改,若是遇到传说的崩溃,亦会自动生成 .dmp 和 崩溃原因;
转储文件存放: C:/Users/用户名/AppData/Local/XMuli/Sunny/cache/Sunny_Dumps/dump_2024_02_29_11_31_30_714.dmp
实现方法可通过 WIN API 来实现
#ifdef _MSC_VERSetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)ApplicationCrashHandler);//注冊异常捕获函数#endif
额,你没接触过 DUMP,完全不会对其进行解剖分析?也简单写了一个使用 WinDbg 进行入门
『问题』希望单例运行EXE,确保全局唯一性?
可以通过共享内存QSharedMemory 和系统信号量 QSystemSemaphore,双重保证程序在一台终端上,仅会运行一个;
也能杜绝很罕见的一种情况,即使上次程序崩溃之后,但仍有残留的僵死进程,被误判当前没有启动。严谨(中指推一下眼镜)
QString uniqueKey = "SunnyUniqueKey"; // 使用唯一的标识符来创建共享内存和系统信号量QSharedMemory sharedMemory;sharedMemory.setKey(uniqueKey);// 尝试附加到现有的共享内存并分离if (sharedMemory.attach()) { sharedMemory.detach();}// 尝试创建共享内存,如果已经存在,表示已经有一个实例在运行, 判断是为了确保在同一台计算机上只能运行一个相同实例的程序。if (!sharedMemory.create(1)) { qDebug() << "There is already an instance of the application running (by QSharedMemory)!"; return 1;}// 创建系统信号量, 再尝试获取系统信号量,如果已经被其他实例持有,程序就退出, 判断是为了确保在多个进程同时启动时,只有一个进程能够继续执行。QSystemSemaphore用于创建系统信号量,如果系统信号量已经被其他实例持有(比如由于上一次程序异常退出导致信号量未被释放),则acquire函数会返回false,QSystemSemaphore systemSemaphore(uniqueKey, 1, QSystemSemaphore::Open);if (!systemSemaphore.acquire()) { qDebug() << "There is already an instance of the application running (by QSystemSemaphore)!"; return 1;}// ...程序其它逻辑// 释放系统信号量systemSemaphore.release();
『问题』截图项目运行直接置顶显示后,按下按键后,窗口无任何响应,需额外点击一下才能开始截图?
分析和解决方案《Qt新弹窗不响应键盘按键,难道也是无焦点?》
『问题』热键输入框控件,输入后显示的方块 ■◆ 乱码?
分析和解决方案《创建 QKeySequenceEdit() 后,显示方块■◆乱码》,还是多看下 Qt Assistant 解围粗心。
『问题』使用 VS2022 和 QtCreator 如何调试 Qt 5.15 的源代码?
『问题』截图会有很多个属性的校验和“且”的属性使用?
虽简单,但移除标志位容易忘却,这里简单列举一下《C++ 标志位使用:校验、添加、删除》
『问题』Qt Creator 报警告⚠ Misleading indentation; statement is not part of the previous ‘if‘
那日强迫症,且需要删除空格才能解决,属实比较稀少;记录下解决方案 《Misleading indentation; statement is not part of the previous ‘if‘》
『问题』重构此版时候,也会大量使用 Lambda 表达式
展开讲解一下,若未接触过,属会用会看即可《Lambda 表达式详解》
『问题』Visual Studio 断点偶遇进不去相关函数?
Release实际也是可以调试的,新手容易不知晓,知晓的亦可能会翻车,值得记录下;属于 Release / Release with Debug Info / Debug 的差异《Visual Studio 断点调试之箭头偏移进错函数,捉虫记》
『问题』源码不能外发的情况下,如何进行调试?
『问题』 Sunny 截图在在不同系统、编译器上开发、使用不同的 Kit Tools 上面如何解决乱码问题?且有时调试窗口乱码?还有 ANSI,UTF8,UTF8-BOM 采用哪种?
参考《 愿编程不再乱码(含Qt)-根因深究》,以及 QtExamples 的 “「第 6 章」 QT / IDE 乱码根因和解决”
『问题』 遇到需要使用的开源三方库,如何优雅的使用 Git 管理,确保拉取三方库即最新,又不会打乱本仓库的历史线?
开源三方库引入,想要优雅,基本 git submodule
或 git treemodule
命令之间二选一;推荐前者,理由为 《git submodule 基本用法》
『问题』 如何选用 LOG 日志库?自己简单封装一套,还是选用开源库?便于后面定位和分析
比较犹豫和纠结的一个问题,两种都试过;现总结为:项目初期使用自带的 QDebug 即,不够用再写一个类,和宏封装一套,满足需求即可,勿跑偏,功能才是重点。最后期可以选用三方库引入:
一点经验参考 《Log:日志选型调研『一』》、《Log:日志之 Spdlog 极简用法示范『二』》、《Log:日志之 Spdlog 核心构成『三』》
『问题』CMake 管理跨平台项目,生各平台的 IDE 的解决方案?以及 Window 上自带一些基础宏的数据类型含义?
现在已经幸福多了,ChatGPT 横空出世,已经可以解答了,故介绍一些高频或者重点宏,自己结合去搜🔍
CMake 的重要宏:《CMake 设置 Target 输出目录和后缀名》、《CMake 之 BUILD_SHARED_LIBS 和 CMAKE_BUILD_TYPE 用法教程》
Windows 的重要宏:《LPSTR/LPCSTR/LPTSTR/HWND/HANDLE/HMODULE/HINSTANCE 等含义和区别》
分享一下喜悦
在 v1.3 发布的那天晚上,记得 downloads 是 900+ 的样子;发布完了就去睡觉了。
第二天早上一看下载量就是 1k, 1.1k;中午再看一眼就是 1.2k;晚上再看就是 1.3k;
第三天是 1.4k,
第四天是 1.5k,
… …哈哈哈哈哈哈哈哈哈, 放假回家过年了,也没怎么看
约大大前天回来一看,嗯,还是 1.5k,大家玩的开心愉快
大前天到了 1.6k
前天到了 1.7k
昨天号到了 1.7k
今天的到了 1.8k , 纪念打卡(–写这篇时)
在写过的所有软件中,总的下载量虽然不是最大的,但短时间增速率长是最快的 ヾ(≧▽≦*)o。
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!
即使现 2024 年,但这份指南的详细程度也是的网上首份的,亦含 Linux 商店的上架流程,敬热爱,敬开源,敬自己;
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
写好的 EXE,添加了应用代码签名之后,是很想上架到应用商店
于上一年写了好几个跨平台的桌面应用程序,成功发布之后,添加了应用代码签名之后,接着就是想上架到应用商店
一图胜千言,完整的上架审核的流程的重难点和提供解决方案。
注册 Microsoft 开发者账号,个人约 $19,永久买断制度; 企业版本的 约 $99;
可以上架常规的 EXE、 MSI 以及 MSIX,这些等,只需要签名即可。
*.msi
通常是企业用来批量推送静默升级和安装用的; *.msix
是微软商店推荐的打包格式,类似于一个沙盒,对权限等管理的比较严格,属于 UWP 的效果。
必定会遇到的坑🕳有:
准备工作
中文语言注册官网存在 bug,需要 F12 修改下代码
购买代码签名证书,摸索不同类型的 EV / Standard 证书签名;费用:$ 几百-几千🔪/每年
有自己的官网域名,且备案;费用:¥数百
购买对象存储,以及子域名的 DNS 解析,设置 Https 直链下载;费用:¥数百
上传和微软团队审核
Sunny 截图也能够支持静默安装;
# innosetup制作的 exe 静默安装,其参数为 /silent /norestart# msi 静默安装参数 BasicMSI.msi /qn 需要管理权限运行 CMD Sunny_setup_msvc_1.3.0_x64.exe" /VERYSILENT # 不会出现安装向导(MS 上架用这个)Sunny_setup_msvc_1.3.0_x64.exe" /silent /norestart # 会出现安装向导
vc_redist.x64.exe 在自己软件安装之前,是必须先检测下,和安装之前默认安装此运行时依赖;
我的解决方案是 通过 Inno Setup 写一段 .iss 脚本
procedure InstallVCRuntime( );var szAppName, szParam, szExecutable, szArchitecture, szVCRuntimeInstalled, szFileExists: String; nRetCode: Integer; bFileExists, bVCRuntimeInstalled: Boolean; // 定义一个布尔变量 begin szArchitecture := '{#MyArchitecture}'; if szArchitecture = 'x64' then begin szAppName := 'vc_redist.x64.exe'; end else begin szAppName := 'vc_redist.x86.exe'; end; szExecutable := ExpandConstant('{app}\') + szAppName; bVCRuntimeInstalled := RegKeyExists(HKEY_LOCAL_MACHINE, 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{8bdfe669-9705-4184-9368-db9ce581e0e7}'); // x64或x86 均为此 guid 数值 bFileExists := FileExists(szExecutable); if bVCRuntimeInstalled then szVCRuntimeInstalled := 'True' else szVCRuntimeInstalled := 'False'; if bFileExists then szFileExists := 'True' else szFileExists := 'False';Log('-->szAppName:' + szAppName + ' szExecutable:' + szExecutable + ' szArchitecture:' + szArchitecture + ' szVCRuntimeInstalled:' + szVCRuntimeInstalled + ' szFileExists:' + szFileExists + ' The value of {app} is: ' + ExpandConstant('{app}')); if bVCRuntimeInstalled then // 系统若已经安装了 VC_redist 则跳过 begin Log('VC_redist is already installed. Skipping installation.'); end else if bFileExists then // 此文件存在才安装 begin Log('VC_redist is install'); szParam := '/install /quiet /norestart'; Exec(szExecutable, szParam, '', SW_HIDE, ewWaitUntilTerminated, nRetCode); Sleep(100); end else begin Log('Executable file not found: ' + szExecutable); endend;
可以参考我写的这篇《分享如何拥有一份私人的『开源代码签名证书』》,已详述购买 EV / Standard / Open Source 的 Code Signing Certificate 🎉🎉🎉过程,还有如何进行应用签名;
还要能够过 Microsoft Defender SmartScreen, 里面是一套一套的,初次提交被拒好几次都属正常。若是 EV 企业证书,氪大金,直接过。若是 Standard / Open Source 这类标准证书,需先将 EXE 提交 Submit a file for malware analysis, 重复多提交几次后,虽不会再报毒;但是审核依旧是会被拒绝, WHAT THE FUCK???
对应实际的运行弹窗是这个
唯一解决方案就是等待,待 exe 的下载量和安装量足够多,且数量越多,在微软的积累量信誉越多,过阈值即可被认可,顺利上架 Microsoft Store。
总结:
个人作品上架微软商店的流程很折磨,最后上架成功后也是拨开云雾;
若是文章对你有价值,亦可帮我积累Sunny的微软信誉,甚至感谢🙇 ; 截图下载 👉 Sunny_setup_msvc_1.3.0_x64.exe
微软商店 GIF 见文章顶部截图,此为 Web 预览的截图:
驱动认证,可找一些 WHQL 实验室给过,费用约¥ 1w /次。或者自己买 EV 证书自己过 WHQL,周期会很长 。
验证方式为 Windows Update 驱动中可看到,另外一种是在 catalog.update.microsoft.com 中查询,驱动是否上架了和微软推送了。 驱动上架就不展开讲述了。
上架方式分为两种
①若是需要上架到 UOS Stoe ,则需要注册对应开发者账号,官方文档 应用上架指南。额(⊙﹏⊙),但需要实名填写相关信息,通过之后就可以同时上架到 UOS 商业和 Deepin 社区两个版本。
可以在看到历史的每一个包和上架的版本,这一点很棒很有心,且每一种语言的展示也是用 Tab 页显示出来了,体验很出彩。
②若【仅】想要上架 Deepin 社区,突然发现前不久,有新出来了一种简化了的投递流程(2024.02.19),方便多了 社区投递系统使用说明, 基本直接上传应用包即可。
已在 Deepin V20.9-V23 上移植完成
上架 深度商店效果
还发现了当初上架的一个开源软件 lfxSpeed,一个简单的 Deepin/UOS 的任务栏网速插件,真时间匆匆~
上架星火商店步骤
上架成功
Note: 网页上传 .deb 总是错误,解决方案,必须在 Linux 系统下进行上传 .deb 才会成功;
已移植到 openkylin 1.0.1 等版本,运行效果。
上架规则
需填写《开源软件上架申请表格_V1.0.0.docx》,且对 .deb 的 debian 文件夹的文件有点格式要求;参考如下。符合标准后提交表格和 .deb 即可。
版本镜像下载:openkylin 1.x X86
详细规则参考:
1、 包名_版本_架构.deb,各个字段需要与control文件里的参数保持一致。
2、 包名不能含有中文,只能包含数字字母和三个符号(.+-),英文字母必须是小写,版本号必须以数字开头,架构只能为arm64和amd64和misp64el
例如:powerword_1.0_arm64.deb (均为下划线)
3、 软件中文名称、英文名称,各长度小于50个字符。
4、 icon(规范:128像素*128像素);
5、 软件截图(宽/高 1.5 按此长宽比的应用实际截图分辨率即可(至少5张)建议尺寸:1000像素*680像素)
6、 软件分类:精品、办公、开发、图像、影音、网络、游戏、教育、社交、其他、系统。最多选3个,第一个为主分类,后两个为附属分类。
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
Processor 12th Gen Intel(R) Core(TM) i7-12850HX 2.10 GHz
Installed RAM32.0 GB (31.7 GB usable)
System type 64-bit operating system, x64-based processor
Edition Windows 10 Pro
Version 22H2
OS build 19045.3930
Experience Windows Feature Experience Pack 1000.19053.1000.0
截至今日 2024.02.02 ,如下步骤开启是亲测可行的;
“设置” > “系统” > “关于”,确保操作系统内部版本号为 Build 19045.3754 (KB5032278) 或更高
若有需要亦可手动下载 KB5032278 这个包,实测版本大于即可,没这个 KB 也可以成功。
Github下载 ViveTool 并将其解压,用于开启 Windows 设备上启用隐藏的实验性功能。
进入下载目录,管理员执行如下:
vivetool /enable /id:46686174,47530616,44755019
执行成功提示如下
重启系统后。观察 Windows Copilot 出现于右下角通知处?若未出现,也可右键任务栏,看是否有Windows Copilot相关的字样,启用即可。
若是步骤 3 失败,则打开注册表,进入 Computer\HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Shell\Copilot\BingChat
后,将 IsUserEligible 数值修改为 1,再重复步骤3,即可看到 Copilot 图标
开启成功
“Settings - Data & time - Region”,将 “Country or region” 设置为 US;最后本机 “ Language” 设置如下.语言应该不是必须的。
必须下载最新版本的 Edge Beta 版本,Become a Microsoft Edge Insider | Microsoft Edge,安装后可卸载旧的 Edge。此刻再点击,右侧任务栏 Copilot,即可享受。若是想要使用 “文生图” 的功能,需要采用 在 Edge Beta 中登录自己账号,就可以立即在 Edge 中使用 Coplot 功能。
最后,也可在系统自带的中使用 Copilot 中,体验使用 AI 带来的生成式的效率和创意。
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
Title | URL |
---|---|
Site | thinkymate.com |
Guide | wiki |
用于 ChatGPT 和 AI 的简单易用的桌面应用程序! 🎉🎉🎉🎊🎊🎊🎊
ChatGPT和AI:
语音:
支持文本到语音(TTS)和文本到语音(TTS)功能
支持硬件耳机联动扩展
支持自动智能措辞和转换,更流畅自然
支持修改语速和替换语音库
💻 MacOS 13
+ 📎 Qt 5.15
📎 gcc/g++ 9.2
📎 gdb8.3
💻 Ubuntu 22.04
📎Deepin 20.9-23+
📎 Qt 5.15.2
📎 gcc/g++ 9.0
📎 gdb8.0
💻 win10 22H2
📎 Qt 5.15.2
📎 Visual Studio 2022
📎 C++17
如果对您有帮助,或者觉得有用,您可以点击该仓库的⭐ Star 🍴 Fork 两个图标,方便抬手之间,说点赞的手, 手留余香;其次可以我喝一杯冰的快乐水。
如何反馈?非常欢迎您加入我们!您可以 提一个反馈 ;提出任何错误、建议、功能设想,或帮助改进本软件。
xmulitech@gmail.com | https://thinkymate.com
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
Open Source Code Signing Certificate 🎉🎉🎉
当自己写的软件开发完毕后,若想进行商店软件发布,则下一步需要搞定『代码签名证书』。给你的 EXE 标记一个戳,写上一个被 Microsoft 所认证的签名。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
代码签名证书(Code Signing Certificates)是什么?有什么用?
证书分为三类
Code Signing Certificates 的价格 / 年;
Signing in the Cloud | Signing - code | Signing - set | |
---|---|---|---|
EV | € 379 | € 329 | € 359 |
Standard | € 189 | € 129 | € 159 |
Open Source | € 49 | € 25 | € 69 |
注:购买实物的时候,还会有约 € 35 的运费 + 23% 的税费,默认是 DHL 快递。
现代 (2024 年) 的签名证书购买 ,分为两类;
**云上: ** 手机上使用 OTP + PC 联网条件
**本地: ** 实体的 cryptographic card + card reader;
一种是大的密码卡片 + 大的读卡器
另一种是类似于手机卡卡片 + USB 读卡器
在线付款有较大概率性会被拒;手机提示信用卡支付成功,但是网站提示交易失败。通常等待三天左右,会自动原始账单返回,但是由于汇率和莫名的手续费,每次失败会损失约 ¥20 。可尝试切换支付方式或信用卡和切换节点。
因我购买的是开源代码签名证书,其支持以下数字签名:
步骤如下:
付款成功
激活账号
验证本人以及开源开发者的身份
等待验证通过邮件,和按照邮件提示操作,里面的二维码都是是一次性的,一口气按照操作提示好
下载 SimplySign Desktop,,使用签名工具时必须联网验证身份 token;
使用 signtool + CMD进行签名
打开 SimplySign Desktop 输入验证后,通过调用 Windows 自带的 signtool.exe 工具,来实现对 EXE / DLL 的签名;参数如下
> signtool.exe Valid commands: sign -- Sign files using an embedded signature. timestamp -- Timestamp previously-signed files. verify -- Verify embedded or catalog signatures. catdb -- Modify a catalog database. remove -- Remove embedded signature(s) or reduce the size of an embedded signed file.
输入如下命令签名,盖上自己的章
查看 EXE 属性,确认签名成功;最后对 EXE 签名成功,右键属性可查看到。
当签上名后,就可上架 Microsoft Store ;也可以上架三方 XX 软件管家/ 商城,但通常需要提供软著和额外的其它文件。
这部分下次有空时,另单独写一篇,详细介绍上架流程全程;
下载地址:GitHub ,这是一个ChatGPT 免费使用的跨平台客户端;更多介绍参见官网 thinkymate.com 。
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
“原厂电池” 太昂贵了,不如自己拆替换,多的钱买🥩🍖红烧吃吃。另外和两个方案就是“移植电芯”和“三方超容电池”。手机截止今天(2023.12.06),恰好约两年,这两个方法的电池都替换过。长期使用了一年,感觉可以来说下体验.
开始换了第一块移植电芯的电池(2023.01),此时还市面上还没有超容电池量产。剪断旧电池的开排线版寄给店家,帮电焊后再寄回,自己换。电池费用 120 = 电芯 70 + 手工费50,完美。刚替换以后确实超级能用,体验爽歪歪。开始有慢慢上涨回95%,今年11月后来又掉到84%,就又准备一块了。
优点: ❶能显示电池容量❷原装苹果排线版,更安全❸理论上充电电流对电芯的最完美❹超便宜价格
缺点: ❶排线版已有500多次电池循环,无法清零❷苹果的保护机制,充电次数超过500/1000次,会有会有10%容量充不了,原生保修的电芯也一样,属无解。❸有替换技巧,电池容量才能慢慢上涨❹动手能力
这次换了一块超容电芯3480毫安,有蹲到前不久的量产出来了(2023.11)。电池费用220,已经替换上使用20天,还顺便清了灰;拍照给店家得老师傅也说我整的挺干净得🎉;目前使用嘎嘎爽,下班回家还有60-70%⚡。后续使用几个月后,有空再来更新结果
优点: ❶大容量100+%的电池,排线版0循环❷价格便宜
缺点: ❶有弹窗,约15天后永久消失❷无法显示电池容量❸自己动手
两次自己换电池都很满意的,均一把换成功,DIY 的拆开机快乐。
电池这就一个消耗品,随心所欲的用,购机至今有十分之二的时间会用到自动关机,再充电,真比较伤电池,但这种时刻不考虑保养,用起来也是爽快🍋🍃。一年换一块,也嘎嘎香。
先写这么多,一些注意点晚点更新
原厂电池,504次循环容量就到 86%,伤电池的用法已经示范过;拆旧电池排线版,使用绝缘的✂,且电池用光再拆机,若如我这样大力掰弯,心急了。,有大概率见到爆炸🧨,注意安全;
QtExamples 欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
源码:*.h *.cpp
符号调试包:*.pdb (属 Windows 专有)
动态链接库: *.dll
静态链接库: *.lib (通常是前者,也可以是 .dll 的符号信息,编译时刻使用)
your.exe 可以进行单步调试 == your源码 + your.pdb + your.dll + microsoft.pdb + microsoft.dll + 这些文件路径全部设置正确
以下两个 IDE 均使用 ”MSVC 2022 + Qt 5.15.2” 来编译项目作为示范,且生成的 x86 的 Debug 模式的 your.exe。
Visual Studio 2022 依次打开 菜单栏的【Tools】【Options】,进入【Debugging】【Symbols】添加 Qt 的符号调试包路径
右键打开解决方案的属性【Solution】【Properties】,【Debug Source Files】添加源码路径
编译工程,模式为 Debug、Win32,设置断点【F9】后,开启运行调试【F5】,卡住断点,再怕【F11】 进入任何一个 Qt 源码的函数调试, QWidget::show() 函数为例
若是每次 【F9】调试加载很慢,是因为每次从微软服务器拉取这些文件到本地比较耗时。也在步骤1中,和我一样取消【Microsoft Sysbol Servers】前面的勾,调试很快且顺滑,且不影响正常的软件调试功能。
Qt Creator 10.0.1 依次打开 菜单栏的【Edit】【Preferences】,进入【Debugger】【Source Path Mapping】【Add Qt sources…】添加 Qt 的源码路径,此处是调试需要
【Debugger】【CDB Paths】的 “Symbol Paths” ,”Source Paths” 添加 MSVC 的符号调试包的路径和源码路径。
【Enviroment】【Locator】【Custom】添加 Qt 的源码路径和自定义的文件路径,作用是便于快捷键直接定位具体文件。注意此处路径分隔符号是 /
快捷键【Ctrl + K】快速定位左下角,然后搜索 Qt 源码的文件名以及自定义项目的文件名称,迅速切换,体验极佳
编译工程,模式为 Debug、32bit,设置断点【F9】后,开启运行调试【F5】,卡住断点,再怕【F11】 进入任何一个 Qt 源码的函数调试, QWidget::show() 函数为例
若用 VS2022 / QtCreator 10.0.1 单步调试 Qt 源码失败,排查原因
若是 IDE 调试时发现没有 Qt 的符号调试包,教程可参考姊妹篇一;以及学会如何查看 对应模块是否加载
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
MACOS
WINDOWS
LINUX
按键 | 描述 | 模式 |
---|---|---|
←, ↓, ↑, → ( A, S, W, D ) | 移动选中框位置 1 像素 | 局部 |
Ctrl + ←, ↓, ↑, → | 扩展选中框尺寸 1 像素 | 局部 |
Alt + ←, ↓, ↑, → | 收缩选中框尺寸 1 像素 | 局部 |
Shift + ←, ↓, ↑, → | 移动选中框位置 10 像素 | 局部 |
Shift + Ctrl + ←, ↓, ↑, → | 扩展选中框尺寸 10 像素 | 局部 |
Shift + Alt + ←, ↓, ↑, → | 收缩选中框尺寸 01 像素 | 局部 |
Shift + F4 | 快速保存截图 | 局部 |
Esc | 退出 | 局部 |
F6 | 窗口智能截图 | 全局 |
F7 | 延时截图 | 全局 |
F8 | 全屏截图 | 全局 |
技术架构属初看觉着很简单,耗时几天就能写一个 Demo 级的截图,如很早写的 ShotX。
但后来心心念念,准备正式写一个具有高级/商业的软件时候,就属于有点规模。其属细节点超级多;
思路:
写的时候,保证开发过程的多平台编译,push 代码时触发构建 CI/CD, 及生成对应平台的包便于安装。
截图的工作量、尤其是细节的实现是挺多的。需要花时间来打磨产品和优化细节,让它成为美观又好用的一款软件,再造一个极佳的轮子。
绝知此事要躬行;列举开发过程中遇到的具体困境,以供后来者参考。
git submodule
还 git treemodule
命令呢?ui->retranslateUi(this)
函数?『解决』 愿编程不再乱码(含Qt)-根因深究
『解决』 Git Submodule 基本用法
『解决』从 LOG 分析调研,采用 Spdlog / QDebug,以及对此两套的宏封装
『解决』有很多,后抽空整理更多
『解决』 LPSTR/LPCSTR/LPTSTR/HWND/HANDLE/HMODULE/HINSTANCE 等含义和区别
『解决』 参考
『解决』还是 Qt Assistant 解围粗心, 创建 QKeySequenceEdit() 后,显示方块■◆乱码
『解决』构建的自动打包
『解决』未完待续。。。
Qt >= 5.15.2
CMake >= 3.16
MSVC >= 2019 | MinGW >= 8.1.0 | GCC >= 9.4 | Clang >= 12.0
备注: 这是已经成功编译的一些版本,在更低的版本未经过测试。
工具链: Windows 10 & Qt 5.15.2 & CMake 3.24.1 & MSVC 2019 ( or MinGW 8.1.0)
编译步骤:
# ******************** MSVC 2019 ********************#『Step1』# x86:# 添加 "C:\Qt\5.15.2\msvc2019\bin" 到 path 后,终端执行 echo %PATH% 使其立即生效"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat" x86cmake -G "Visual Studio 16 2019" -A Win32 ..devenv Flipped.sln /Build "Release|Win32"# x64:# 添加 "C:\Qt\5.15.2\msvc2019_64\bin" 到 path 后,终端执行 echo %PATH% 使其立即生效"C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvarsall.bat" x64cmake -G "Visual Studio 16 2019" -A x64 ..devenv Flipped.sln /Build "Release|x64"#『Step2』Visual Studio 2019 open `Flipped.sln`#『Step3』windeployqt bin/Flipped.exe --no-translations# ******************** MinGW 8.1.0 ********************QtCreator opens the `CMakeLists.txt` file in the root directory of the source code
工具链:
编译步骤:
git clone --recursive https://github.com/XMuli/Flipped.gitcd Flippedmkdir build & cd buildcmake ..make -j16
构建各个平台后的包,附上另外一些实际运行图。Other 更多截图效果可 → 在此预览
: 我的主页 | : 好奇我的小窝 |
---|---|
: 直接和我聊天~ | :浏览量 100W+ |
若是帮助到了你,或者觉得有用,可以点击该项目的的 ⭐Star 和🍴 Fork 的两个图标,方便抬手之间,表示点个赞,手有余香,其次才是一份冰的肥宅快乐水。 → project → flipped
- 若是此项目帮助到了你,或者觉得有用,或是想帮助此项目的发展,你也能够邀请我喝一杯杯肥仔快乐水。 -
非常欢迎你的加入!对于此软件有任何缺陷、建议、功能想法、都可 提一个 Issue ;或者帮助此项目的完善,提交一个 Pull Request。请遵循 Contributor Covenant 行为规范。
→ **离线安装包下载 Releases **
→ 反馈 BUG/SUGGEST,用户社区等,和最新版本安装包获取,在QQ群:418103279
QtExamples 欢迎 star
⭐ 和 fork
🍴这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录
ExCMake 欢迎 star
⭐ 和 fork
🍴这个系列的 CMake
学习,附学习由浅入深的目录
OpenWrt
。[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
miwifi_r4a_firmware_72d65_2.28.62.bin [官方] 可刷机固件,可开启 telnet
openwrt-22.03.2-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin
openwrt-22.03.2-ramips-mt7621-xiaomi_mi-router-4a-gigabit-initramfs-kernel.bin
squashfs-sysupgrade.bin 是我们要刷的版本,initramfs-kernel.bin 是类似于重启后就会镜像还原的版本;
通过 openwrt.org 刷官方原生 最新的 openwrt-22.03 固件;OpenWRTInvasion 则利用漏洞进入小米路由器
💻 win10 22H2
📎 小米路由器4A千兆版(R4A)
📎 虚拟机的 Ubuntu 20.04
先刷为 2.28.62
版本;此为经过验证可以刷机的版本
链接路由器,打开 192.168.31.1
登陆后,拷贝出链接的 stok 数,此时标签页不关闭
主机 Win10 连接路由器 Wifi 后,虚拟机的 Ubuntu 20.04 网络设置为桥接,ifconfig
确保处于同一网段;
进入 OpenWRTInvasion 文件夹;作用为开启 telnet
pip3 install -r requirements.txtpython3 remote_command_execution_vulnerability.py
显示 “done! Now you can connect to the router using several options: (user: root, password: root)”
属成功,才可进行下一步
固件可通过提前上传到 /tmp
下,依次操作后刷机则成功
telnet 192.168.31.1 (user: root, password: root)cd /tmpmtd -e OS1 -r write openwrt-22.03.2-ramips-mt7621-xiaomi_mi-router-4a-gigabit-initramfs-kernel.bin OS1
刚刷好没有 WIFI 的,默认 IP 192.168.1.1, 账号 root,密码无;网线可进后台。
也可以先刷 initramfs-kernel.bin 后;
注:
打开 192.168.31.1
的标签页也使用虚拟机 ubuntu 的浏览器打开获取 stok 数;之前 Win10 浏览器打开,执行命令,总提示 Warning: the process has finished, but seems like ssh connection to the router is not working as expected.
;屡次 telnet 进不去;浪费光阴
OpenWRTInvasion,该脚本只能运行在 Linux、Mac 上
需要先刷为支持的版本,如 2.28.62
原生默认没开启 WiFi;还以为没成功,氪!
WIFI最好通过图形界面(LuCI)开启,相关设置在 Network(网络)> Wireless(无线)里头。
选择启用之后,记得进入 Edit(编辑)> Interface Configuration(接口配置)> Wireless Security(无线安全)来设置密码。
WIFI名在 Interface Configuration(接口配置)> General Setup(常规设置)> ESSID 修改。
sed -i 's/downloads.openwrt.org/mirrors.ustc.edu.cn\/openwrt/g' /etc/opkg/distfeeds.confopkg updateopkg instll libustream-openssl ca-bundle ca-certificates
参考 https://mirrors.ustc.edu.cn/help/openwrt.html
在 “system-Software” 搜索 luci-i18n-base-zh-cn
之前,先Update 下方可搜寻到
→ release 下载 luci-app-openclash_0.45.78-beta_all.ipk
后 ,执行 opkg install /tmp/**.ipk
安装;遇最后一行提示 * opkg_install_cmd: Cannot install package luci-app-openclash.
则需要按照 wiki 先安装所必须的依赖。
然,被提示拿捏的死死的;刷小米固件,会想起某帖说的 8M,也明白了;暴风哭泣。不说了,快跑,撤退~~
https://openwrt.org/inbox/toh/xiaomi/xiaomi_mi_router_4a_gigabit_edition 的 “Notes on firmware exploit procedure” 部分
std::shared_ptr
、std::unique_ptr
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
实现原理提前需要理解 特殊成员函数、std::exchange() C++14
、std::swap()
、 std::move()
、constexpr
、explicit
、noexcept
等,若是遗忘可参考此文
最后,Demo 实现或许不够十分完美和严谨,但对于其理解智能指针的原理和面试手写实现时候,足够。若有纰漏,请指正。
shared_ptr
的原理: 通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。
通过引用计数和模板来实现 shared_ptr;构造函数定义的时候,要初始化其指针、引用计数、和 mutex
“copy assignment constructor” 除了校验是否相等、是否为空的时候、拷贝时要先释放旧资源,旧的引用计数 -1,赋值后再指向对新的资源的引用计数 +1
释放资源时,要先校验是否存在,及计数为 0 才释放;
💻 win10 22H2
📎 Visual Studio 2019
📎 C++11
见 SharedPtr.h
/******************************************************************* * Copyright (c) 2022~-023 XMuli All rights reserved. * Description: C++ 实现一个核心的 shared_ptr 智能指针模板类 ******************************************************************/#pragma once#include <iostream>#include <mutex>using namespace std;template <typename T>class SharedPtr{public: SharedPtr() : _ptr(nullptr), _refCount(nullptr), _pMutex(nullptr) { cout << "default constructor" << endl; }; SharedPtr(T* obj) : _ptr(obj), _refCount(new int(1)), _pMutex(new mutex) { cout << "no default constructor" << endl; }; SharedPtr(const SharedPtr<T>& obj) // 其 _refCount 可以通过另外一个指针来修改,指向的是同一个地址 : _ptr(obj._ptr) , _refCount(obj._refCount) , _pMutex(obj._pMutex) { cout << "copy constructor" << endl; addRefCount(); }; SharedPtr<T>& operator=(const SharedPtr<T>& obj) { cout << "copy assignment constructor" << endl; if (&obj != this) { if (_ptr != obj._ptr) { release(); // 先释放旧的资源 _ptr = obj._ptr; _refCount = obj._refCount; _pMutex = obj._pMutex; addRefCount(); // 再技计数 +1 } } return *this; } //SharedPtr(SharedPtr<T>&& obj) noexcept; //SharedPtr<T>& operator=(SharedPtr<T>&& obj)noexcept; ~SharedPtr() { cout << "destructor" << endl; release(); } T& operator*() { return *_ptr; } T* operator->() { return _ptr; } int useCount() { return *_refCount; } T* get() { return _ptr; }private: void addRefCount() { cout << "addRefCount" << endl; _pMutex->lock(); ++*_refCount; _pMutex->unlock(); } void release() { cout << "release" << endl; bool bDelMutex = false; _pMutex->lock(); if (_ptr && --*_refCount == 0) { // 先校验是否存在,及计数为 0 才释放 delete _ptr; delete _refCount; _ptr = nullptr; _refCount = nullptr; bDelMutex = true; } _pMutex->unlock(); if (bDelMutex) delete _pMutex; }private: // 需在构造函数中初始化 T* _ptr; // 指向管理资源的指针 int* _refCount; // 引用计数 mutex* _pMutex; // 计数自增非原子操作,加锁解决多线程};int main(){ SharedPtr<int> sp1(new int(10)); SharedPtr<int> sp2(sp1); *sp2 = 20; //sp1 与 sp2 在管理这部分资源,引用计数为 2 cout << sp1.useCount() << " *ptr:" << *sp1 << endl;//2 20 cout << sp2.useCount() << " *ptr:" << *sp2 << endl;//2 20 SharedPtr<int> sp3(new int(30)); sp2 = sp3; //sp3 赋值给它,释放管理的旧资源,引用计数-1, cout << sp1.useCount() << " *ptr:" << *sp1 << endl; //1 20 cout << sp2.useCount() << " *ptr:" << *sp2 << endl; //2 30 cout << sp3.useCount() << " *ptr:" << *sp3 << endl; //2 30 sp1 = sp3; cout << sp1.useCount() << " *ptr:" << *sp1 << endl; //3 30 cout << sp2.useCount() << " *ptr:" << *sp2 << endl; //3 30 cout << sp3.useCount() << " *ptr:" << *sp3 << endl; //3 30 std::cout << "Hello World!\n"; return 0;}/*****************************打印结果*******************************no default constructorcopy constructoraddRefCount2 *ptr:202 *ptr:20no default constructorcopy assignment constructorreleaseaddRefCount1 *ptr:202 *ptr:302 *ptr:30copy assignment constructorreleaseaddRefCount3 *ptr:303 *ptr:303 *ptr:30Hello World!destructorreleasedestructorreleasedestructorrelease ******************************************************************/
Note:
mutex 实现了引用计数是线程安全的。但智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
书写测试时,若使用默认构造函数, 成员变量 _ptr、_refCount、_pMutex 在 release() 中容易崩溃;推荐带参的构造函数,完美运行测试
unique_ptr
的设计思路非常的粗暴:防拷贝,也就是不让拷贝和赋值。 💻 win10 22H2
📎 Visual Studio 2019
📎 C++11
见 UniquePtr.h
/******************************************************************* * Copyright (c) 2022~2023 XMuli All rights reserved. * Description: C++ 实现一个核心的 unique_ptr 智能指针模板类; ******************************************************************/#pragma once#include <iostream>using namespace std;template <typename T>class UniquePtr{public:constexpr UniquePtr() : _ptr(nullptr) { cout << "default constructor" << endl; };explicit UniquePtr(T* obj) : _ptr(obj) { cout << "no default constructor" << endl; };UniquePtr(UniquePtr<T>&& obj) noexcept : _ptr(obj._ptr) {cout << "move constructor" << endl;obj._ptr = nullptr;}UniquePtr<T>& operator=(UniquePtr<T>&& obj) noexcept{cout << "move assignment constructor" << endl;if (&obj != this) {if (obj._ptr) { _ptr = obj._ptr; obj._ptr = nullptr;}}return *this;}UniquePtr(const UniquePtr<T>& obj) = delete;// C++11 delete 禁止方式,C++98 用 private 来隐藏 UniquePtr<T>& operator=(const UniquePtr<T>& obj) = delete;~UniquePtr(){cout << "destructor" << endl;if (_ptr) {delete _ptr;_ptr = nullptr;}}T& operator*() const { return *_ptr; }T* operator->() const { return _ptr; }T* get() const { return _ptr; }T* release() // return std::exchange(_ptr, nullptr); // C++14{T* temp = _ptr; _ptr = nullptr;return temp;}void reset(T* ptr)// std::exchange(_ptr, ptr); // C++14{_ptr = ptr;}void swap(UniquePtr<T>& obj){std::swap(_ptr, obj._ptr);}private:T* _ptr;};int main(){UniquePtr<int> up1(new int(10));cout << "up1:" << up1.get() << " *ptr:" << *up1 << endl;UniquePtr<int> up2(std::move(up1)); // 控制权变更cout << "up1:" << up1.get() << endl; // nullptr, 此时 up1 已无控制权cout << "up2:" << up2.get() << " *ptr:" << *up2 << endl;UniquePtr<int> up3(new int(30));UniquePtr<int> up4(new int(40));cout << "up3:" << up3.get() << " *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << " *ptr:" << *up4 << endl;up3 = std::move(up2); // 控制权变更cout << "up3:" << up3.get() << " *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << " *ptr:" << *up4 << endl;up3.swap(up4);cout << "up3:" << up3.get() << " *ptr:" << *up3 << endl;cout << "up4:" << up4.get() << " *ptr:" << *up4 << endl;up3.release();cout << "up3:" << up3.get() << endl;std::cout << "Hello World!\n";return 0;}/*****************************打印结果*******************************no default constructorup1:0086DEB8 *ptr:10move constructorup1:00000000up2:0086DEB8 *ptr:10no default constructorno default constructorup3:008656D0 *ptr:30up4:00865700 *ptr:40move assignment constructorup3:0086DEB8 *ptr:10up4:00865700 *ptr:40up3:00865700 *ptr:40up4:0086DEB8 *ptr:10up3:00000000Hello World!destructordestructordestructordestructor ******************************************************************/
QtExamples 【Studio】
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
特殊成员函数是类(或结构)成员函数,在某些情况下,编译器会自动为你生成。
const T&
const T&
T &&
T &&
move 的函数相对于 copy 的函数没有 const
copy 是属完整的再复制拷贝一份;move 是对将亡的右值进行指针互换、节省空间提升效率
”复制 / 移动构造函数“ 属于新建对象,无需判断两者是否相等;”复制 / 移动赋值运算符“ 属于已有的对象进行赋值,赋值时需要先判断两者是否相等。
若有深拷贝时,”复制 / 移动赋值运算符“ 除了判断相等、数值是否有效;在拷贝字节前,还要先释放旧资源
只有当类存储了需要释放的系统资源的句柄,或拥有其指向的内存的指针时,你才需要定义自定义 ”析构函数“
三五法则 cppreference.com
💻 win10 22H2
📎 Visual Studio 2019
📎 C++17
见 SpecialMembers.h
/******************************************************************* * Copyright (c) 2022~2023 XMuli All rights reserved. * Description: C++ 类的六个特殊成员函数 ******************************************************************/#pragma once#include <iostream>#include <utility>using namespace std;class A{public: A() : m_ptr(nullptr) { std::cout << "default constructor" << endl; } A(const char* s) : m_ptr(nullptr) { std::cout << "no-default-val constructor" << endl; if (s) { auto n = std::strlen(s) + 1; m_ptr = new char[n]; std::memcpy(m_ptr, s, n); } } A(const A& other) : m_ptr(nullptr) { std::cout << "copy constructor" << endl; if (other.m_ptr) { auto n = std::strlen(other.m_ptr) + 1; m_ptr = new char[n]; std::memcpy(m_ptr, other.m_ptr, n); } } A& operator = (const A& other) { std::cout << "copy assignment constructor" << endl; if (this != &other) { delete[] m_ptr; // Free the existing resource. 重点 !!! if (other.m_ptr != nullptr) { auto n = std::strlen(other.m_ptr) + 1; m_ptr = new char[n]; std::memcpy(m_ptr, other.m_ptr, n); } } return *this; } A(A&& other) noexcept // 行参无 const : m_ptr(nullptr) { std::cout << "move constructor" << endl; if (other.m_ptr) m_ptr = std::move(other.m_ptr); other.m_ptr = nullptr; } A& operator=(A&& other) noexcept { // 行参无 const std::cout << "move assignment constructor" << endl; if (this != &other) { delete[] m_ptr; // Free the existing resource. if (other.m_ptr) m_ptr = std::move(other.m_ptr); other.m_ptr = nullptr; } return *this; } ~A() { std::cout << "destructor" << endl; if (m_ptr) delete[] m_ptr; }private: char* m_ptr;};A fn() { A t("A fun()"); return t;}int main(){ A a1("a1"); // default constructor A a2(a1); // copy constructor A a3 = a1; // copy constructor a1 = a3; // copy assignment constructor std::cout << "----------------------------\n\n"; //fn(); // function returning a A object A a5 = std::move(a1); // move constructor A a6; // default constructor a6 = std::move(a1); // move assignment constructor std::cout << "Hello World!\n"; return 0;}/*************************** 运行结果 ******************************** no-default-val constructorcopy constructorcopy constructorcopy assignment constructor----------------------------move constructordefault constructormove assignment constructorHello World!destructordestructordestructordestructordestructor ******************************************************************/
拷贝构造函数是 A a2(a1);
形式,还可以是 A a3 = a1;
这种形式。关键看是新创建对象还是已有的对象赋值。move 的函数赋值,借助 std::move()
将左值转换为右值。
QtExamples 的 【Studio】
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
OpenWrt
,初次接触记录下。以及开启 OpenClash + clash-rules 的进阶使用[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
miwifi_rb03_firmware_3e872_1.0.54.bin [官方 2022.08.24]
miwifi_rb03_firmware_stable_1.2.7.bin [官方 2022.03] 可刷机固件,默认开启 telnet
miwifi_rb03_firmware_stable_1.2.7.bin
,默认已开启 Telnet 和 SSHtelnet 192.168.31.1
(输入 root/60be9bd0)scp .\ax6s-1120\factory.bin root@192.168.31.1:/tmp
ssh root@192.168.31.1
;telnet 可以关掉,执行 mtd -r write /tmp/factory.bin firmware
刷机。路由器自动重启,默认IP 为 192.168.6.1
后,默认账号密码 root/password
ax6s-full.bin
或 ax6s-mini.bin
进行刷写固件注:
第二步骤通过 SN 码计算 root 密码:
python .\unlock_pwd.py SN码
此版本发现 ax6s-full.bin
实测重启后,WiFi 名称会被重置默认的 bug,而 ax6s-mini.bin
不会
简介:OpenClash ,其有开源内核 foss 和闭源内核 premuium 之分(CFW、OpenClash 都是后者内核),后者通常支持使用规则集 clash-rules。初次安装后通常直接运行会失败,LOG 如下,则需要自行安装内核
2022-12-12 13:26:54 【Dev】版本内核更新失败,请检查网络或稍后再试!2022-12-12 13:26:52 【Dev】版本内核正在下载,如下载失败请尝试手动下载并上传...2022-12-12 13:26:50 提示: 检测到内核文件不存在,准备开始下载...2022-12-12 13:26:49 第二步: 组件运行前检查...2022-12-12 13:26:49 第一步: 获取配置...2022-12-12 13:26:49 OpenClash 开始启动...
cd /etc/openclash/corewget https://github.com/vernesong/OpenClash/releases/download/Clash/clash-linux-armv8.tar.gz # OpenWrt首页查看内核平台tar -zxvf clash-linux-armv8.tar.gz # 解压为clash文件,tun内核需要下对应文件,后改名clash_tunchmod 777 clashchmod 777 clash_tun# 即可成功开启内核# 注意优化 dns、ipv6 等操作
以下列出 Dev 和 TUN 内核下载地址。
Dev 内核下载:https://github.com/vernesong/OpenClash/releases/tag/Clash
Tun 内核下载:https://github.com/vernesong/OpenClash/releases/tag/TUN-Premium
Tun 游戏内核:https://github.com/vernesong/OpenClash/releases/tag/TUN
使用进阶
proxy-providers
里面加入 filter
(只能过滤,不能做排除)下载 小米路由器修复工具 后运行
笔记本网口和路由器 Lan 口网线连接,确保处于同一个网段,选中官网固件
选择 “以太网 -> ip” 后,下一步,此时断电,按住重置按钮直至黄灯闪烁松开,等待几分钟
刷机成功
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
趁闲暇,花了五天给看完了;感比较久,然统计共 12 h 即可阅完;虽累亦甚至愉悦。
本书重点探讨成年人学习英文的方法和策略。对于自学英语,其指明了一些常见的误区,和对近百年来出现过的主流方法进行了归纳,依据 Rod Ellis 《二语习得概论》 理论和进百年得时间验证进行定论。当然,本书本书直接点题,没有一个通用通杀的方法,但又一个通用的自然逻辑顺序可以作为参考,目前在亲身验证中,也在寻找出一条适合自己的道路。
介绍学习外语中常见的误区。
总结: 不同人的语言学习阶段,最佳学习环境是不同的。对初学者来说,自然外语环境的效果最差,不必寻求。有计划、有步骤的、自己从基础单词听力开始学习。
初学者有效方式之一:通过图画、实物、肢体语言开始学习与其对应的单个英文单词发音
中级学习者:适合多媒体和计算机辅助语言,重点学习听力,后由浅入深的阅读
高级学习者:自然外语环境比较明显,英文电影、合适的英文材料更佳。
简单介绍一些科学原理,和记单词重在读音、语法不重要的观点。成人需要英语思维方可做到“听”、“说”反应迅速。
哑巴英语
外语思维:直接建立外语词汇本身和思维直接连接,还关联相关“外语词汇”以及其慨念与思维概念的直接连接
对于高频词汇和句子,尽可能使用外语思维。低频仅占交流中 10% 的的抽象词汇和特殊用法,不必强要外语思维;二八原则,不必耗费大量时间和精力
外语的阅读和写作,正确也是用外语思维;但思考或写作前期构思,可使用一定母语思维。
“语法-翻译法”教学是不可取的,有明显弊端,属于落后的老古董保守派。
单词:获得多为表现得形式,从而建立词汇意义在大脑得长久印象
讲语法
语法知识的多少和交流的流利程度和正确程度之间,没有丝毫关系。语法知识不能不能帮助形成英语思维,反而是在捣乱。
对语法的真正掌握,是有特定的自然次序的(拉森-弗里曼习得次序)。无论成年人是否学习语法知识,最后都是按照这个顺序掌握的
1. 进行时 ing2. in3. on4. 复数5. 不规则动词中的过去形式6. 所有格 ’s7. is, am, are8. 冠词9. 规则东西的过去式开始出现-ed10. 规则第三人称单数-s11. 不规则第三人称单数-es
语法价值的含义:
论记忆:成人的记忆力远比儿童好
对于学习英文来说,应该完全放弃英文字,只学习英语声音。将注意力集中到声音上,真正按照学习语言的因该的自然顺序去掌握外语(“先听说后读写”)。
要做到流利的识别英文字,最重要的前提是这些单词的”发音”都已经清晰的在大脑中形成,然后通过各种认知手段去识别文字。
学习外语的成年人,国际英标一定要学,总共才是四十几个。
详细介绍百年来学习英文主流方法历史,通过理论支撑和时间验证来阐述各自利弊和实用性。
不存在某一个统一、的具体的“学习方法”,根据学习者自身情况、不同阶段、科学的制定不同的学习策略。
本章也是前三章的提炼,也是最无用的一章,一切所需要的都需要自己去前三章寻找。
仍将学习过程分为四个阶段,具体阶段也提供一些操作方法。
STL
中 std::set
创建自定义结构体的对象,发现使用 “函数对象” 和 “定义普通函数 + decltype” 两种方式会有差异,且其中一种书写会导致崩溃?探究一番后,值得记录一下。[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
对于 std::set
创建对象分为两种:⒈基础数据类型; ⒉自定义结构体类型。前者教程很多,故仅讲述后者;
💻 win10 21H2
📎 Visual Studio 2019
📎 C++17
其中对于其中两种方式创建,使用示例如下,此处直接放结论:
std::set<MyData, cmpSortObj> s; // "函数对象"创建 set 对象 --> OKstd::set<MyData, decltype(cmpSort)*> s(&cmpSort); // "定义普通函数 + decltype"创建 set 对象 --> OK: 编译、运行成功;调试也成功std::set<MyData, decltype(cmpSort)*> s; // --> Error: 编译、运行成功;调试会失败
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
C++ / Qt
偶尔会遇到需要设置一些属性,在此记录一番,方便下次时候。// C++ 标志位flages & WS_POPUP // 检查一个标志位flages |= WS_POPUP // 设置一个标志位flages &= ~(WS_POPUP | WS_SYSMENU) // 除去多个标志位 // QT 标志位, 除了上面之外,还可用此函数setWindowFlags(Qt::FramelessWindowHint, true); // 开启此属性 setWindowFlags(Qt::FramelessWindowHint, false); // 关闭此属性
本文初发于 “偕臧的小站“,同步转载于此。
std::vector
中的元素进行去重,其中元素为自定义结构体类型。提供三种思路,并且附上详细示例和分析。关键词内容:std::unique
函数去重,却导致的 std::vector
发生改变(遇内存泄漏)std::vector
利用 std::set
去重(自定义结构体创建 set 对象的方法)sort
和 unique
中 比较 / 等于
函数书写(重载、函数、函数对象;严格弱序、相等)[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
在开发防病毒业务中,需要对传入到 vector
中的样本进行去重 ;本篇全程采用 C++ STL 库,未调用 Qt 接口。业务抽象出一个具体例子如下。
构建环境: 💻 win10 21H2
📎 Visual Studio 2019
📎 C++17
搜索发现,对 std::vector
共分为两种,一种基础数据类型,另一种自定义结构体类型。
基础数据类型
SO 提供三种解决方案,推荐使用第二种。见 What’s the most efficient way to erase duplicates and sort a vector?
自定义结构体类型
实际项目中,这种我们更常遇到,才是所需。余下全文重点讲解此。
对于自定义结构体 类型,使用 STL 的 set
、sort
、unique
时,都是需要自定义其严格弱序的比较函数 或者相等函数 的(此处用 _Pr
表示)。
其中 比较函数 _Pr
的实现有三种(附上的完整源码):
<
函数,简单,参见代码方式一。略class
或者 struct
罢了。PS: 因实际项目中的结构体,没有和无法重载 operator<
, 故 1 不可用。然后决定采用 2 方式。
而 2 式推荐使用常规声明和定义函数方式,而不使用 Lambda 方式定义;原因是后者在某些算法中会失效。
其中 3 式对于创建 std::set
对象很方便。
思路: 先利用 sort 进行排序,将重复的项使之相邻,然后利用 unique 的属性进行去重。注意点为 unique 去重特性。
// 核心代码sort(vec.begin(), vec.end(), cmpSort);auto ite = std::unique(vec.begin(), vec.end(), cmpUnique);vec.erase(ite, vec.end());
但其中大有文章,所以举一个完整例子:
#include <iostream>#include <string>#include <algorithm>#include <vector>#include <set>using namespace std;struct MyData { wstring name; wstring md5;};bool cmpSort(const MyData& d1, const MyData& d2) { return d1.md5 < d2.md5;};bool cmpUnique(const MyData& d1, const MyData& d2) { return d1.md5 == d2.md5;};int main(){ vector<MyData> vec = { { L"a1.exe", L"B9204B6362A65C248950D5A41F341DCC"}, { L"a2.exe", L"B9204B6362A65C248950D5A41F341DCC"}, { L"b1.exe", L"C96D1AA5D314954417C28A645ED72665"}, { L"b2.exe", L"C96D1AA5D314954417C28A645ED72665"} }; wcout.imbue(locale("", LC_CTYPE)); wcout << L"----------调整前--------" << endl; for (const auto& it : vec) wcout << L"[it.name]" << it.name << L"[it.md5]" << it.md5 << endl; sort(vec.begin(), vec.end(), cmpSort); auto ite = std::unique(vec.begin(), vec.end(), cmpUnique); vec.erase(ite, vec.end()); wcout << L"----------调整后--------" << endl; for (const auto& it : vec) wcout << L"[it.name]" << it.name << L"[it.md5]" << it.md5 << endl;}
运行查看结果,似乎是完美符合预期的??? 可真的如此?
实际它会产生内存泄漏,原因是由于使用的自定义 cmpUnique() 函数导致的,其并未是真正的两个结构体对象相等,且 stackoverflow.com 亦能够佐证 Ref [4]
尝试将 cmpUnique()
修改如下,便不再有内存泄漏了,但此功能函数,却也”失效”了,没有起到去重的效果,可以调试自行查验效果
bool cmpUnique(const MyData& d1, const MyData& d2) { //return d1.md5 == d2.md5; // 此『不严格相等』会内存泄漏 return d1.md5 == d2.md5 && d1.name == d2.name; // 此『严格相等』满足语法,却不满足实际业务需求};
更新
本想后面有时间再继续研究下方式一,看看自定义 cmpUnique()
修改达到严格弱序的要求,能够不会有内存泄漏,亦能够完美符合要求。但心里痒痒,总是还有一点困惑,两天后,半夜睡不着,又起来研究了下。
翻了下 std::unique()
的默认第三个参数的默认实现,如图,发现就是直接 ==
比较。那就是严格的对象相等,满足语法和编译,但不满足业务的需要,故这个点也没有疑问了。
实际情况复杂得多: 实际的 MyData
结构体是微软的 COM 组件中的 Bundle
结构,每一个**Bundle
对象** 的属性项目的数量和内容都可能不同,仅有少部分重叠。需求为仅只需比较的两三个属性内容是否完全一致,来判断两个对象是否为同重复文件。亦没有提供直接判断两个 Bundle
对象 相等的接口。简直是要要命, Help,Help,Help!!!
综上: 决定弃用此方式,改用下面『方式二』,且最终完成目的。
思路: 很简单,先将 vector 数据导入 set,利用其单一性去重,然后再数据赋值给 vector,完美的思路,亦不会有方式一的内存泄漏。其中难点为给创建 std::set 的对象进行自定义类型排序和赋值。其难点解决方式可参考 Ref[2]的方式三
其中 std::set 的自定义排序函数 cmpSort(),又分为定义在类外还是类内,若是前者则甚至简单,若是后者,则必须定义为静态成员函数。
// 核心代码set<MyData, decltype(cmpSort)*> s(&cmpSort); // 此种方式比较少见,且第二个 & 若想省略亦可,则编译器亦会自动添加for (unsigned i = 0; i < vec.size(); ++i)s.insert(vec[i]);vec.assign(s.begin(), s.end());
注: 若偷懒采用 set<MyData, decltype(cmpSort)*> s;
此方式定义对象,则会导致调用 s.insert(vec[i]);
时崩溃,坑。
这种大概是实际业务中最常遇到的场景,亦是这几种最复杂的一种。
// 核心代码set<MyData, decltype(DeDuplication::cmpSort)*> s(&DeDuplication::cmpSort); // 本行定义为重点for (unsigned i = 0; i < m_vec.size(); ++i) s.insert(m_vec[i]);m_vec.assign(s.begin(), s.end());
其中 Class DeDuplication 种 cmpSort()
必须为 static
类型,定义如下。
#include <iostream>#include <string>#include <algorithm>#include <vector>#include <set>using namespace std;struct MyData{ wstring name; wstring md5;};class DeDuplication{private: static bool cmpSort(const MyData& d1, const MyData& d2) { // ***** 注意,必须是静态 ***** // Simple example return d1.md5 < d2.md5; // Complex sample //if (d1.md5 != d2.md5) // return d1.md5 < d2.md5; //else // return d1.name < d2.name; };public: void finally() { wcout.imbue(locale("", LC_CTYPE)); wcout << L"----------调整前--------" << endl; for (const auto& it : m_vec) wcout << L"[it.name]" << it.name << L"[it.md5]" << it.md5 << endl; set<MyData, decltype(DeDuplication::cmpSort)*> s(&DeDuplication::cmpSort); for (unsigned i = 0; i < m_vec.size(); ++i) s.insert(m_vec[i]); m_vec.assign(s.begin(), s.end()); wcout << L"----------调整后--------" << endl; for (const auto& it : m_vec) wcout << L"[it.name]" << it.name << L"[it.md5]" << it.md5 << endl; };private: vector<MyData> m_vec = { { L"a1.exe", L"B9204B6362A65C248950D5A41F341DCC"}, { L"a2.exe", L"B9204B6362A65C248950D5A41F341DCC"}, { L"b1.exe", L"C96D1AA5D314954417C28A645ED72665"}, { L"b2.exe", L"C96D1AA5D314954417C28A645ED72665"} };};
若是普通类型,会导致 std::set
对象编译不过,报错如下,其解释参考 devblogs.microsoft.com Ref[3]。
Error (active)E0289no instance of constructor "std::set<_Kty, _Pr, _Alloc>::set [with _Kty=MyData, _Pr=bool (DeDuplication::**)(const MyData &d1, const MyData &d2), _Alloc=std::allocator<MyData>]" matches the argument listDeDuplication
其性能不如『方式二』,略,可参见 Ref[1] 种的第三种。
最后结合实际,采用方式二的 class
内定义 static
此方式最合宜解决此问题。且附上完整源码。
方式一和方式二的类外定义,见完整源码的项目 Unique
方式二的类内定义,见完整源码的项目 DeDuplication
对于给 std::set
创建自定义结构体对象,可见 STL 的 std::set 创建自定义结构体的对象,定义严格弱序的比较函数
QtExamples 【DeDuplication】
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
[1]: What’s the most efficient way to erase duplicates and sort a vector?
[2]: C++ set自定义排序
[4]: eliminating duplicates from vector of custom object - std:unique causes crash on free
]]>[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
screenshot.cpp:164:2: Misleading indentation; statement is not part of the previous 'if'screenshot.cpp:161:5: previous statement is here
PS:💻 win10 21H2
📎 Qt 5.15.2
📎 QtCreator 8.0.0
修改,将其修改为 Tab
按键或者 回车自动对齐
,即可消除此警告⚠;改动如下
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”
CMake
设置 Target
输出目录和后缀名set(EXECUTABLE_OUTPUT_PATH "xxx/path") # 可执行文件的输出目录set(LIBRARY_OUTPUT_PATH "xxx/path") # 库文件的输出目录set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "xxx/path") # debug 可执行文件的输出目录set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "xxx/path") # release 可执行文件的输出目录set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "xxx/path") # debug 库文件的输出目录set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "xxx/path") # release 库文件的输出目录set(CMAKE_DEBUG_POSTFIX "xxx/path") # debug 库文件的后缀名set(CMAKE_RELEASE_POSTFIX "xxx/path") # release 库文件的后缀名set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX "_d") # debug 可执行文件的后缀名set_target_properties(${TARGET_NAME} PROPERTIES RELEASE_POSTFIX "_r") # release 可执行文件的后缀名
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
一. set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin)
上面的语句能设置可执行文件的输出目录
在 Win + VS 环境下,会自动在你所设置的目录后面扩展一层 <CONFIG>
目录,所以最终生成的 Debug 版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Debug
目录下,Release 版本程序会在 ${PROJECT_SOURCE_DIR}/../bin/Release
目录下。
在 Linux + GCC 环境下,无论是 Debug 还是 Release,生成的可执行程序会直接放在你所设置的目录下,不会有差异。
二. set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../lib)
上面的语句能设置库文件的输出目录
在 Win + VS 环境下,会自动在你所设置的目录后面扩展一层 <CONFIG>
目录,所以最终生成的 Debug 版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Debug
目录下,Release 版本库会在 ${PROJECT_SOURCE_DIR}/../lib/Release
目录下。
在 Linux + GCC 环境下,无论是 Debug 还是 Release,生成的库文件会直接放在你所设置的目录下,不会有差异。
三. set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../bin)
上面两条语句分别设置了 Debug 版本和 Release 版本可执行文件的输出目录,
一旦设置上面的属性,在任何环境下生成的可执行文件都将直接放在你所设置的目录.
四. set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/../lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/../lib)
上面两条语句分别设置了 Debug 版本和 Release 版本库文件的输出目录,
一旦设置上面的属性,在任何环境下生成的库文件都将直接放在你所设置的目录。
五. set(CMAKE_DEBUG_POSTFIX "_d")
set(CMAKE_RELEASE_POSTFIX "_r")
上面两条语句分别设置了 Debug 版本和 Release 版本下库文件的后缀名。
六. set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX "_d")
set_target_properties(${TARGET_NAME} PROPERTIES RELEASE_POSTFIX "_r")
上面两条语句分别设置了 Debug 版本和 Release 版本下可执行文件的后缀名。
欢迎 star
⭐ 和 fork
🍴这个系列的 CMake
学习,附学习由浅入深的目录。
CMAKE_BUILD_TYPE
和 BUILD_SHARED_LIBS
的两个变量知识点。[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
💻 win10 21H2
📎 Qt 5.12.11
📎 Visual Studio 2019
是 add_library() 的一个全局标志,当 add_library() 没有指定 [STATIC | SHARED | MODULE]
是哪一个时,可以决定为生成 动态库 还是 静态库。
Typical values:
ON
: 让 add_library() 生成 .dll 动态库,对应 SHAREDOFF
: 让 add_library() 生成 .lib 静态库,对应 STATIC ;默认值CMake 代码如下,对应生成产物如图:
set(BUILD_SHARED_LIBS ON) # default is OFFadd_library(${PROJECT_NAME} ${SRCS_MAIN})
指定 生成产物 的构建类型。
Typical values:
Debug
:详细调试信息Release
:无调试信息RelWithDebInfo
:带有调试信息的 Release,依旧可能又略微优化MinSizeRel
:没使用过对于生成 .dll/.lib 的项目而言:
__declspec(dllexport)
或 __declspec(dllimport)
。这部分,以及结合如何使用 .dll 后面单独详细讲解一篇。
附: 本篇对应的修改源码在此 CMakeLists.txt
欢迎 star
⭐ 和 fork
🍴这个系列的 CMake
学习,附学习由浅入深的目录。
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
在研究实现跨平台的 AcrylicWindow 亚克力(类似透明磨砂) 效果,调用 Windows API 时,发现其函数的定义如下,发现这两个宏有点陌生,故有点出神,于是查了查 WINUSERAPI
/ WINAPI
的含义。
WINUSERAPI BOOL WINAPI SetWindowCompositionAttribute( _In_ HWND hWnd, _Inout_ WINDOWCOMPOSITIONATTRIBDATA* pAttrData);
表示函数的导入
WINUSERAPI// 定义一#if !defined(_USER32_)#define WINUSERAPI DECLSPEC_IMPORT#else#define WINUSERAPI extern "C"#endif// 定义二#define DECLSPEC_IMPORT __declspec(dllimport)
从两层含义定义可知,WINUSERAPI
粗略看作关键词 __declspec(dllimport);
__declspec(dllimport) 表示你告诉编译器要显式的从 dll 导入函数;尤其当类中有 static 成员时,没有它基本都会 Link 失败。可参见 此文
常用宏 WINAPI 或 CALLBACK 来表示 __stdcall
调用约定。表示参数入栈时,是从右往左依次顺序入栈的。 其它调用约定自行 Googel 关键词:函数调用约定
1 #define CALLBACK __stdcall2 #define WINAPI __stdcall
欢迎 star
⭐ 和 fork
🍴 这个系列的 C++ / QT / DTK
学习,附学习由浅入深的目录,这里你可以学到如何亲自编写这类软件的经验,这是一系列完整的教程,并且永久免费!”