之前在 用 unique_ptr 管理 Windows HANDLE 中介绍了如何基于 std::unique_ptr
快速实现对 Windows HANDLE 的 RAII 化。
但是如果我们也要对 fd
或者 Windows 上的 SOCKET
做类似的处理,重复实现 xx_handle
和 xx_handle_deleter
就显得过于琐碎。
实际上我们可以利用模板参数注入,将共同点抽象出来,不同点实现成各自的 type traits,然后利用模板进行注入。
这样一来,我们只需要简单实现每个 handle type 对应的基础属性,就可以直接复用核心实现。
1 | template<typename Traits> |
PS:不同于文章中的例子,基底类起名 handle_ptr
更为确切;因为在 unique_ptr
的内部会用来定义 pointer
;而几乎 unique_ptr 的语义行为都是围绕 pointer
来完成的。
有了上面这个基底之后,针对 fd-wrapper,我们只需要
1 | struct fd_traits { |
这样一来,实现一个新的 unique handle wrapper,我们只需要确定四个东西:
is_valid()
close()
剩下的就是常规的类型实例化。
C++ 11 引入了没有掀起什么波澜的 std::error_code 和几乎没收到什么关注的 std::system_error。
偶然发现后者可以用来做针对 system calls 的异常汇报处理。
Win32 API 调用失败后具体的错误码可以通过 ::GetLastError()
获取;Linux 上 syscall 的错误码则用更 C-ish 的 errno
获取。
如果希望通过异常来汇报这些系统调用失败,那么通常会从 std::runtime_error
继承出一个 exception class,额外包含系统调用的错误码。
例如:
1 | class win_last_error : public std::runtime_error { |
上面的代码稍加处理后可以同时支持 GetLastError()
和 errno
std::system_error
恰好也是一个和 system facilities 有关的异常类,汇报系统相关的错误。
除了传统的 what()
之外它还提供一个 code()
函数返回对应的 error_code
而 std::error_code
可以通过指定不同的 category 来指示其包含的错误码语义。
所以实际上我们可以这么用:
1 | inline pipe make_pipe() { |
注意这里使用的 system_category()
,它代表 error code 是 reported by the system.
有些地方可能会说这里要使用 std::generic_category
;但是通过试验发现使用 std::generic_category
的 Windows last-error 和 ec.message()
是对不上的;而 std::system_category()
则是正确的。
猜测 generic category 可能针对的是 CRT 的错误码
Q: error_code 是啥?
A: 这玩意儿实际上是 ASIO 的作者针对 async-handler 的错误处理设计的;boost 中 ASIO 和 filesystem 都大量支持了它。
但是这玩意儿的设计门槛有点高,以至于不看完几篇文章压根没法理解这东西的设计意图;导致除了 ASIO 和 filesystem 及他们相关的衍生 lib 之外都没啥人去用它。
而 filesystem C++ 17 才进入标准;而 ASIO/network TS 几天前也差不多宣告死亡了…
Q: 为啥要用异常?
A: Why not?我不排斥使用异常,而且我觉得我写的东西还没关键到需要考虑目前异常实现带来的性能问题。
并且我觉得 exception 在充满大量业务逻辑的应用层是非常优秀的错误处理机制。
即使提供了支持 chaining operations 的 sum type(例如这个 expected),在业务层基本也需要人肉实现 failure cascading 层层上抛。
因为公司的安全政策要求,公司发的笔记本会在5分钟没有操作后自动锁住当前账户,并且这是由预装的安全组件强制保证的。
(不要想着可以把安全组件卸载或者禁用了,会被 IT 查水表的)
有时候加着仓或者用手机看个新闻就超时锁屏了,总有种摸鱼被发现的感觉,不太好。
更蛋疼的是,WFH 的时候只能用公司发的笔记本;而每次锁屏后都会导致 VPN 掉线需要重连,并且有概率会无法重连需要重置网卡….
没办法只能自己写了个小工具,在3分钟没有操作后进入模拟工作状态,定时发送键盘按键消息;同时监听鼠标和键盘消息,如果出现人为操作,就退出模拟状态。
经过一段时间的体验,基本实现了初衷,摸鱼再久也不会被发现了 😁
项目代码完全开源,可以在这里查看
核心就三点:
GetLastInputInfo()
获取程序上次获得输入的时间;用来判断 idle durationSendInput()
发送键盘事件;为了避免干扰,选择了 ScrollLock 这个按键因为用到了系统的 API 所以目前只支持 Windows,估计也不好移植到其他平台。
一开始 GUI 是用 nana 做的,后来发现如果要支持 tray icon 的消息处理还要自己对窗口做一遍 sub classing…
索性弃用 nana 用 native win32 做了个 dialog,起码各种 message handling 会灵活一点。
因为是自家用,所以也不提供功能选项定制啥的,毕竟用不到啊。
最后再吐槽一下:我是真不喜欢做 UI
手里有一批某课程内容的教案但是都是导出的 HTML 文档,因为每次打开都会请求一堆的外部资源(比如JS),导致卡顿几秒钟。
但是这是盗版资源 (shame on me) 所以这些外部请求完全是捣乱;于是想到批量转换为 PDF 就好了。
一开始选择的是 wkhtmltopdf,但是 libpng 会因为源文件的 sRGB 不正确导致失败。看到有个 2017 年报的 issue 到现在还是 Open 状态,遂放弃。
后来想到浏览器可以正常打开,也可以正常 print to pdf,那何不让浏览器批量处理?
恰好 Chrome 支持 headless mode,结合简单的 powershell 就可以做到批量转换:
1 | $dest_dir = "/your/dest/dir/" |
每次调用都会创建一个 headless chrome 做转换,而一次转换大概需要十几秒;同时这个调用是启动 chrome 之后就立即返回的,并非等到转换结束。
所以为了避免短时间内创建大量的 headless chrome 进程把系统资源耗死,这里简单的用 sleep 做一个节流器
比较新的版本的 postfix 除了 milter 之外还可以用 policy delegation 做 content filtering;后者的优势在于简单容易实现,但是只能读取有限的 email attributes。
policy delegation 实现上也有三种:
第三种实现上最简单,并且虽然文档上说它也是基于 unix domain socket 实现通信,但是这部分通信实际上对 policy server 是透明的。
第三种情况下,只需要提供一个简单的 binary,能够读写 stdin/stdout 即可完成通信:
文档上虽然没有明确写出这点,但是通过附带的 example 用例可以证实。
不过使用这种方式要注意一个坑:stdin/stdout 的读写必须不能使用 fread()/fwrite()
std::cin/std::cout
这种带有应用层缓存的 IO 设施。
虽然这里是对 stdin/stdout 读写,但是实际上底下通讯还是会经过 unix domain socket,使用带应用层缓存的库函数读写时会导致阻塞。
相应的,这里要么使用 read(2)/write(2)
这种 syscall,要么手动屏蔽标准库的应用层缓存。
对于没有经验的人来说,这里是一个很容易掉的坑