Windows 平台编译 FFmpeg

需求

  • FFmpeg 7.0 开始已经支持 D3D12VA,可以自己编译一份采用 LGPL 协议的定制版了。

  • 以前都使用 ShiftMediaProject,现在想学习一种新的编译方式。

解决

这次采用较为普遍的方式:在 MSYS2 环境下编译。

1. 下载、安装

官网下载,并且官网有 Installation 说明。安装完 MSYS2 本身后,需要在 MSYS2 环境下安装编译 FFmpeg 所需的工具:

1
pacman -S autotools yasm

当然,您还需要下载 FFmpeg 代码,目前合适的版本为 7.1。下载完,解压到合适的目录,比如 D:\devel\ffmpeg,这样代码目录就是 D:\devel\ffmpeg\ffmpeg-7.1

2. 启动编译环境

为了编译 x64 版本,运行 x64 Native Tools Command Prompt for VS 2022。然后进入 MSYS2 安装目录,输入以下命令:

1
msys2_shell.cmd -use-full-path

此时获得一个新打开的 MSYS2 实例窗口,后面的命令在这里输入(即,不需要 x64 Native Tools Command Prompt for VS 2022 窗口了)。

3. 编译

由于稣只需要 D3D12VA 对 HEVC 的支持,所以编译其实很快,命令行如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cd /d/devel/ffmpeg/ffmpeg-7.1
export OUT_DIR=../build
./configure \
--target-os=win64 \
--arch=x86_64 \
--toolchain=msvc \
--enable-shared \
--disable-all \
--disable-autodetect \
--enable-swresample \
--enable-avcodec \
--enable-encoder=hevc_d3d12va \
--enable-decoder=hevc \
--enable-d3d12va \
--enable-hwaccel=hevc_d3d12va \
--prefix="$OUT_DIR"

make -j
make install

Windows 平台用 VBS 保护私钥安全

问题

之前稣写过《macOS 上用触控 ID 安全登录 SSH》和《Debian 12 上用 TPM 2.0 安全登陆 SSH》,为何没 Windows 版?

分析

当然有!只是太简单,稣以为大家都会……直到今天才发现不少人都不懂保护自己的私钥,真以为没人惦记你的三千块钱存款吗?

解决方案

网络上能轻易找到的一种解决方案是使用虚拟智能卡,它使用 TPM 2.0 保护私钥,而且使用时需要输入 PIN,安全性是可以的,唯一的问题就是很麻烦。

详情参考:Secure SSH Access with TPM2-Backed Key

稣的解决方案基于 VBS,全称 Virtualization-based Security那啥,不是 VBScript 这种已经去世几十年的东西。 VBS 的底层有一部分是 TPM 2.0,安全性有保障。

1. 离线产生私钥

  • 专门买一台百来块的擸𢶍小主机,从不接入网络,是从不,连局域网都不。

  • 产生密钥导出时需要使用强密码保护。

  • 将加密的密钥文件(.pfx)复制到一个安全的 U 盘,但是这个 U 盘丢了也不怕,因为擸𢶍小主机上还有密钥,而且 U 盘里的文件也有密码。

当然,如果您觉得私钥丢了也无所谓,那可以不用保存私钥副本,只需要保证私钥不被偷。

2. 导入使用密钥的机器

这步隐含“使用密钥的机器”是不那么安全的,所以需要处处防御:

  • 机器拔网线、断 WiFi,重启,进入安全模式。

  • 插入 U 盘,快速导入私钥,导入时必须选择“使用虚拟化安全保护私钥(不可导出)”,快速移除 U 盘。

  • 重启。

导入时有几个可选项:

  • “启用强私钥保护。如果启用这个选项,每次应用程序使用私钥时,你都会收到提示。”稣一般也开启,以便知道哪些应用不老实。

  • 密码。如果机器只有自己使用,可以不用设置,把“此密钥需要密码”点掉。即使不设置密码,被 VBS 保护的私钥也不可能被偷走。

后记

本文没有任何命令和图片,单纯传授心法,因为绝大多数的观众老爷们根本没有安全需求,贴出图片也是浪费时间和流量。

Diffie-Hellman 密钥交换协议

Diffie-Hellman 密钥交换协议,允许双方在没有预先共享秘密密钥的情况下通过不安全的通信渠道建立共享的秘密密钥。这个名字来源于发明者 Whitfield Diffie 和 Martin Hellman。

Diffie-Hellman 密钥交换原理简介

Diffie-Hellman 密钥交换(通常简称为 DH)依赖于数论中的一个事实:在一个有限字段上,给定生成元 g 和素数 p,计算 $g^a \mod p$ 相对容易,但是尝试反向计算,即已知 $g^a \mod p$ 求 a,则是计算上不可行的,这种问题被称为离散对数问题。

流程

  1. 参数生成和共享:首先,双方同意使用一个公共的大素数 p 和基数 g(这两个值就是所谓的 “DH Parameters”),g 通常是 2。

  2. 私钥和公钥生成:

  • 每一方选择一个私有(不与对方共享)的随机数,作为自己的私钥。设发送方私钥为 $\alpha$,接收方为 $\beta$。
  • 然后计算公钥 $A = g^\alpha \mod p$(发送方)和 $B = g^\beta \mod p$(接收方),并互相交换这些公钥。
  1. 计算共享秘密:
  • 发送方计算 $S = B^\alpha \mod p$。
  • 接收方计算 $S = A^\beta \mod p$。
  • 如果计算正确,双方将得到相同的值 S,该值可以用作通信双方的共享秘密。

由于反向计算离散对数是计算上不可行的,因此第三方无法仅仅通过拦截 A 和 B 来确定共享秘密 S,除非他们能够解决离散对数问题,这在实践中是非常困难的,尤其是对于足够大的 p。

使用场景

Diffie-Hellman 主要用于在不安全的通道上安全地交换密钥,这些密钥之后可以用于加密后续通信。例如,在 TLS/SSL 握手过程中就可能使用它来建立会话密钥。

密码套件和 DH 参数

TLS 支持多种类型的密码套件,这决定了密钥交换、消息认证、加密和密钥材料生成算法。当涉及到 Diffie-Hellman 密钥交换的密码套件时,它们通常会分为两类:

  • 传统 DH 密钥交换:在这种情况下,服务器和客户端事先同意一组 DH Parameters(公共的大素数 p 和基数 g)。服务器在 TLS 协议的相应阶段将这些参数发送给客户端。然后,服务器和客户端各自生成一个随机私钥,并基于此私钥和接收的参数计算公钥,再互换公钥以建立一个共享秘密。由于这些参数(特别是素数 p 和基数 g)是公开交换的,所以对安全性几乎没有影响,关键是私钥的私密性。

  • 椭圆曲线 Diffie-Hellman 密钥交换(ECDH):与传统 DH 类似,但使用椭圆曲线数学而非模幂运算。在 ECDH 中,参数是指椭圆曲线的定义,以及用于生成公私钥对的基点。这些参数同样需要在通信双方间共享或事先约定。

SSL/TLS 握手和 DH 参数

在实际的 SSL(或 TLS)握手过程中,以下步骤涉及 DH Parameters 的使用:

  1. 服务器 Hello 消息:服务器选择一个密码套件,该套件可能包含 DH 密钥交换方法。

  2. 服务器 Key Exchange 消息:如果所选的密码套件基于 Diffie-Hellman 密钥交换,服务器将在这个消息中发送 DH Parameters(对于 ECDH,发送椭圆曲线参数和服务器的公钥)。对于某些 DH 套件,服务器的公钥可能已经在一个证书中,这种情况下,参数可能不需要在这条消息中发送。

  3. 客户端 Key Exchange 消息:客户端接收到服务器的 DH Parameters 后,生成自己的 DH 密钥对,发送自己的公钥给服务器。

  4. Premaster Secret 的计算:一旦双方都有了对方的公钥,它们就可以独立计算出一个共同的秘密值(Premaster Secret),接下来使用这个秘密值生成会话使用的加密密钥。

在整个过程中,即使 DH Parameters(素数 p、基数 g 和相应的公钥)是公开的,由于 Diffie-Hellman 问题的难解性,攻击者不能轻易地计算出共享秘密,从而保证了密钥交换过程的安全性。

注意

虽然 Diffie-Hellman 本身是一种安全的密钥交换协议,但它不提供身份验证功能。因此,在某些实现中,它通常会与数字证书或其他身份验证机制结合使用以防范中间人攻击。

真实的程序员情商高得可怕!

情绪商数(Emotional Quotient,EQ)是指一个人对自己和他人情感的感知、理解和应对能力。但长期以来,在传统文化浸濡下,很多人以为情商是处事圆滑,甚至是 PUA 那套。但凡有人拒绝被 PUA,就会迎来一顿“情商低”的舆论攻击。

程序员,就是最大的受害者群体,可能有之一,但几乎……没有!

以下从四个模型五个视角来说明——其实天生的程序员,情商高得可怕!

四个模型

  1. 情绪知觉(Perceiving Emotions):程序员最善于观察,所以很容易知道对方是否被激怒,随时会调整自己的话术,既不能让对方被气死,更不能让对方动手打人。

  2. 情绪理解(Understanding Emotions):程序员最善于理解前向传播和后向传播。对方如何被激怒,甚至气得说不出话的,都是全程跟踪和拿捏的。甚至还能为下一次战争调整模型参数。

  3. 情绪运用(Using Emotions):程序员最善于隐藏情绪。隐藏实力、知己知彼,才能百战不败。打不过时,有的脾气爆发,辞职走人,有的留下一堆诗山,辞职走人。

  4. 情绪调节(Managing Emotions):程序员最善于保持脸瘫,情绪一直良好。

五个视角

  1. 自我意识(Self-awareness):程序员平均智商高于常人,自我意识同样更强。

  2. 自我管理(Self-management):程序员自我驱动力很强。

  3. 社交意识(Social awareness):世人对程序员最大的误解在此。其实程序员的社交意识特别强烈,不然为何有世上最大的同性社交平台?程序员们只是排斥无效社交而已,人们理解的那种酒桌文化就是程序员认为的无效社交。但如果哪天需要创业,需要演戏,也不是不行哦。稣曾经见过某些隐藏实力的程序员爆发惊人的酒量,您想说这是个例?非也,都是人,这种个例的比例不会低于整体人群的比例。

  4. 关系管理(Relationship management):程序员特别能管理关系!能力差的,很容易就被管理出局。能力强的也特别突出,一般用来搞上面的人,争取气死一批是一批。

  5. 自我激励(Self-motivation):程序员特别擅长跳槽加薪……另一方面,别人眼中枯燥无味的东西,程序员乐此不疲,不也是自我激励吗?

真实案例

程序员天生会《孙子兵法》,而且运用自如。下面看看一些经典案例。

  1. 产品经理:理解能力怎么这么低下?

故意的。他是觉得产品经理没干啥活,故意不懂,让产品经理把事情理清楚,流程画完整再来。你想浪费时间争辩,我也正好休息一会儿,发泄一下,争取当场气死你。

  1. 领导:你怎么把问题又抛给我?

原因大约有三类:一是你管太多了。疑人不用,用人不疑。管是没问题的,不要管技术细节,你真这么懂,自己做?让程序员花时间解释,你怎么不自己看代码?二是你交代的需求不明确,只能你自己选型。三是你带着偏好,比如你喜欢某方案,一个劲提起,那不然你随便实现了?

  1. 写个文档都写不好?

两类比较大的可能:一是写的这类文档不应该程序员写,某些角色不够专业或上心,应该自己写的,推给程序员。二是这是机密技术,怎么能写明白呢?杀人诛心啊!还是装傻保命吧。

  1. 怎么没有架构设计文档?是不是偷懒?

相反,正是因为实在,所以“对不起,没有!”。这通常是一个不合事宜的问题,尤其是在敏捷开发的团队。一些业务为主的产品,有合格的需求文档基本就行,开发者每个都是架构师。而以技术深度著称的产品,架构通常是固化的,或者有知名的最优解,没得选,没人只是架构师。而以量级大为特色的产品,基本就是抄,架构是现成的,比如国产操作系统。总之,在真实的商业软件里架构设计是一个特别尴尬的东西,往往是后补上的。但是,万一真的有一个架构师职位的人,一定要让他事先写好,以后实现发现不对,再来讨伐他。em……花时间写这东西,还可能被批判,情商高得可怕的程序员,怎么会写呢?

  1. 测试环境好好的,怎么到了用户环境就出 bug?

这是为了 GDP、为了民生考虑的伟大情操!业界不成文的规则:开发阶段只解决理想环境的需求,测试阶段解决产品、测试人员的饭碗,发布阶段解决运营人员和自己后续的饭碗。开发周期短,其它周期就长,程序员是精于计算的,不打没胜算的仗。如果不能养活一群人,那和当畜生直接给吃掉有啥区别?

该不该用 std::string_view 替代 const std::string&?

问题

  • 有的大佬建议使用 std::string_view 替代 const std::string&,也有的大佬表示反对,到底该不该呢?

  • 以下代码运行后显示什么?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string_view>

using namespace std::string_view_literals;

int main() {
auto s = "Hello C++ 20!"sv.substr(0, 5);
printf("%.*s\n", static_cast<int>(s.size()), s.data());
std::cout << s << '\n';
printf("%s\n", s.data()); // buggy
}

分析

反对者可能是 C 语言的受害者!(这里没说反对得不对哦!)

先看看 CppCoreGuidelines 的说法:

In C++17, we might use string_view as the argument, rather than const string& to allow more flexibility to callers

这里说的是使用 std::string_view 作为参数,能够适配更多的调用者。不管传入 const char* 类型,还是 std::string 类型的变量,都能编译。

反之,如果参数是 const std::string& 类型,则有以下缺点:

  • 传入 std::string_view 类型无法编译。

  • 传入 const char* 类型,会多构造一个 std::string 类型的临时变量。

但是 CppCoreGuidelines 并没有建议把 const std::string& 参数都替换成 std::string_view,而是说字符串是一个大话题,需要分很多种情况讨论。具体可见 SL.str: String 节,下面列出大概意思:

Text manipulation is a huge topic.
std::string doesn’t cover all of it.
This section primarily tries to clarify std::string’s relation to char*, zstring, string_view, and gsl::span<char>.
The important issue of non-ASCII character sets and encodings (e.g., wchar_t, Unicode, and UTF-8) will be covered elsewhere.

  • SL.str.1: Use std::string to own character sequences
  • SL.str.2: Use std::string_view or gsl::span<char> to refer to character sequences
  • SL.str.3: Use zstring or czstring to refer to a C-style, zero-terminated, sequence of characters
  • SL.str.4: Use char* to refer to a single character
  • SL.str.5: Use std::byte to refer to byte values that do not necessarily represent characters
  • SL.str.10: Use std::string when you need to perform locale-sensitive string operations
  • SL.str.11: Use gsl::span<char> rather than std::string_view when you need to mutate a string
  • SL.str.12: Use the s suffix for string literals meant to be standard-library strings

那么,究竟啥场景能用 std::string_view 类型参数?

F.25: Use a zstring or a not_null<zstring> to designate a C-style string 节有一句话:

If you don’t need null termination, use string_view.

这句是重点!因为 std::string_view 并不保证 Null-terminated,如果函数使用 std::string_view 参数,却做了 Null-terminated 的假定,那么,在把参数传给其它 C-Style API 时,就可能导致 bug,甚至崩溃。

例如,这位大佬的文章说的情况:

简单地说,std::string 是保证 Null-terminated 的,而 std::string_view 不保证,当 const std::string& 参数被替换为 std::string_view 时,对于部分没有 Null-terminated 的字符序列,C-Style API 可能读入超过范围的字符,而导致逻辑上的错误,还可能因为读到无权限的地址而崩溃。

总结

简单地说,看情况而定。总之,为了写高质量的 C++ 代码,必须细分这么多的情况。

但您如果熟悉 C 语言,可能会提出疑问:大部分 CRT 函数或 C-Style API 不都做了字符串是 Null-terminated 的假定?确实如此!如果人人写好注释,人人遵守规则,那确实能把代码简化。问题是:异常和意外,哪个先来?

在容器的遍历中使用迭代器删除元素

问题

  • 小伙伴在 std::map 的遍历中使用 map.erase(iter++) 删除元素,稣在审查代码时提醒:最好养成习惯使用 iter = map.erase(iter),因为对于其它容器,前者可能是错的。

分析

先跑代码测试,再讲道理:

例一

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
#include <deque>
#include <iostream>
#include <list>
#include <vector>

int main() {
// std::list<int> c = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// std::deque<int> c = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::vector<int> c = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (const auto& e : c) {
std::cout << e << ' ';
}
std::cout << '\n';

for (auto iter = c.begin(); iter != c.end();) {
if (*iter & 1) { // remove odd elements
// iter = c.erase(iter); // This is correct in all cases

c.erase(iter++); // For list/map/set, this is correct;
// but for deque/vector, this may crash!
} else {
++iter;
}
}

for (const auto& e : c) {
std::cout << e << ' ';
}
std::cout << '\n';
}

使用 gcc version 12.2.0 (Debian 12.2.0-14) 测试:

1
2
3
4
5
6
7
8
9
$ g++ -std=c++20 crash.cpp -o crash

$ ll
-rwxr-xr-x 1 618 618 32K 7月13日 15:56 crash*
-rw-r--r-- 1 618 618 741 7月13日 15:55 crash.cpp

$ ./crash
0 1 2 3 4 5 6 7 8 9
fish: Job 1, './crash' terminated by signal SIGSEGV (Address boundary error)

在 std::vector(和 std::deque)中,删除元素会导致后续元素移动,当删除最后一个元素 9 时,有以下具体步骤:

  • 对 iter 取值(给 erase),它指向 9;
  • iter++,使 iter 变成 end;
  • erase 9 使“现 end”前移了一位,那么 iter 指向的“原 end”就不再是“现 end”;
  • 循环条件 iter != c.end(); 满足,继续循环……

其它容器,比如 std::list(链表)、std::map/std::set(红黑树),其删除操作不会影响到其他迭代器,所以使用 .erase(iter++) 是安全且高效的。

例二

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
#include <deque>
#include <iostream>
#include <list>
#include <vector>

int main() {
std::vector<int> c = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
for (const auto& e : c) {
std::cout << e << ' ';
}
std::cout << '\n';

for (auto iter = c.begin(); iter != c.end();) {
if (2 == *iter || 3 == *iter) { // remove 2 and 3
// iter = c.erase(iter); // This is correct in all cases

c.erase(iter++); // for vector, this is incorrect!
} else {
++iter;
}
}

for (const auto& e : c) {
std::cout << e << ' ';
}
std::cout << '\n';
}

删除 std::vector 中相邻的元素,结果漏删了 3。

解决

唯一安全并通用的方式是 iter = c.erase(iter);,要养成习惯。

附录

  • Erase-Remove Idiom in C++

行结束符

1. 问题

  • 为何各平台行结束符不同?

  • 跨平台开发应该使用哪种行结束符?

2. 分析

行结束符(end-of-line, EOL)是文本文件中用来表示新行(new line)的间隔,也称作断行符(line break)。

历史上,有三种 EOL:CR、LF 和 CRLF。其中:

  • CR (Carriage Return),即回车;
  • LF (Line Feed),即换行。

古典的 macOS 使用 CR 做 EOL,现在已经改为 LF,所以主流只有 LF 和 CRLF。

哪个更好呢?

从技术的发展史来看,CRLF 更直观。在机械打字机时代,CR 和 LF 分别具有不同的作用:LF 将打印纸张上移一行位置,但是保持当前打字的水平位置不变;CR 则将“Carriage”(打字机上的滚动托架)滚回到打印纸张的最左侧,但是保持当前打字的垂直位置不变,即还是在同一行。 当 CR 和 LF 组合使用时,就是将打印纸张上移一行,且下一个打字位置将回到该行的最左侧。Windows 采用 CRLF,说明微软的人是很技术思维的,就是耿直地认为:行结束就应该像打字机那样先 CR,再 LF。

但从存储和解析的成本来看,LF 更好。所以现在微软在 Windows 11 里也倾向于同时支持两者,目前就连记事本也能识别 LF 格式的 EOL 了。

只使用 LF 可行吗?

能统一肯定是好事!在写代码这件事上,只使用 LF,没啥坑。VS2022、VSCode 都能同时支持 CRLF 和 LF。

3. 实践

  1. git 设置
1
2
git config --global core.autocrlf input
git config --global core.safecrlf true
  1. 工程 .gitattributes 模板
1
2
3
4
*                   text=auto eol=lf
*.sln text eol=crlf
*.vcxproj text eol=crlf
*.vcxproj.filters text eol=crlf

有其它 Windows 特有,并且一保存就自动格式化为 CRLF 的文件,都设成 text eol=crlf

  1. 工程 .editconfig 模板
1
2
3
4
5
6
7
8
# Visual Studio generated .editorconfig file with C++ settings.
root = true

[*.{c++,cc,cpp,cxx,h,h++,hh,hpp,hxx,inl,ipp,tlh,tli}]
charset = utf-8-bom
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

华为擎云 W515 安装 Debian 12

1. 需求

GitHub 太慢,Gitee 老是被封,发邮件反馈好多次,至今给稣打上奇怪的标签。

https://gitee.com/umu618

于是直接使用 gitlab-ce 打造自己的 git 服务,实现真正的 Private 仓库。

2. 硬件选型

首先,上擸𢶍,一台 J4125,6GB 内存的机器。实测 6GB 内存不太够,淘汰。

看来需要 8~12GB 内存比较靠谱,手里还有 3 台 N100,内存分别为:LPDDR5 4800MHz 8GB、DDR4 3200MHz 32GB、DDR5 4800MHz 32GB,问题是 N100 这么高贵的核显不是浪费了?果断都淘汰。

后来,把 DDR4 3200MHz 32GB 的 N100 机器出给道友,并收了他的华为擎云 W515,硬件选型迎来完美结束——省电的 arm64 CPU(整机并不省电);8GB 内存;带不动 4K 的擸𢶍核显,一点都不浪费。

3. OS 选型

UOS 和 KylinOS 都玩了好几遍,它们都是桌面系统,有许多限制,不适合做 Server。

考虑上 Debian,但正常安装 Debian 11 和 12 都会遇到各种黑屏、无法启动。推测新内核无法启动 W515,是因为国产生态的开发比较保守,主要使用 4 和 5 这两个大版本,没给 6 的大版本适配。

最开始的解决方式是把 Debian 的内核换成 UOS 或 KylinOS 的,虽然启动报错导致启动慢,但又不是不能用。截图是 KylinOS 内核:

W515 信息

EFI 程序也是使用 Kylin 的:

W515 EFI

至此已经能够使用最新 Debian 发行版,并安装 gitlab-ce。

4. 后记

以上折腾完都快一年了,最近发现原来 Debian 10 (Buster) 的内核就是 4.19 系列,所以它的 arm64 版可以直接使用……

修复因防火墙故障导致的 winget 无法使用

问题

首次使用 winget,报无法注册 windows.firewall 扩展,防火墙无法启动。

相关问题

  1. https://github.com/microsoft/winget-cli/issues/4305

  2. 安装Microsoft Store应用时,系统无法注册 windows.firewall 扩展

分析

不仅是 winget 会因为防火墙故障而无法启动,其它 Microsoft Store 应用也可能遇到,说明这问题出在防火墙,而不是 winget。

经过排查发现根源是 Base Filtering Engine 服务无法启动导致。用 sc 命令可以发现,Windows Defender Firewall (mpssvc) 依赖 Base Filtering Engine (bfe),故 BFE 无法启动必然导致防火墙无法启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sc qc mpssvc
[SC] QueryServiceConfig 成功

SERVICE_NAME: mpssvc
TYPE : 20 WIN32_SHARE_PROCESS
START_TYPE : 2 AUTO_START
ERROR_CONTROL : 1 NORMAL
BINARY_PATH_NAME : C:\Windows\system32\svchost.exe -k LocalServiceNoNetworkFirewall -p
LOAD_ORDER_GROUP : NetworkProvider
TAG : 0
DISPLAY_NAME : Windows Defender Firewall
DEPENDENCIES : mpsdrv
: bfe
: nsi
SERVICE_START_NAME : NT Authority\LocalService

尝试

  1. 使用 sfc 修复几次,bfe 还是无法启动!

  2. 从另一台机器导出一份干净的 BFE 服务注册信息,导入故障机,依然无法启动。

  3. 后来想到:导入时使用的是当前用户身份,可能权限不太一样,于是检查 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\BFE 下每个键的权限,发现确实是不一样的。

  4. 给 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\BFE\Parameters 设置权限,添加用户 NT Service\BFE 即可。

参考

Base Filtering Engine (BFE)服务无法启动-CSDN博客

双拼输入法

用户故事

早在 2018 年,稣就因为鼠标手换用轨迹球,后来连键盘也开始敲不得了。

稣发现,同龄程序员似乎没这烦恼,大概他们都转行了规规矩矩地学会指法,而稣长期只使用双手的大拇指、食指和中指,而且必须看着键盘打。其中大拇指主要用来敲敲空格,所以基本上都在磨损食指和中指,长期下来,稣就输了!

但是似乎减少击键比学指法简单,于是大约从 2022 年中开始,稣选择中国人最擅长的弯道超车——学双拼。

实测

1. 小鹤

开始选择双拼方案时,第一印象是都差不多。选小鹤主要是觉得这是自然码的一种改进版,而且是中国人开发的,应该更懂汉字输入法。

使用大约两年,感觉小鹤挺适合稣。因为小鹤更多常见字集中在单手,对不会指法和盲打的人更友好。

2. 微软

但小鹤还是把稣惯坏了,使得学习指法被搁置,食指和中指的磨损依然远比其它手指厉害。听说改用微软方案可以练成少冲剑(;),稣便想一试,毕竟这是六脉神剑微软啊!

于是练了 2 周的微软双拼,少冲剑(;)大成,但发现两个问题:

  1. 手机上键盘布局不统一,iOS 和 Android 的 ; 位置不同,而且 iOS 的上两排原本和 PC 键盘一样垂直错开的,因为多了 ; 变成垂直对齐了,十分奇怪……

  2. 微软是固定零声母(默认为O),不自然,不太习惯。

iOS 上自然码系的键盘布局

iOS 上微软方案的键盘布局

3. 自然码

如图,自然码其实和已经熟悉的小鹤区别不大:

自然码键位

小鹤键位

两者有 15 个键是一样的:

相同的键

把自然码如下图这般移动 11 个键即得到小鹤:

不同的键

目前使用自然码将近一个月,这相对陌生的方案逼着稣使用正确的 PC 键盘指法,使得稣会继续用它。

辅助码

割爱!不管是 PC,还是手机,稣只使用系统原生的输入法,都不支持,就不想了。