一周杂记 in Week 4 Feb 2022

Life

  • 刷完了 The Expanse S05;这一季主要仍是在做剧情铺垫和完善角色形象,但是这已经是倒数第二季了,这个时候才开始要丰满主要反派 Marco Inaros 的形象未免也太晚了吧。何况还要顺带加上 Philip…这就导致这一季又重复了一遍之前的旧模式,缺乏点新意。
    另外这一季 Alex 的演员因为性骚扰丑闻被剧本杀了,Clarissa 加入队伍,不得不感慨一下。
    同时 Mrs.Avasarala 重新当选为联合国秘书长;以及 White Collar 里的 Peter 叔 Tim DeKay 打了个酱油,出演反叛的 MCRN 军官,结果最后一集在穿越 Ring 的时候直接被湮灭了…

Read More

一周杂记 in Week 3 Feb 2022

节后第二周,生活和工作节奏都慢慢恢复到了之前的状态。

Life

  1. 一口气刷了好几集 The Expanse S05,感觉 S05 前半部分又在攒局积攒冲突。但是一个不好的地方是核心冲突又回到了人上,protomolecule 还是“飘在外面”;期待后续有更高的格局

Read More

一周杂记 in Week 2 Feb 2022

Blog 从上次更新到现在至少荒几个月。一直没更新主要是太懒了并且没有想到有什么需要特别写的。

春节休假期间偶尔反思了一下觉得还是需要日常回顾自省一下,就算没有什么新的体会或者收获,当 journal 留着万一哪天得了阿兹海默症没准还能起到正向作用 🤪。

Life

因为节前几天杭州出现了不大不小的疫情,出现扩散后老婆医院不允许医护回家所以又一起在杭州过了一次年。所以节后就没有请年,开始恢复工作节奏。

Read More

unique_ptr For Generic Scoped Handle

之前在 用 unique_ptr 管理 Windows HANDLE 中介绍了如何基于 std::unique_ptr 快速实现对 Windows HANDLE 的 RAII 化。

但是如果我们也要对 fd 或者 Windows 上的 SOCKET 做类似的处理,重复实现 xx_handlexx_handle_deleter 就显得过于琐碎。

实际上我们可以利用模板参数注入,将共同点抽象出来,不同点实现成各自的 type traits,然后利用模板进行注入。

这样一来,我们只需要简单实现每个 handle type 对应的基础属性,就可以直接复用核心实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
template<typename Traits>
class handle_ptr {
public:
using handle_type = typename Traits::handle_type;

handle_ptr() noexcept = default;

// implicit
handle_ptr(std::nullptr_t) noexcept {}

// implicit
handle_ptr(handle_type handle) noexcept
: handle_(handle) {}

~handle_ptr() = default;

explicit operator bool() const noexcept {
return Traits::is_valid(handle_);
}

// implicit
operator handle_type() const noexcept {
return handle_;
}

friend bool operator==(handle_ptr lhs, handle_ptr rhs) noexcept {
return lhs.handle_ == rhs.handle_;
}

friend bool operator!=(handle_ptr lhs, handle_ptr rhs) noexcept {
return !(lhs == rhs);
}

private:
handle_type handle_{Traits::null_handle};
};

template<typename Traits>
struct handle_ptr_deleter {
using pointer = handle_ptr<Traits>;

void operator()(pointer ptr) {
Traits::close(ptr);
}
};

PS:不同于文章中的例子,基底类起名 handle_ptr 更为确切;因为在 unique_ptr 的内部会用来定义 pointer;而几乎 unique_ptr 的语义行为都是围绕 pointer 来完成的。

有了上面这个基底之后,针对 fd-wrapper,我们只需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct fd_traits {
using handle_type = int;

static bool is_valid(handle_type handle) noexcept {
return handle != null_handle;
}

static void close(handle_type handle) noexcept {
::close(handle);
}

static constexpr handle_type null_handle{-1};
};

using fd_deleter = handle_ptr_deleter<fd_traits>;
using unique_fd = std::unique_ptr<fd_traits::handle_type, fd_deleter>;

inline unique_fd wrap_unique_fd(int raw_fd) {
return unique_fd(raw_fd);
}

这样一来,实现一个新的 unique handle wrapper,我们只需要确定四个东西:

  1. handle type 的实际类型
  2. null handle 的值,用来确定 default/zero initialized 之后 handle 状态
  3. 实现 is_valid()
  4. 实现 close()

剩下的就是常规的类型实例化。

Read More

(Ab)use system_error exception for System Calls

C++ 11 引入了没有掀起什么波澜的 std::error_code 和几乎没收到什么关注的 std::system_error

偶然发现后者可以用来做针对 system calls 的异常汇报处理。

0x0 A Traditional Approach

Win32 API 调用失败后具体的错误码可以通过 ::GetLastError() 获取;Linux 上 syscall 的错误码则用更 C-ish 的 errno 获取。

如果希望通过异常来汇报这些系统调用失败,那么通常会从 std::runtime_error 继承出一个 exception class,额外包含系统调用的错误码。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class win_last_error : public std::runtime_error {
public:
using error_code_type = unsigned long;

explicit win_last_error(const char* msg)
: runtime_error(msg),
error_code_(::GetLastError()) {}

[[nodiscard]] error_code_type error_code() const noexcept {
return error_code_;
}

private:
error_code_type error_code_;
};

上面的代码稍加处理后可以同时支持 GetLastError()errno

0x1 Using std::system_error

std::system_error 恰好也是一个和 system facilities 有关的异常类,汇报系统相关的错误。

除了传统的 what() 之外它还提供一个 code() 函数返回对应的 error_code

std::error_code 可以通过指定不同的 category 来指示其包含的错误码语义。

所以实际上我们可以这么用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
inline pipe make_pipe() {
#if defined(OS_WIN)
...
if (::CreatePipe(&fds[0], &fds[1], &sa, 0) == 0) {
throw std::system_error(std::error_code(::GetLastError(), std::system_category()),
"CreatePipe() failure");
}
...
#else
...
if (::pipe(fds) != 0) {
throw std::system_error(std::error_code(errno, std::system_category()),
"pipe() failure");
}
...
#endif
}

注意这里使用的 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 的错误码

0x2 Q&A

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 层层上抛。

Read More

himsw - Hey I aM Still Working

因为公司的安全政策要求,公司发的笔记本会在5分钟没有操作后自动锁住当前账户,并且这是由预装的安全组件强制保证的。

(不要想着可以把安全组件卸载或者禁用了,会被 IT 查水表的)

有时候加着仓或者用手机看个新闻就超时锁屏了,总有种摸鱼被发现的感觉,不太好。

更蛋疼的是,WFH 的时候只能用公司发的笔记本;而每次锁屏后都会导致 VPN 掉线需要重连,并且有概率会无法重连需要重置网卡….

没办法只能自己写了个小工具,在3分钟没有操作后进入模拟工作状态,定时发送键盘按键消息;同时监听鼠标和键盘消息,如果出现人为操作,就退出模拟状态。

经过一段时间的体验,基本实现了初衷,摸鱼再久也不会被发现了 😁


项目代码完全开源,可以在这里查看

核心就三点:

  1. GetLastInputInfo() 获取程序上次获得输入的时间;用来判断 idle duration
  2. 进入 simulation state 之后定时通过 SendInput() 发送键盘事件;为了避免干扰,选择了 ScrollLock 这个按键
  3. 利用键盘钩子和鼠标钩子监听全局的键盘/鼠标事件;有人为活动后,自动退出 simulation state

因为用到了系统的 API 所以目前只支持 Windows,估计也不好移植到其他平台。

一开始 GUI 是用 nana 做的,后来发现如果要支持 tray icon 的消息处理还要自己对窗口做一遍 sub classing…

索性弃用 nana 用 native win32 做了个 dialog,起码各种 message handling 会灵活一点。

因为是自家用,所以也不提供功能选项定制啥的,毕竟用不到啊。

最后再吐槽一下:我是真不喜欢做 UI

Read More

Using Headless Chrome to Convert Offline HTML to PDF in Bulk

手里有一批某课程内容的教案但是都是导出的 HTML 文档,因为每次打开都会请求一堆的外部资源(比如JS),导致卡顿几秒钟。

但是这是盗版资源 (shame on me) 所以这些外部请求完全是捣乱;于是想到批量转换为 PDF 就好了。

一开始选择的是 wkhtmltopdf,但是 libpng 会因为源文件的 sRGB 不正确导致失败。看到有个 2017 年报的 issue 到现在还是 Open 状态,遂放弃。

后来想到浏览器可以正常打开,也可以正常 print to pdf,那何不让浏览器批量处理?

恰好 Chrome 支持 headless mode,结合简单的 powershell 就可以做到批量转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$dest_dir = "/your/dest/dir/"
$src_files = Get-ChildItem "/your/src/dir/"

$env:Path += ";C:\Program Files (x86)\Google\Chrome\Application\"
foreach ($file in $src_files) {
$output_file = $file.BaseName + ".pdf"
$dest_file = Join-Path -Path $dest_dir -ChildPath $output_file
Write-Output "Processing $dest_file"
chrome.exe --headless --disable-gpu --print-to-pdf-no-header --print-to-pdf=$dest_file $file
# a simple throttle
Start-Sleep 5s
}

Write-Output "Congratulations!!!"

每次调用都会创建一个 headless chrome 做转换,而一次转换大概需要十几秒;同时这个调用是启动 chrome 之后就立即返回的,并非等到转换结束。

所以为了避免短时间内创建大量的 headless chrome 进程把系统资源耗死,这里简单的用 sleep 做一个节流器

Read More