善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。

2025-11-11

你可能从未注意过的 MFC 陷阱:模态对话框禁用机制的局限性

在最近的一个软件开发案例中,遇到了不容易处理的情况。

在我们的软件中,有那种一直浮在界面上的非模态对话框,例如那种浮动的工具面板。但是,这种窗口,貌似不会在主窗口弹出模态对话框的时候被 Disable。如果模态对话框是在这个非模态对话框中弹出的,那没有问题。

用 VS2013 和升级到最新的 VS2022 各写了一个测试程序,发现这就是 MFC 的默认逻辑。
在主窗口上放了两个点击后各自会弹出非模态对话框和模态对话框的按钮。先弹出非模态对话框,然后再去弹出模态对话框。此时主窗口被 Disable,无法响应鼠标、键盘消息。但非模态对话框不受影响。

本想靠调整产品设计「容忍」过去。但问题在于,如果在保持模态对话框弹出的情况下,去把非模态对话框先关闭了,那主窗口会被 Enable。此时它跟模态对话框之间都能响应鼠标、键盘消息,效果就好像之前弹出的模态对话框变成了非模态对话框一样。这个时候就会有「后果」了,产品设计再怎么调整,也没法让软件在这种乱了套的情况下还能工作正常。


在网上 Google 了半天,不要说解决方案,连问题都没人提到。讲解模态 / 非模态对话框的文章有一些,但都是很入门的介绍。只是从「使用者」的角度去讲用法谈区别,并没有涉及我遇到的问题。很少从原理角度进行说明,更没有去分析源代码。大概 MFC 现在用的人真的不多了吧?

图片由 Google Gemini 生成

还是得自己动手,在 DoModal() 处下了一个断点,单步跟踪进到 MFC 的代码里面看了一下,就明白了。以下代码来自 VS2013,VS2022 我貌似没有安装 MFC 源代码,但从表现上看二者在这个逻辑上应该相差无几。我们一起来看看 CDialog::DoModal() 到底干了些什么事情吧:

INT_PTR CDialog::DoModal()
{
	// can be constructed with a resource template or InitModalIndirect
	ASSERT(m_lpszTemplateName != NULL || m_hDialogTemplate != NULL ||
		m_lpDialogTemplate != NULL);

	// load resource as necessary
	LPCDLGTEMPLATE lpDialogTemplate = m_lpDialogTemplate;
	HGLOBAL hDialogTemplate = m_hDialogTemplate;
	HINSTANCE hInst = AfxGetResourceHandle();
	if (m_lpszTemplateName != NULL)
	{
		hInst = AfxFindResourceHandle(m_lpszTemplateName, RT_DIALOG);
		HRSRC hResource = ::FindResource(hInst, m_lpszTemplateName, RT_DIALOG);
		hDialogTemplate = LoadResource(hInst, hResource);
	}
	if (hDialogTemplate != NULL)
		lpDialogTemplate = (LPCDLGTEMPLATE)LockResource(hDialogTemplate);

	// return -1 in case of failure to load the dialog template resource
	if (lpDialogTemplate == NULL)
		return -1;

	// disable parent (before creating dialog)
	HWND hWndParent = PreModal();
	AfxUnhookWindowCreate();
	BOOL bEnableParent = FALSE;
	CWnd* pMainWnd = NULL;
	BOOL bEnableMainWnd = FALSE;
	if (hWndParent && hWndParent != ::GetDesktopWindow() && ::IsWindowEnabled(hWndParent))
	{
		::EnableWindow(hWndParent, FALSE);
		bEnableParent = TRUE;
		pMainWnd = AfxGetMainWnd();
		if (pMainWnd && pMainWnd->IsFrameWnd() && pMainWnd->IsWindowEnabled())
		{
			//
			// We are hosted by non-MFC container
			// 
			pMainWnd->EnableWindow(FALSE);
			bEnableMainWnd = TRUE;
		}
	}

	TRY
	{
		// create modeless dialog
		AfxHookWindowCreate(this);
		if (!CreateRunDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), hInst) && !m_bClosedByEndDialog)
		{
			// If the resource handle is a resource-only DLL, the dialog may fail to launch. Use the
			// module instance handle as the fallback dialog creator instance handle if necessary.
			CreateRunDlgIndirect(lpDialogTemplate, CWnd::FromHandle(hWndParent), AfxGetInstanceHandle());
		}

		m_bClosedByEndDialog = FALSE;
	}
	CATCH_ALL(e)
	{
		TRACE(traceAppMsg, 0, "Warning: dialog creation failed.\n");
		DELETE_EXCEPTION(e);
		m_nModalResult = -1;
	}
	END_CATCH_ALL

	if (bEnableMainWnd)
		pMainWnd->EnableWindow(TRUE);
	if (bEnableParent)
		::EnableWindow(hWndParent, TRUE);
	if (hWndParent != NULL && ::GetActiveWindow() == m_hWnd)
		::SetActiveWindow(hWndParent);

	// destroy modal window
	DestroyWindow();
	PostModal();

	// unlock/free resources as necessary
	if (m_lpszTemplateName != NULL || m_hDialogTemplate != NULL)
		UnlockResource(hDialogTemplate);
	if (m_lpszTemplateName != NULL)
		FreeResource(hDialogTemplate);

	return m_nModalResult;
}

看到高亮的代码应该就能明白了。MFC 在弹出模态对话框的时候,只是把 hWndParent 给 Disable 了。然后如果发现主窗口还没被 Disable,再补上一刀。后面这个逻辑应该就是为了应对在非模态对话框中弹出模态对话框的情况。

看到了这些逻辑,就可以明白本文最开始讲到的情况是情理之中的。模态对话框只管住了大 BOSS 和父亲,对于兄弟和叔伯之类,都没有去管。如果情况复杂一点,例如非模态对话框中又弹了第二层的对话框,那么不管这个对话框是模态还是非模态,接下来都有机会整出点问题。


知道了问题的原因,接下来就要寻找靠谱的解决方案了。肯定有人解决过这类问题,因为有些软件没有这种问题。但不知道是因为觉得问题太简单了不值得说,还是想藏着掖着?

希望是前者。因为真正的解决方案确实也很简单。

之前有同事尝试解决这个问题,办法是让模态对话框去通知所有的非模态对话框(或者说需要被 Disable 的窗口)自行 Disable,用了我们软件内部与 Windows 消息相互独立的另外一套通讯机制。

但这个解决方案有一些问题:模态对话框除了自己开发的那些,还有系统提供的 MessageBox、文件打开对话框等,另外还包括第三方库中的那些,都是我们无力触达的。所以虽然它花了很多力气让对话框们去多重继承一个基类,但还是没法彻底解决这类问题。

回头想想,主窗口不是总是会被 Disable 的么?那么监听主窗口的 WM_ENABLE 消息,在事件处理函数中把那些需要额外 Disable 掉的窗口集中处理掉,不是就可以了?

实际做起来也很简单,基本上就是在主窗口的 OnEnable() 中用 EnableWindow(bEnable) 把状态传递过去。唯一需要动点脑筋的,就是让那些需要额外 Disable 掉的窗口把自己的 HWND 注册到一个主窗口拿得到的地方,也就是说待处理的窗口列表需要管理起来。

具体的实现代码我就不贴了。重要的是解决问题的思路,具体如何实现,可以有很多种办法,选择适合自己的哪一种就好。相信读到这里的,都是合格的 Win32 / MFC 程序员。

2025-11-07

小目标

去年,儿子上了初中,虽然是「预备班」,即传统说法中的「六年级」,但还是换了个学校,换了同学,也换了班主任老师。总之,一切都是全新的了。

跟小学那个有点坏的数学班主任不同,初中的班主任是教体育的。女老师,精瘦,据说以前是踢足球的,不知道是不是哪个体校退下来的。倒是应该比较受学生的欢迎,特别是男生。

班主任的有些做派,我并不认同。当然我也知道现在基层教育的种种难处,巨大的机构裹挟之下难有个人发挥的空间,所以也不想去苛责这些年轻人。不过她有个事情我倒是蛮想点赞的:她让这些学生每天订一个「小目标」,去完成它,并且在家校本上记录下来。

儿子同学的小目标,来自「钉钉」家校群

预备班很勉强地坚持了一个半学期,这事在儿子身上目前大致算是撂下了。如果我不坚持催促,他是不会去订这个「小目标」的。即使在我的要求之下勉强去做了,也只是把自己偶然做过的一些事情给揉碎了写上一些。这基本上就是在「应付」了,肯定不是老师的本意,也不是我的。要我老实说的话,这种事情在这个年龄段的小男孩身上,的确还是有些勉强。

老师其实还订了一些别的「规矩」,比如按学号轮流在「钉钉」群里发家校本的内容。本意是借机督促每个学生都认真抄写每天的作业要求,然后也给那些偶尔忘抄或忘带的小可怜一个「补救」的机会。然而群里最后一次的发送记录是 10 月 15 日,接下来不知道是谁没发,再后面「链条」就断了。

出现这种情况,我也完全能理解。去问儿子什么时候该他发,他也不知道,只是记得他前一个同学是谁。他就只盯着那个同学,人家不发,他也就不把这件事放在心上。玩 STL 的都知道 forward_list 断了是啥下场。这规矩订得还是有一些脆弱,应该改成 vector 或 map。


我读初三的时候,语文老师也订了一个规矩,让我们每天回去记录一条新闻。规定了字数,大约两百字左右,不算多,但也显然不是你去把标题一抄就能合规的程度。规矩一出,班上同学一片哀嚎。作文也就六七百字,这相当于一周固定增加了两篇作文的作业量了。

我其实也很头疼。在那之前,我写个作文也是写几句话就开始数字数,尽量加写长点的定语来凑篇幅。如果去记录新闻的每一句话,却又来不及。我看过一点《大卫·科波菲尔》,知道速记学起来没那么容易。当时又没有手机可以摄下来录下来(话说我还真动过用录音机的心思),因此刚开始的一段时间,我也是跟其它同学一样,到了晚上就开始发愁。直到有一天……

那天我记得很清楚。我边吃晚饭边在看新闻联播,进入到最后十分钟的「他国都很乱」环节,播报了一条新闻,是讲联合国核查小组在伊拉克检查大规模杀伤性武器的事情。因为是我相对感兴趣的话题,所以虽然正在吃饭没去记录,但整件事情至少听完以后还能向家人完整复述出来。等我吃完饭准备写作业的时候,才发现只要用自己的话把听到的内容再描述一遍,这个作业是如此的简单。

不需要速记,不需要录音机,不需要每句话每个字都一样,只需要一个大概。我很轻易地就写了六七排,肯定超过两百字了。这个时候我才发现,原来两百字不是太多,而是不够。我很讶异:我都初三了,为什么以前都没学会这么简单的事情?以及,为什么班上其他人也做不到?

后来我在想,或许这就是所谓的「开窍」。事情发展到了一定的时候,窗户纸捅破了,突然一下就明白了。

老师第二天就把我写的东西当着全班的面念了一遍,然后传给班里每个人看。现在想来,当时她心里肯定有一种「我终于等到了」的感觉。那一刻,肯定很有成就感,多巴胺超级加倍。

班上同学们也很快就明白了,毕竟是重点中学,大家也都不是傻子,只是差那一下的棒喝。当天就有明白过来了的人开始模仿,接下来甚至有的家伙开始编「新闻」。当年那种奇奇怪怪的「社会新闻」有很多,不过我们听完之后还是纷纷议论「太离谱了」「肯定是编的」。但是老师也未置可否。回头看看,编的故事显然是更不错的果实,只要编得足够好。

151 岁,谁说是高科技来着?

当年我的老师是不是算是给我们设立了一个小目标,我不是很清楚。但是显然,目标不可能一天达到,但是也不能放弃,继续做下去,可能就有收获。指不定是在哪一天,但是放弃了,这事就真的没戏了。

就像丽春院的小姐姐说的:「叫,不一定有客。不叫,就一定没有客。」


儿子经常陷入「小目标写个啥」的忧思。我看到真是又好气来又好笑。要是让我每天只立一个小目标,我自然会嫌少:探索并关注 1 - 2 个有意思的 Substack 频道;写一篇 Blog;整理相册;去 Steam 上淘一个好游戏……简直不胜枚举,数不胜数。不过,儿子看来还缺些时候。我到初三才稍微开了一下窍,不能拔儿子的苗。

何况,儿子也有儿子的烦恼。我看了他最近的英语题目,包括数学试卷。有不少题都是那种「你猜猜我要考你什么」的题目。比如一句英语空两个位置让你填,目的其实是想让你填这个学期刚学过的某个词组。中国考生说不定真能答对,换个英语母语者来答,肯定不知道出题人脑子进过什么 shit。

百发百中

这种「先射一箭再画靶子」的事情,从我还是学生起,直到现在,一直就没变过。所以能教出什么来,考试分数高的都是些什么人,可想而知。当公务员肯定是好材料,可惜我个性不适合。

前天儿子一张数学试卷 58 分,我跟他一道题一道题地看错在哪里。昨天他 76 分,我跟他说你如果认真答题不粗心,大概也就是这个分数,七十多,运气好能上八十。如果哪天你开始得 100 了,那我就要担心你的脑壳是不是开始方了。

不多不多,买不了熊猫

算了,咱家都是普通人,还是先从定一个能达到的小目标开始吧,比方说先挣它一个亿……

2025-11-05

日渐老去

如果人生是一场足球赛,我已经进入下半场了。

图片由 Google Gemini 生成,非本人

周末跟太太回了一趟盐城,去了乡下的老家。太太张罗了一桌饭菜,宴请附近的亲戚和邻居。我则被苏北老男人们拖着喝酒,53 度的白酒,可能喝了得有二两半。

喝完酒,老男人们开始抽烟聊天,而我则躲进了房间玩《太阁立志传 5》。老游戏,里面的时间是一天一天地过。我的记忆是连续的,然而太太说我喝断片了。

次日她跟我说了我当时的一个细节,我对其发生前后的事件都有印象,而且印象并不模糊,唯独对这件事情一点印象都没有。按太太的说法,这件事我的参与度非常的高,不应该没有印象。如果不是太太在诓我,那就是我真的失忆了。

部分地,失忆了。


我的酒量,意外地还不算坏。

按照岳父的说法,我的极限应该在半斤白酒左右。当然,跟酒的度数,以及当时的身体状态也有关系。十五年前我去三亚出差,被迫一口气干了三杯白酒,每杯都有至少二两。后来也还有喝,然而最终也只是半夜爬起来吐掉了而已,并没有失忆。甲方也说,没想到你还挺能喝。

当然,我后来难受了一整天,大概是伤到了胃。但现在,显然是不可能这样喝酒了。

唉,酒有什么好喝的?但为什么我偏偏就躲不掉?


早上起床,上完厕所回来,闻到卧室一股味儿。

开门开窗通风,味道很快就散了。但是这味道勾起了我的一些不太愉快的回忆。

二十年前,我在父母房间,闻到的就是这样的味道。他们房间通风不是太好,也不太爱开窗通风,所以味道没那么容易散掉。于是后来我就避免长时间呆在他们房间。

我知道,那其实就是「老人味」。现在,我也有了。

是的,我也已经是老人了。


有一次,跟儿子在小区南门,看到 LED 屏幕上写着「45 岁以上的老人……」。回来跟太太说,她还不相信,还好有儿子作证。

这件事情非常打击我。因为我自认为还是个年轻人。

遇到老男人,我第一反应还是「爷爷」或「叔叔」,结果人家年龄可能比我还小。走路还是总想蹦蹦跳跳,遇到奇怪或者有意思的东西就想弄个明白。虽然我和太太都认为老歌更好听,但新歌我也并不排斥。年轻人知道的梗,流行的用语,我也几乎都知道。

对于新出现的事物,我积极拥抱,非常有热情,也不故步自封,觉得自己熟悉的那一套才是最好的。我也是一个还不错的聆听者,从来不像老登那样去驳斥年轻人的言论,更不会去指导别人的生活。而且只要有道理,别人还挺容易说服我的,几乎可以称为「从善如流」了。相比之下,有的同事可能年纪比我还小,但无论说话做事,还是面孔身材,都像个老头。

我看起来也的确显年轻,以至于时常被错认为是太太的儿子。商店的营业员,来装修的水电工,甚至太太闺蜜的父亲,都曾经搞错过。貌似我现在看上去跟实际年龄可能有接近20岁的差距了?

唯一能够出卖我的,只有脸上的皱纹:抬头纹、鱼尾纹、法令纹……。我不想去学普特勒做拉皮或打肉毒素,而且最好也不要去想这类事情:太太虽然会拿我那些「糗事」调侃,但我知道她已经有压力了。

然而,看起来老不老,是一回事。但身体是实实在在已经老了。


初次认识到这件事,是两年前一次去崇明玩的时候。

崇明还是比较适合养老的,生活节奏并不快。离上海不远,现在又快有地铁了。我们去泡了两天酒店,在东平森林公园逛了一圈,最后临走时,在一个街边小公园又晒了一会儿冬日难得的太阳。

小公园其实也不算很小。里面有一些健身器材,有单杠也有双杠。引体向上,我是 2021 年就知道自己已经拉不上去了。23 的 BMI 虽然不算有问题,但也不低。因此我就按照儿时的记忆,双手一撑,上了双杠。

本来想的是来一招前滚翻。高中的时候学过,也考过,当时可以轻松完成,要点其实就是肩部要顶住,双臂打开不能收,收就掉下去了。心里想着技术要领,没想到刚一上杠,分腿一坐,大腿内侧拉伤了,赶紧下来。

这伤我后来养了起码一个星期。就是这件事情,让我知道自己已经不是从前那个自己了。虽然这事主要应该怪我没热身,但下课、放学去玩的时候,谁 TM 热身啊?


其实身体更早就开始告警了。37 岁的时候,我尚且能靠节食把体重很快地降下来。过了 40 岁之后再来,但体重怎么也不掉了。吃的还是一样的东西,干的还是相同的活儿,可能还更忙了。只能解释为代谢变了。

身体的零件,也逐渐出了状况。最早也是最明显的是眼睛。虽然医生解释为基因问题,但触发条件还得是年龄。人一老了,就跟车子年限长了一样,各种零部件就开始出状况。一会儿天窗漏水,一会儿烧机油。不知道能用到几时。

这次是脑子,我就很害怕。外婆曾经最害怕阿兹海默,我也一直很担心这种事情。因此从 2021 年末开始,我每天记日记。坚持到现在有四年了,后悔没有早点开始做这件事情。

尽管记的都是流水账,但对我而言是很重要的事情。回头看的时候,发现不少事情已经不记得了。有一些是细节已经模糊,有一些甚至整件事都没印象。回想起以前外婆一遍一遍地反复刷琼瑶的电视剧,这种性质的「失忆」,我能接受,但也很可悲。如果我没有拿文字记录下来,那这些日子就算是白活了?


我记日记,还有一个目的,就是用来证明我目前的记忆不是被「植入」的。

可能觉得我在说科幻小说的事情。但现在科幻小说里面的事情,不是天天都在上演么?先不说那些反乌托邦小说,就单说 AI。谁要是小时候得知有个 AI 能陪你聊天,还知道全人类的知识,那还不得把它一直玩到坏掉?然而现在的人用它来搞色情图片换脸,所以说人类真的没救。

不知道自己接下来工作要干到几时,以及能干到几时。尽管我前面自诩「年轻」,但也知道自己并不真的「年轻」了。不管我自认为多少岁,老板和保险公司都是看在眼里,明白在心里的。驾驭 AI 而不是被 AI 取代,五年内我还有一点信心,五年之后我就有点没底,十年之后更是比较悲观。那个时候我也还没法「退休」,所幸房贷应该已经还完了。

回想起父母当年自己开修理铺修车的经历。一开始也是意气风发,说是下岗也好,下海也好,总之心里不虚。两口子南下去深圳打工,学习技术,回来后自己开店。在 90 年代其实混得不错,但进入 200X 年代就有点力不从心。多年积攒的经验,慢慢开始派不上用场。眼见车内电器的集成度越来越高,最后一切都被封在一块芯片里面,靠另一块芯片来诊断。战场慢慢撤退至出租车,靠着这些老爷车勉强维持生意,最后也是五十多岁就彻底歇业了。

我觉得自己应该没有办法干到真正退休。按共产党的说法,我要 63 岁才能退休。到时候退休金应该早就破产了。不管我现在缴了多高,有没有得拿都是一个大问号。不过我有一个 65 岁前到期的定期寿险,自杀也可以领,有 300 万之多。虽然不知道 300 万到时候值多少钱,但是按照现在的通胀率,应该还是不错。所以……

所以现在这些年干点啥?多看点儿书吧。既然我拿这个当人生目标,而且也有明确的 Deadline 的话,那就应该及早开始了。

2025-11-03

原地复活

玩 IT 的朋友都知道,「自主维修」曾经有三大法宝:拍一下,重启试试,重装系统。

随着集成化程度越来越高,「拍一下」已经不好使了。重装系统一般是菜鸟才会使的招数——你当初怎么弄坏的,重来一次还是会弄坏。重启大法嘛,倒是一直还好使,特别是 Windows 这种东西。Linux 用不太上,但是即使是 iOS 偶尔也还是会需要。

我的 iPhone SE2,今年上半年就已经屏幕不行了(参见《我的手机史(十三)——iPhone SE2》)。我仍然中意有 Touch ID 的手机,然而国行已经买不到 SE3 了,因此不得已去买了一部美版 SE3,把它换了下来。SE2 从此退居二线,却也一直没真正退休。

美版 SE3 也有它的问题。说是无锁,但实际上有 MDM 锁,我算是被坑到了,拿到手一迁移就撞上了。当然因为价格低,也并没有被真正坑到,还借此搞到了一个靠谱的开锁工具。但不能迁移,也就意味着我的「传家宝」Surge 2 没法继续用了,因为原作者已经把它下架了。我若不想投入,就只能在 SE3 上用着免费的 Potatso,时不时就断一下,苦不堪言。

所以我这半年还把 SE2 留着,总觉得 SE3 只是过渡一下的角色,想着接下来买部行货 iPhone 的时候,还是从 SE2 迁移过去。我这两台手机上的 App 几乎一致。除了少数垃圾国产 App,不允许多账号登录,例如支付宝和钉钉,没有办法只能先注销登录,其它 App 我都是两边一起用。数据也靠 iCloud 尽量同步。可以这样说:如果有必要,我只要把 Sim 卡换过来,很快就能转移回 SE2 上继续使用。

其实这样做也有一个副作用,就是我时不时会拿错手机,以及平时裤兜的负重变双倍。不过上个星期,我的这份「坚持」,终于有了回报。2025 年 10 月 26 日,刚好是 SE2 过完五岁生日的 10 天之后,它的屏幕问题,突然自己就好了。

本来已经没抱什么希望了。一直想着去把屏幕换掉,顺便也换个电池。但又怕维修的时候非要我还原系统,所以一直没去搞。就这样拖着,结果 26 日洗澡的时候,我突然发现全键盘的时候 p 键可以按到了。

图片由 Google Gemini 生成

我直到现在也不知道到底是怎么回事。是中校同志搞的鬼吗?是气温原因?或者是因为我当时把它带进了浴室里面用?到现在一个星期了,它仍然是好的,并不是回光返照。所以我又把 Sim 卡换了回来,主力手机回到 SE2 上,SE3 就先跟它交换身份。

不知道这台 SE2 还能撑多久。电池的确不太行了,峰值电量只有 76% 了,需要我多加注意,但跟半年前比也没什么变化。话说近年推出的新款 iPhone 的电池貌似的确要好很多,或许是硬件上的改进,但我疑心是新版 iOS 的功劳。我 SE3 用了半年,峰值电量还是 100%。本以为是障眼法,但太太 21 年买的 iPhone 12 Pro Max 到现在四年半了,也还有 86%。所以 Apple 可能真的是想了点什么办法,只可惜我的 SE2 没早点用上这黑科技。

这件事给我的一个「启迪」就是:不少看上去已经可以「盖棺定论」了的事情,可能还有转圜的余地。所以,各位也请不要灰心,继续坚持,说不定再坚持一下可能就好了。

2025-10-29

连滚带爬

2025 年 10 月 28 日晚,摄于下班路上

我最接近「连滚带爬」的时候,是一次从上海去北京出差。

低估了高峰期上海地铁 10 号线的装载能力,也低估了从地上跑到地下换乘所需的时间,更是大大地低估了上海虹桥高铁站的宏大规模。
我以为地铁到站还剩十五分钟,应该来得及。接着我就开始连滚带爬了。

长长的扶梯啊,仿佛没有尽头。背着个大包的我,怀揣脂肪肝,此时已经上气接不了下气。自动扶梯的台阶偏偏又特别高,可我的大腿已经几乎抬不起来了。
愧对家乡父老!比起郑智化来,我是真的靠手脚并用爬上去的。滚是没有真的「滚」,我可不想真的糗「死」了。

还好公司提前给我拿到了票。当年持纸质票更容易「通关」。我见过有人拿着身份证去,被人赶去取票,最后没赶上火车。
当然,那不是在上海,上海还是要好些,毕竟是后来靠「封城」真的「清零」过的中国「天花板」城市。


我最早见到郑智化的样子,是在香港的「卫视中文台」。
那个时候,我大概跟我儿子现在差不多年纪。内地电视台,那个时候就已经江河日下了。后来湖南卫视勉强算「中兴」,但我早已不再看。

开眼看世界,随即惊讶于「人家」的广告居然如此「有意思」,以及节目播出是如此准时。从来不延误,偶尔提前几秒无事可做,宁可播一个时钟的画面,也不再多插一个广告。
相比之下,内地电视台的广告多得让人想骂娘,还没意思。本地的录像台倒是更守时一些。

除了卫视中文台的主台,还有音乐台,时不时播点 MTV。有一次甚至还播了张学友 92 年演唱会,分了上下半场,我用 VHS 完整录了下来。还记得从《花花公子》开始的关之琳的伴舞。下半场上来第一首歌就是《夕阳醉了》,萨克斯的音色也把我吹得如痴如醉。这次 60+ 演唱会,很可惜没法有昔日的任何感觉,遗憾。

郑智化的歌,当时正当红的是《星星点灯》。《水手》是上一波,MTV偶尔也有播出。另外让我印象深刻的,就是《麻花辫子》。

其实《星星点灯》和《水手》的路数很相似,用现在的话来说,Vector 离得比较近。我还记得在《重庆晚报》上看到过一小块「豆腐干」,评论说《星星点灯》缺乏创意,不过是《水手》的翻版,郑智化江郎才尽云云。看到这些文字的时候,我还没有概念,后来真的听了这两首歌,倒也时常把旋律搞混。

《重庆晚报》上的音乐评论,也有挺不靠谱的。我曾经看到过另一块「豆腐干」,抨击《心雨》的「资产阶级爱情观」,说明天就要「成为别人的新娘」,今天还要「最后一次想你」,简直是伤风败俗。那个时候我还只是小学生,看了也只能暗暗记在心里,不足为外人道。

在《星星点灯》和《水手》里面,郑智化的「残疾人」感觉还不明显。不过《麻花辫子》里面就比较明显,拄着拐杖。我跟着哼了几句,结果被外婆向父母打小报告,说我有「早恋」的苗头。可班上也没有麻花辫的女同学啊?

其实中国大陆也有一个还算出名的残疾人歌手,还上过春晚。1987 年春晚,不是现在网上搜到的那个 87 年生的小伙子。郑智化是腿不好使,他是腿没了。这次若是换成是他,工作人员可能会「轻松」一点吧,各种意义上。

当然,在网络酸民眼里,这些都不是事。重要的是中国南波万,以及必须赢两次。


这次十一回了一趟重庆。跟父亲吃了两次饭。
他总是劝我,去试试看办一张「残疾证」。这次又被我拒绝了。

他可能觉得,自己靠着残疾人身份,得了不少好处。坐公交车自不必说,穷游「大好河山」也省了不少门票钱。
另外一个理由就是,我姑父靠着股骨头坏死的残疾证明,提前了几年退休了。

我让他省省吧,还提前退休呢。太太的闺蜜得了癌症都没法提前退休。今时不同往日了,我甚至都没指望过自己还能拿到养老保险。
但是冲他吼了半天,嗓子都哑了,他反正也听不见,自顾自地说自己的。

算了,反正火锅店里面也吵得很。外面跳广场舞也吵。吵死拉倒。

2025-10-24

程序员节,闻逆流有感

「惊闻」《四中全会决定大幅提高科技自立》,感觉到大概率又要搞一波运动了。

学大寨?放卫星?大炼钢铁?超英赶美?
不知道又有多少人借此机会大发「国难财」,不知道有多少从我这里上缴的税费溜进了这些投机者的口袋。

作为深受「信创」其害的 IT 从业人员,退休之心不由得更加迫切了。躲进小屋成一统,管它冬夏与春秋。反正种子我已经播下去了。

若要问我有什么想说的:如果《中科院反右中消失的一页——寻找青年物理研究者刘治平》这种事情不能得到真正的解决,包括那虽然幼稚可笑但起码是个态度的「平反」,以及彻底的清算和至少两代人以上的反思,那么所谓「科技自立」,只不过是镜中花、水中月,南柯一梦耳。

以及,以上只是必要条件,而非充分条件。听说现在义务教育不教「逻辑」,有不明白的请自行弯腰摸石头。

图片来自《中国数字时代》,阿平漫画


加油 2025

图片由 ChatGPT 生成,[惊喜]它能正确生成含中文的图片了

根据 Blogger 的统计,2008 年,我写了 59 篇Blog。
今年我已经写了 52 篇了,加上这篇,是 53 篇。

该用什么词汇来形容呢?中兴之年?垂死病中惊坐起?啊呸!

去年重新开始在 Blogger 上写 Blog 之后,写了 31 篇,我已经很惊奇了。已经算是 2008 年以来的新高。今年再加把油,努把力,或许可以超过 2008 年的数目。

神马?2007 年有 110 篇?那个时候真的是话痨,现在不敢想了。2006 年的部分,还在整理中,数目并未确定,不知道是会更多还是更少。

这篇 Blog,本来应该等年底总结的时候,再来说这些话。不过我也想给自己打个气。2025 年还剩下至少两个月,7 篇 Blog,只要我不要太懒,也不要遇到什么事情,应该能够做得到。

把话先摆在这里,也算是一个鞭策,回头再来打自己的脸的时候,就会更疼一些。

以上。

2025-10-23

开设 Substack

图片来自网络

在 Medium 上也有写东西,有一些也发在了 Blogger 上。除了简 / 繁体,与 Blogger 这里最主要的区别,就是多一份《上海日记》。

一度觉得 Medium 上的书写感觉很不错,简单、纯粹。因此尽管它对中文内容诸多「打压」,我还是一直坚持。我并不介意推广的事情,反正我又不打算靠这个赚钱。

直到 Medium 开始把邮件推送从提供正文全部内容,改为只给出前半截,然后引流到 Web 站点去。那个「Continue reading」,搞得我火大。

我知道 Medium 不是 Substack,商业模式不一样。如果大家都在 Mail 里面去阅读,没人访问 WebSite,那它就没钱赚了?不清楚。Medium 的商业模式一直改来改去,我也看不明白。
但起码它现在想的是要把人拉回去,于是把内容藏起来不让邮件订阅者看。这与我想让别人「通过邮件看全文」的需求,是背道而驰的。

于是,我在 Substack 上开了 Publication。

Substack 给我的书写感觉也挺不错的,起码我写个日记希望有的那些功能它都具备。而且中文作者在上面好像要稍微更活跃一些,推介算法也更友好,至少我还能在 Home 上发现一些活人。

新的 Publication 并无意推广,随缘吧。毕竟名人有云:「知道得少一点,可以活得久一点」。
话说,其实我写本文,也只是想吐槽了一下 Medium 而已。

2025-10-22

亲历 AWS 网络大故障

周一下午,我刚完成从 Medium 搬运到 Substack 的第一篇文章,正要去看效果,就发现 Substack 的网页时常报错,很难打开了。

一开始还只是某些访问有问题,多刷几次能出来。后来就渐渐地总是刷不出来了。正在疑惑是怎么回事,转去看 Medium,发现 Medium 也开始不稳定起来了。报错代码是 504,Cloudflare 报的。

本以为是 GFW 的原因,但报错代码是 50X,有时是 503。这是后端服务器有问题的错误代码,看起来跟 GFW 没关系。而且 504 是Gateway Timeout,GFW 显然不可能干扰 Cloudflare 的回源。我开始认识到这次可能是有什么 Internet 基础设施故障了。不知道这两家原本是竞争对手的公司,基础设施怎么会搞到一块儿去的?比较大概率是 AWS,因为 Google 的服务很稳定,微软也没出问题。

我用 Google 搜了一下,貌似还没什么新闻。Reddit 上有少量用户在各自的专区反映 Substack 和 Medium 出了问题,从印度和葡萄牙的访问都有问题,美国本土倒是好像没人说。我登录了 Reddit 账号,也上去写了两句,接着等消息。

图片来自网络

没过多久,BBC 有动静了,报道说 AWS 出了事故。据说是美东一区的 DynamoDB 访问出现报错和延误。我自己其实也从 AWS 的 Status 页面上刷到了这条消息。我意识到自己可能正在经历一次全球性的 IT 基础设施故障。有 Reddit 用户说 Trello 和 Hulu 也在波及范围,我上 Trello 看了一眼,好像还没事。

再后来没多久,Reddit 上也有人在贴这个新闻。从 BBC 的报道来看,影响面挺大的,一些网游和银行都受到了波及。英国那边有点气噗噗,觉得凭什么美国佬儿的故障要影响到我们 Great Britain。最后的阶段,连 Reddit 也开始访问不稳定了。

可气的是,自始至终,无论是 Substack 还是 Medium,他们自己的 Status 页面上一直都是 OK 的。这样的页面看来只是一个摆设。

不过恢复也挺快,AWS 更新了 Status 说已经定位到了原因之后,不到半小时,访问就纷纷恢复了。下班前我试了一下 Substack 和 Medium,二者的服务都已经正常了。

2025-10-17

Chrome 与黑魔法师

我曾经在 Chrome 的 123 版本上停留了很长一段时间。

为什么坚持用 Chrome 的老版本?
因为如果升级到 >=124 的版本,我用的 Shadowsocks 就会出问题。时不时就卡住一两分钟。完整关掉 Chrome 重新打开,可以立即恢复,所以并不是被封锁了。但遇到的频率很高,总不能一直这样关掉整个 Chrome,所以我就不去升级了。

不去升级,Google 会自己给我升。有一次我一个疏忽,儿子不知道干了什么事情,就把版本升到了 137。我一边回退版本,一边研究如何禁止 Chrome 自动升级,后来在这方面也算是小有心得。

前不久,我发现这次是必须升级了。因为如果不升级,Google 就不让我用 Gemini 了。网页上元素出来不全。查了一下,应该是因为 123 版本的 Chrome 不支持 ch-ua-form-factors。这事让我焦虑了好一段日子,最终还是下决心动手了。
回想起我的 iPhone,当初「被迫」升级到 iOS 16,也是因为如果不升级就不让我用 ChatGPT。AI 真的是人类「进步」的第一大推动力。


我也曾经在 Shadowsocks-libev 3.3.5上停留了很长一段时间,比 Chrome 123 还久。

可不是因为怀旧。尽管我的确一直秉承着「东西还能用就不要去动」的理念,但作为一个从事软件开发的技术人员,跟大势如此脱节并不是什么好事情。我也心知肚明,因此 gfwreport 我都有认真看。现在技术路线是五花八门,乱花渐欲迷人眼,但食死徒对 TLS 盯得很紧,QUIC 也是风口浪尖。我这抱残守缺的做法,倒也能偏安一隅。

这次铁了心要搞个清楚,到底是 Chrome 124 的 X25519Kyber768 搞出了问题,还是伏地魔又玩出了什么鬼花样?

留意到一个现象:用新版的 Chrome 访问 HTTPS 站点,10 秒之后就会准时有 Replay Attack 报到。换成 123 版本就没有问题。还没搞明白 Replay Attack 与我遇到的现象具体有什么因果关系,但二者有关联是肯定的。

或许 X25519Kyber768 导致 TCP 流出现了特别的头部特征?我记得 Chrome 124 刚上线的时候还闹出风波,就是 Client Hello 导致了问题。从我并没被封看来,对方也拿不准,起码没有得出任何结果。但或许跟 AEAD 的抗重放机制一相互作用,就出了问题?个人能力不足,难以最终搞清楚,我不打算继续研究了。


这次还是用「土办法」去解决了。没去换技术路线,只是想办法把流量特征藏了起来。可能也是个小众的做法,但在第五、六集这种困难时期,隐身衣可是好东西。

图片由 Google Gemini 生成

解决方法就不在这里细说了。法师的名字要是被对手知道了,那还得了!