惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Attack and Defense Labs
Attack and Defense Labs
S
Security Affairs
P
Palo Alto Networks Blog
Webroot Blog
Webroot Blog
P
Privacy International News Feed
H
Hacker News: Front Page
www.infosecurity-magazine.com
www.infosecurity-magazine.com
C
Cybersecurity and Infrastructure Security Agency CISA
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
The Blog of Author Tim Ferriss
T
Threat Research - Cisco Blogs
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Spread Privacy
Spread Privacy
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Jina AI
Jina AI
Simon Willison's Weblog
Simon Willison's Weblog
Scott Helme
Scott Helme
A
About on SuperTechFans
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recorded Future
Recorded Future
F
Fortinet All Blogs
PCI Perspectives
PCI Perspectives
Google DeepMind News
Google DeepMind News
T
Troy Hunt's Blog
MyScale Blog
MyScale Blog
I
InfoQ
F
Full Disclosure
宝玉的分享
宝玉的分享
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Security Archives - TechRepublic
Security Archives - TechRepublic
Latest news
Latest news
W
WeLiveSecurity
月光博客
月光博客
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
N
Netflix TechBlog - Medium
量子位
P
Proofpoint News Feed
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
GbyAI
GbyAI
SecWiki News
SecWiki News
AI
AI
The Last Watchdog
The Last Watchdog
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
GRAHAM CLULEY
N
News and Events Feed by Topic
K
Kaspersky official blog
V2EX - 技术
V2EX - 技术

Yesterday17's Blog

2026 新年解密红包 / Melody Flag | Yesterday17's Blog 谈谈 Iori 的设计思路(二):如何实现一个 Showroom 录制工具? | Yesterday17's Blog 谈谈 Iori 的设计思路(一):从 Nico Timeshift 说起 | Yesterday17's Blog Iori Minyami 0.1.0 发布 | Yesterday17's Blog 2025 新年解密红包 / Melody Flag | Yesterday17's Blog 使用 Cloudflare Warp 解决罗森票务的海外登录问题 | Yesterday17's Blog How To Blog 04: The Astro v5 Era | Yesterday17's Blog 谈谈 tokio::select! 的公平性 | Yesterday17's Blog Learning Pingora 05 - Connect with TLS | Yesterday17's Blog Leaving Bytedance | Yesterday17's Blog 大橋彩香 AsiaTour「Reflection」上海公演 个人向记录 & Repo | Yesterday17's Blog Recoving from burnout - What happened? | Yesterday17 Yubikey 重建手册 | Yesterday17's Blog How To Blog 03: Heimus | Yesterday17's Blog 🪧 Blog Migration Accouncement | Yesterday17's Blog Learn Your IDE - VSCode 是如何仅重启插件的? | Yesterday17's Blog How To Blog 02: Astro❤️Password | Yesterday17's Blog How To Blog 01: Why, How, and the Future | Yesterday17's Blog Learning Pingora 04 - Establish L4 Connection | Yesterday17's Blog Learning Pingora 03 - Upstreams and Peers | Yesterday17's Blog Learning Pingora 02 - A Simple HTTP Server | Yesterday17's Blog Learning Pingora 01 - Getting Started | Yesterday17's Blog 2024 新年解密红包 / Melody Flag | Yesterday17's Blog 向新的一年飞驰——记录 2023 | Yesterday17's Blog 「サクラノ刻」对话选摘(2) | Yesterday17's Blog PGP Key Revocation 注销声明 | Yesterday17's Blog 「サクラノ刻」对话选摘(1) | Yesterday17's Blog 2023 新年解密红包 / Melody Flag | Yesterday17's Blog 『蒼の彼方のフォーリズム』通关感想 | Yesterday17's Blog 单显卡直通教程 | Yesterday17's Blog 对博客与笔记的思考 | Yesterday17's Blog Project Anni 之旅(3)自动化 Flutter 应用 CI/CD 上架流程 | Yesterday17's Blog AsobiStage 直接播放链接 | Yesterday17 如何在后分P时代进行投稿——sswa使用详解 | Yesterday17's Blog JSON RPC 与 LSP 协议基础 | Yesterday17's Blog Grajapa Shueisha / BookEnd 加密方式调查 | Yesterday17's Blog 【2022篇+WriteUp】如何再收一个新年红包? | Yesterday17's Blog 如何将良心云的良心功能清理干净 | Yesterday17's Blog 【油猴脚本】bilibili 投稿页面返回旧版+旧版页面强制允许分P上传 | Yesterday17's Blog Cloudr1v1 授权方式分析 | Yesterday17 Typora 1.0.2 逆向实录 | Yesterday17's Blog Project Anni 之旅(2)ValueAfterTable——toml-rs的实现与限制 | Yesterday17's Blog IPv4透明代理+IPv6 Passthrough——树莓派单臂软路由折腾记 | Yesterday17's Blog Chaos; Child 汉化补丁 神秘编码探索 | Yesterday17's Blog 镣铐与舞蹈——个性与共性之迷思 | Yesterday17's Blog Go 学习笔记 02 - 找准 io 之道 | Yesterday17's Blog NAT Slipstreaming v1 原理浅析 | Yesterday17's Blog 绕过「9-nine-」的 CDKEY 验证——KrkrPlugin 正(?)向实录 | Yesterday17's Blog 静流的青春纪念册——「サクラノ刻 -櫻の森の下を歩む-」体验版感言 | Yesterday17's Blog Project Anni 之旅 01 - 从 clap-builder 到 derive | Yesterday17's Blog [Google CTF 2021] CPP WriteUp | Yesterday17's Blog 获取 アソビステージ 的实际播放链接 | Yesterday17's Blog 90 行 Rust 代码实现 AsyncTeeReader | Yesterday17's Blog 或许还算有价值一读的文章列表 | Yesterday17's Blog 从零开始的 Seedbox 之旅 | Yesterday17's Blog [随笔]技术型博客行文迷思(1) | Yesterday17's Blog 浅谈 git fetch 的工作方式 | Yesterday17's Blog 『ソーサレス*アライヴ! ~the World's End Fallen Star~』通关感想" | Yesterday17's Blog Rust std::fmt 格式语法简述 | Yesterday17's Blog 日亚修改居住国的解决方案 | Yesterday17's Blog [Windows/Linux] GC553 的 Switch 完美采集之路 | Yesterday17's Blog 【翻译】Subtyping and Variance / 子类型与变型 | Yesterday17's Blog Berd's Red Envelope 2021 WriteUp | Yesterday17's Blog 【中英对照】ALSA 音频 API 使用教程/A Tutorial on Using the ALSA Audio API | Yesterday17's Blog 从 cue_scanner.l 看 CUE Sheet 的词法单元 | Yesterday17's Blog Postman 历史记录导出的解决方案 | Yesterday17's Blog 《恋爱绮谭 不存在的夏天》通关感想 | Yesterday17's Blog [微机实验/TD-PITE] 微机接口综合实验 | Yesterday17's Blog [微机实验/TD-PITE] 键盘扫描及数码管显示实验 | Yesterday17's Blog [微机实验/TD-PITE] 数码管显示实验 | Yesterday17's Blog Airsonic Advanced+Google Drive+Caddy 部署纪实 | Yesterday17's Blog X-NUCA 2020 - hellowasm 题解 | Yesterday17's Blog [微机实验/TD-PITE] 8251 串行接口实验 | Yesterday17's Blog Node.js child_process.fork 与 env 污染 RCE | Yesterday17's Blog EP.01 「夜の向日葵」 | Yesterday17's Blog [微机实验/TD-PITE] 8254 定时/计数器实验+选做实验 | Yesterday17's Blog [JLU CTF/2020] babywasm WriteUp | Yesterday17's Blog PHP 反序列化与经典利用 | Yesterday17's Blog WebAssembly 逆向简述 | Yesterday17's Blog 『彼女、お借りします』一期完结点评 | Yesterday17's Blog [微机实验/TD-PITE] D/A 转换实验+选做实验 | Yesterday17's Blog [微机实验/TD-PITE] A/D 转换实验+选做实验 | Yesterday17's Blog 开源项目申请 JetBrains Open Source License 简单流程 | Yesterday17's Blog 微软拼音与 JetBrains 搜索快捷键冲突的解决方案 | Yesterday17's Blog [微机实验/TD-PITE] 8259 中断优先级实验+选做实验 | Yesterday17's Blog IFTTT 测试(续) | Yesterday17 IFTTT 测试 | Yesterday17's Blog [微机实验/TD-PITE] 存储器扩展实验+选做实验 | Yesterday17's Blog 新版 GCC 针对 -fdump-translation-unit 的替代方案 | Yesterday17's Blog 一次 HSTS 策略配置的排错之旅 | Yesterday17's Blog 我是我自己——论获取 HTTPS 证书时的验证步骤 | Yesterday17's Blog 【设计文档】对 PUG 的大规模设计修订(1.1) | Yesterday17's Blog GS65 折腾记(2)加装固态,分区,Grub2 引导 Manjaro LiveCD | Yesterday17's Blog 「さくら、もゆ。」的空白字体列表——一次逆向问题定位过程实录 | Yesterday17's Blog GSuite 探索篇(1)使用 Service Account 向 Google Drive 传输文件 | Yesterday17's Blog 『サクラノ詩 -櫻の森の上を舞う-』通关感想 | Yesterday17's Blog 《ATRI -My Dear Moments-》通关感想 | Yesterday17's Blog [工具][VSCode 扩展] AegiKit——方便 Aegisub 使用的工具箱 | Yesterday17's Blog 贝塞尔曲线、字体矢量化与曲线运算 | Yesterday17's Blog NAT 类型初探 | Yesterday17's Blog
YukiNative 踩坑记——Windows 的消息队列 | Yesterday17's Blog
Yesterday17 · 2020-09-02 · via Yesterday17's Blog

最近在实现 YukiNative,也算是顺风顺水虽然一直在现学 C#。对于 YUKI 中大部分平台依赖的代码都解决地差不多了。但是到了我自己加的某一个功能时,却出了大麻烦……

ToC

  • 简述
  • YukiNative 简介
  • 控制台程序与图形界面应用
  • 消息队列
  • 消息循环
  • 于是……
  • 解决
  • 结语
  • 鸣谢

简述

功能本身说起来很简单:让 YUKI 跟随游戏窗口同步最大化/最小化。但仔细想想就会发现这其实是一个很平台依赖的功能需求。在 Node 中,我是这么实现的:

import * as ffi from 'ffi'

const user32 = ffi.Library('user32.dll', {

SetWinEventHook: ['uint32', ['uint32', 'uint32', 'uint32', 'pointer', 'uint32', 'uint32', 'uint32']],

UnhookWinEvent: ['bool', ['uint32']]

})

const EVENT_SYSTEM_MINIMIZESTART = 0x0016

const EVENT_SYSTEM_MINIMIZEEND = 0x0017

export function registerWindowMinimizeStartCallback(

handle: number,

callback: () => void

): boolean {

return doRegisterEventHook(EVENT_SYSTEM_MINIMIZESTART, handle, callback)

}

export function registerWindowMinimizeEndCallback(

handle: number,

callback: () => void

): boolean {

return doRegisterEventHook(EVENT_SYSTEM_MINIMIZEEND, handle, callback)

}

function doRegisterEventHook(

event: number,

handle: number,

callback: () => void

): boolean {

const eventProc = ffi.Callback('void',

[ffi.types.ulong, ffi.types.ulong, ffi.types.int32, ffi.types.long, ffi.types.long, ffi.types.ulong, ffi.types.ulong],

(hook: number, event: number, hwnd: number, obj: number, child: number, thread: number, time: number) => {

callback()

})

const num = user32.SetWinEventHook(event, event, 0, eventProc, handle, 0, 0)

process.on('exit', () => {

if (num !== 0) {

user32.UnhookWinEvent(num);

}

eventProc;

})

return num !== 0

}

其实这里也藏着一个坑,就是 process.on('exit') 的地方。为了防止回调在被调用之前就被 GC,我们需要额外引用一下 callback

言归正传。不难发现,要做到这个功能,其中一种方法就是通过 SetWinEventHook,也就是上文中用到的方法,配合回调函数实现。在 Node 下,这种方法可谓是开箱即用,没有耗费我太多的时间。纵使对 Windows API 一窍不通,也不算特别困难。因此当我移植到这一步时,我满心以为这项工作很快就能结束。

然而事实证明,我错了。完成这项移植,我们需要稍微了解一些偏底层的知识。虽然只是一点,但需要就是需要,逃不过的。

YukiNative 简介

在说明之前首先来简单介绍一下 YukiNative 的存在。[YukiNative](https://github.com/Yesterday17/YukiNative) 是为了替代 YUKI 中平台依赖部分而独立出来的纯 Windows 应用程序,其目标是替代目前 YUKI 中所有和 Windows (偏)底层打交道的部分,包括 DLL 调用、进程(退出)状态监听、以及我加的窗口检测。

YukiNative 使用了 .NET Framework 4.7.2,但理论上也可以在 .NET Core 2.0 下编译。

控制台程序与图形界面应用

众所周知,在 Windows 下,你可以通过钩子捕获/修改很多东西。这里我们常常会忽视一个概念:钩子是针对 GUI 程序而言的。

想象没有窗口用户界面的时代,那时候只有控制台,但是——如果用窗口的概念去理解的话,也可以说是只有一个窗口。并且当时的事件也很少:键盘事件恐怕是唯一常用的存在了。

然而到了现在,窗口的出现带来了大量的新概念,包括窗口状态、窗口位置等,随之而来的也就出现了大量的事件。这些事件通过消息的形式传递出去,并最终落入需要的窗口手中。

发现了吗?其中没有控制台程序的位置

原因说起来也简单,控制台程序是另一种程序形式。它们没有图形界面(或许有字符界面),与我们今天见到的那些应用们格格不入。

同样如此,所以图形界面那一套默认是没有带到控制台应用中去的。

消息队列

为了应对图形界面带来的这么多消息,消息队列就出现了。

消息队列是针对每一个线程而言的(因为一个线程可能就对应着一个窗口),负责存放线程需要处理的消息。

我们可以通过 GetMessagePeekMessage 来获取消息队列中的消息。唯一的区别就在于前者阻塞,而后者非阻塞

消息循环

通常的图形界面应用会通过消息循环Message Loop)的方式读取消息队列中的内容。也就是一个大循环,里面用 GetMessage 或者 PostMessage 读取消息,并且通过 TranslateMessageDispatchMessage 进行处理。

于是……

还记得上面的 SetWinEventHook 吗?我们现在知道了事件的传递是通过消息进行的,那么我们想要的最大/最小化事件也就一定蕴含在了事件之中。SetWinEventHook 帮我们声明了需要的事件,Windows 将事件对应的消息送到了线程的消息队列中,而我们却一直没有去取,导致了没有注册成功的假象

这种假象是很致命的,和带有垃圾回收的语言结合起来尤甚。你会怀疑出问题的地方究竟在哪里,从而忽视了事情的本质。

解决

成功定位问题之后,解决起来就简单多了。主要部分如下:

public static class MessageLoop {

private static readonly Queue<Tuple<Events, uint, Action<int>>> Tasks =

new Queue<Tuple<Events, uint, Action<int>>>();

public static TaskCompletionSource<int> AddHook(Events @event, uint pid) {

var promise = new TaskCompletionSource<int>();

Tasks.Enqueue(new Tuple<Events, uint, Action<int>>(@event, pid, i => promise.TrySetResult(i)));

return promise;

}

public static void Run() {

while (true) {

if (PeekMessage(out var msg, 0, 0, 0, 1)) {

Console.WriteLine(msg);

if (msg.Message == WmQuit)

break;

TranslateMessage(ref msg);

DispatchMessage(ref msg);

}

else if (Tasks.Count > 0) {

while (Tasks.Count > 0) {

var task = Tasks.Dequeue();

var hook = SetWinEventHook(

task.Item1,

task.Item1,

IntPtr.Zero,

WinEventCallback,

task.Item2,

0,

0);

task.Item3(hook);

}

}

Thread.Sleep(0);

}

}

const uint WmQuit = 0x0012;

[StructLayout(LayoutKind.Sequential)]

private struct MSG {

IntPtr Hwnd;

public uint Message;

IntPtr WParam;

IntPtr LParam;

uint Time;

POINT Point;

}

[StructLayout(LayoutKind.Sequential)]

private struct POINT {

long x;

long y;

}

[DllImport("user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]

private static extern bool GetMessage(ref MSG msg, int hWnd, uint wMsgFilterMin, uint wMsgFilterMax);

[DllImport("user32.dll")]

private static extern bool PeekMessage(out MSG msg, int hWnd, uint wMsgFilterMin, uint wMsgFilterMax,

uint wRemoveMsg);

[DllImport("user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]

private static extern bool TranslateMessage(ref MSG msg);

[DllImport("user32.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]

private static extern IntPtr DispatchMessage(ref MSG msg);

[DllImport("user32.dll", SetLastError = true)]

private static extern int SetWinEventHook(Events eventMin, Events eventMax, IntPtr hmodWinEventProc,

WinEventProc lpfnWinEventProc, uint idProcess, uint idThread, uint dwflags);

[DllImport("user32.dll", SetLastError = true)]

private static extern int UnhookWinEvent(int hWinEventHook);

}

}

完整代码位于 https://github.com/Yesterday17/YukiNative/blob/master/YukiNative/services/Win32.cs

结语

撞坑撞了一晚上的存在吃了没文化的亏。从完全不知道消息队列这个东西到渐渐明白其中的内容,度过了一段非常有意义的时光(笑。

上面反复加粗线程,其实这里有个说得不是特别详细的地方:不同线程的消息队列不同。而由于 C#TaskThread 的语法糖,因此这种类似 Promise 的东西你也是没法用的。你只能乖乖地把所有东西都放到一个线程里,也就是上面代码里 AddHook 的诞生原因了。

在整个从无到有的过程中,下面的这些文章给了我很大帮助,故在文末列出(排名代表顺序)。

鸣谢

  1. https://gist.github.com/fjl/4080259
  2. https://stackoverflow.com/questions/15849564/how-to-use-winapi-setwineventhook-in-python
  3. https://docs.microsoft.com/en-us/windows/win32/learnwin32/window-messages