资讯动态

TextOut与wsprintf组合实战:Win32客户区动态文本绘制全攻略

发布时间:2026/10/17 8:16:28 来源:尧图企业网站定制
1. 先搞懂这套组合拳解决什么问题做 Win32 桌面开发的朋友尤其是刚啃完《Windows 程序设计》前几章的人一定绕不开一个场景你想在窗口客户区里“画”点东西但又不只是画个固定的字符串而是想显示当前鼠标坐标、计时器秒数、某种计数器的实时数值。这时候只靠 TextOut 是不够的因为 TextOut 的第三个参数要的是一个现成的字符串指针你没法直接把 int 丢给它。于是 wsprintf 就派上用场了先把变量“翻译”成文本再交给 TextOut 上屏。这套组合在 Win32 编程里极其常见几乎算得上是入门级“动态文本显示”的标准答案。我当年自己写第一个画图工具时想在状态栏和客户区角落显示鼠标坐标翻遍了手头的教程最后发现核心就是两个 API 的配合wsprintf 负责格式化TextOut 负责绘制。搞清楚这两个函数各自的脾气、参数的坑、以及它们和 WM_PAINT 消息的协作关系后面再学 GDI 文本输出、自定义控件绘制都会顺畅很多。这篇文章适合谁看正在学 Win32 编程但卡在文字输出这块的人或者已经会用 MessageBox 显示文字、但想在窗口客户区做更灵活输出的人。我会把 TextOut 的每个参数、wsprintf 的格式化规则、两者配合的完整案例以及我踩过的内存和刷新相关的坑都拆开讲一遍。看完你不仅能跑通一个动态文本显示的程序还能举一反三地应付“在窗口上画各种运行时信息”的需求。2. TextOut 不是“打印文字”而是“画文字”2.1 TextOut 的完整签名与参数解读先看 TextOut 的函数签名BOOL TextOut( HDC hdc, // 设备环境句柄 int x, // 起始 x 坐标逻辑坐标 int y, // 起始 y 坐标逻辑坐标 LPCTSTR lpString, // 要绘制的字符串 int cchString // 字符串的字符数 );很多新手第一次看到这五个参数都懵尤其是最后一个 cchString它不是“字符串的字节数”而是“字符数”。在 ANSI 编码下一个英文字符占一个字节所以字符数和字节数一致但如果你编译成了 Unicode 版本Visual Studio 默认就是 Unicode一个汉字占 2 个字节却是 1 个字符这时候要格外小心传入字节数等于把一个中文字符拦腰截断显示出来就是乱码或半个矩形。实践中我一般用_tcslen(lpString)来获取长度它能根据工程是否定义了_UNICODE自动选择宽字符版本省心。也可以直接传-1告诉 TextOut 字符串是 NULL 结尾的让它自己数长度。不过传-1有一个隐蔽问题如果你的字符串缓冲区中间某个位置意外出现了 0TextOut 就会提前截断。所以稳妥做法还是显式传_tcslen的结果。hdc 这个参数是设备环境句柄相当于“画板”的凭据。它不会凭空出现通常通过 BeginPaint 或 GetDC 获得。TextOut 画出来的内容不是持久保存的窗口被遮挡后再露出来就会丢失必须依赖 WM_PAINT 消息重绘。你要是把 TextOut 放在 WM_PAINT 之外的地方调用往往会出现“画了但马上消失”的现象后面我会详细说。2.2 逻辑坐标、字体与颜色决定文本“画”在哪、长什么样TextOut 的 x、y 坐标是基于当前映射模式的逻辑坐标。默认的 MM_TEXT 模式下坐标原点在客户区左上角x 向右增大y 向下增大单位是像素。所以你想把文字放在客户区左上角就写TextOut(hdc, 10, 10, ...)人和代码都能凭直觉理解。但“文字画出来什么样”不完全由 TextOut 决定而是由当前选入 HDC 的字体决定。Win32 的 GDI 文本绘制是“画笔选字、画布上墨”的逻辑你创建一个字体对象SelectObject 到 HDC 里后续所有的 TextOut 都会用这个字体渲染。不想自己创建字体的话系统会给你一个默认的“系统字体”通常长得很素字号也偏小。如果你想让显示更清晰或者字号更大就得自己动手HFONT hFont CreateFont( -24, // 高度负值表示根据字符高度计算正负容易搞混 0, 0, 0, // 宽度、角度、角度 FW_NORMAL, // 字重 FALSE, FALSE, FALSE, // 斜体、下划线、删除线 DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T(微软雅黑) // 字体名称 ); SelectObject(hdc, hFont); // 画完记得 SelectObject 还原然后 DeleteObject 释放颜色方面文本颜色默认是黑色。想改成其他颜色用SetTextColor(hdc, RGB(200, 30, 30))这个设置会一直保留在 HDC 里直到你再次修改或者 HDC 被释放。背景颜色用SetBkMode(hdc, TRANSPARENT)设成透明模式否则 TextOut 绘制时会在文字背后画一个矩形背景块颜色默认为白色叠在其他图形上会很难看。有一次我画自绘控件时没把背景模式设成透明文字底下始终顶着一块白底怎么调都不对最后才发现是SetBkMode没调用。这个函数就像一键开关需要记牢。2.3 为什么 TextOut 必须放在 WM_PAINT 里这是新手最容易犯的“逻辑正确但行为诡异”的错误。你把 TextOut 写在窗口过程的某个消息分支里比如 WM_CREATE跑起来发现文字确实出现了但一拖动窗口、一遮挡再恢复文字就没了。原因是 GDI 绘图不具备持久性。窗口的客户区内容本质上不是被系统“记住”的系统只记住了一个概念这部分区域需要重绘。真正的内容是你的程序在收到 WM_PAINT 时现画出来的。操作系统也没法为你保存 TextOut 画出来的像素 —— 如果它能的话内存早就爆了。所以正规做法是把 TextOut 放在 WM_PAINT 分支里用 BeginPaint 拿到 HDC画完用 EndPaint 释放。BeginPaint 会返回“需要重绘的区域”信息在你画之前它还会自动擦掉背景如果你设置了窗口类的背景画刷然后画完之后验证区域告诉系统“这块我已经画好了”。case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hWnd, ps); // 在这里调用 TextOut TextOut(hdc, 20, 20, _T(Hello, Win32), 13); EndPaint(hWnd, ps); return 0; }大到整个窗口需要重绘小到一块 50x50 的矩形区域失效系统都会发 WM_PAINT你的任务就是把这块区域重新画好。这也是为什么“动态刷新文本”的正确做法不是用定时器一直在 WM_TIMER 里调 TextOut而是让那块区域失效然后驱动 WM_PAINT 去重绘。3. wsprintf轻量格式化但边界要心里有数3.1 wsprintf 到底解决了什么问题TextOut 只能画“现成的字符串”。但真实需求里你要画的是“x120, y85”这种拼合了变量的文本总不能手动把数字转成字符串再拼接。这时候格式化函数就是刚需。wsprintf的签名如下int wsprintf( LPTSTR lpOut, // 输出缓冲区 LPCTSTR lpFmt, // 格式字符串 ... // 可变参数 );它的工作方式和 C 标准库的 sprintf 几乎一样按格式字符串里的占位符把后续参数依次替换进去写入 lpOut 缓冲区返回写入的字符数不包括结尾的 NULL 字符。Win32 程序员之所以熟悉它是因为在 Windows 的早期生态里它就是给 GUI 程序用的专用格式化函数头文件windows.h里直接就能用不必额外包含stdio.h。我现在写 Win32 代码依然习惯用它而不是 sprintf因为窗口程序里格式化字符串的场景本来就轻量没必要引入完整的 C 运行时库依赖虽然现在的开发环境里这根本不是问题但保留这个习惯写出的代码更纯粹、可读性也好。它支持%d整数、%u无符号、%x十六进制、%s字符串、%c字符还有一个%ld长整数基本覆盖了我的日常需求。3.2 格式占位符使用避坑指南这块必须细说因为我在这些占位符上犯过不少低级错误。第一%s在 ANSI 版本里对应的是char*Unicode 版本里必须用%ls或者大写%S也能用但写法不统一容易混淆。如果你在 Unicode 工程里用wsprintf(szBuf, _T(name: %s), pszAnsiString)编译器可能会报警告运行时则极有可能乱码甚至崩溃。反过来%s对应宽字符字符串时在很多实现里会把内存按字节硬读出现半截字符。第二参数类型和占位符必须严格匹配。%d对应int你传了个long在 64 位系统上会出问题因为long在 Windows 上占 4 字节倒还好但如果你传size_t8 字节而%d只按 4 字节取参数那么输出结果就是一个莫名其妙的数。64 位环境下格式化整数我建议用%ld配long或者%I64d配__int64别偷懒。第三缓冲区溢出问题。wsprintf不检查输出缓冲区够不够大它只管往里写写的比缓冲区容量多就直接踩内存。所以给输出缓冲区分配空间时宁可多给不能少给。文本场景下 256 字节或 512 字节通常够了但如果你要拼接长列表我的习惯是直接用StringCchPrintf这类安全版本需要strsafe.h它显式接收缓冲区大小参数超长自动截断不崩。不过在经典教学代码里wsprintf依然是最顺手的那个用的时候自己心里装根弦就行。网上对 wsprintf 还有一个共识值得提它的内部缓冲区是 1024 字节这是它在 Windows 内部的固定实现细节这意味着单个格式化输出的结果不应超过 1024 字节否则未定义行为。一个简单的基准如果你的格式化结果可能超过 1KB换个安全的函数别硬撑。3.3 TCHAR 与通用文本映射一行代码兼容两种编码讲 wsprintf 和 TextOut 配合时一定会碰到TCHAR、_T()、LPTSTR这些东西。它们是为了让同一份代码既能编译成 ANSI 版本也能编译成 Unicode 版本而存在的类型映射。简单说TCHAR当工程定义了_UNICODE时它是wchar_t否则是char。LPCTSTR是const TCHAR*。_T(abc)根据编码模式自动给字符串字面量加L前缀。所以案例里我写TCHAR szBuffer[256];而不是char szBuffer[256];写wsprintf(szBuffer, _T(位置: (%d, %d)), x, y);而不是wsprintf(szBuffer, 位置: (%d, %d), x, y);。当你把工程字符集从“多字节字符集”换成“Unicode 字符集”时代码不用改一行行为自动切换。现在 Visual Studio 新建的 Win32 工程默认字符集就是 Unicode所以从第一课起你就应该写_T包裹字符串字面量否则写中文文本时会碰到编译出乱码的问题还不一定报错。我见过太多新手因为字符串字面量没加_T在 Unicode 工程下看到满屏乱码然后怀疑字体问题其实根子出在编码类型不匹配上。4. 完整实操用 wsprintf 构造动态文本 TextOut 上屏4.1 目标场景与窗口程序骨架咱们要做的案例很具体在窗口客户区里实时显示鼠标的当前坐标格式为鼠标位置: (x, y)。每次鼠标移动文字跟着刷成新的坐标。同时客户区中央显示一个静态信息TextOut wsprintf 案例顺便体验一下字体设置。程序骨架直接套用经典 Win32 窗口程序#include windows.h #include tchar.h // 全局字符串缓冲保存上一次鼠标坐标文本 TCHAR g_szMousePos[128] { 0 }; TCHAR g_szScreenInfo[128] { 0 }; // 窗口过程函数 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);窗口创建部分就不再重复贴完整代码了Win32 样板代码大同小异注册窗口类、创建窗口、消息循环。重点在窗口过程里如何处理消息。4.2 WM_MOUSEMOVE 里收集坐标并触发重绘设计思路是这样鼠标移动时系统给窗口发WM_MOUSEMOVElParam的低 16 位是 x 坐标高 16 位是 y 坐标。我先提取出来格式化好文本然后让这块区域失效触发 WM_PAINT 重绘。case WM_MOUSEMOVE: { int x (short)LOWORD(lParam); int y (short)HIWORD(lParam); // 关键只在坐标变化时才重绘否则鼠标晃动会频繁重绘CPU 飙升 static int s_lastX -1, s_lastY -1; if (x ! s_lastX || y ! s_lastY) { s_lastX x; s_lastY y; wsprintf(g_szMousePos, _T(鼠标位置: (%d, %d)), x, y); InvalidateRect(hWnd, NULL, TRUE); } return 0; }这里有个精髓值得说只重绘必要的内容。一开始我没有加 s_lastX / s_lastY 判断鼠标在窗口里随便动动窗口就每秒被无效化几十次每次都要全窗口擦背景重画肉眼可见地发虚、耗电。加了一个“坐标变了才更新”的守卫之后只有在移动到新像素时才触发效率立刻上一个档次。InvalidateRect(hWnd, NULL, TRUE)的第三个参数 TRUE 表示在重绘前擦掉背景。传 NULL 表示让整个客户区失效。如果你想优化性能可以只让文字所在的矩形区域失效比如RECT rc { 20, 20, 300, 50 }; InvalidateRect(hWnd, rc, TRUE);这样只有那个小区域会被重绘对大规模绘制场景是很大的性能提升。4.3 WM_PAINT 里把文本画出来文本已经格式化好了剩下的工作就是把它画上屏幕。伪代码如下case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hWnd, ps); // 设置背景透明避免文字后面白块 SetBkMode(hdc, TRANSPARENT); // 设置文本颜色为深蓝 SetTextColor(hdc, RGB(20, 40, 220)); HFONT hFont CreateFont( -20, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T(微软雅黑)); HFONT hOldFont (HFONT)SelectObject(hdc, hFont); // 画动态坐标左上角 (16, 16) TextOut(hdc, 16, 16, g_szMousePos, _tcslen(g_szMousePos)); // 画静态标题稍微下移 TextOut(hdc, 16, 60, _T(TextOut wsprintf 案例), _tcslen(_T(TextOut wsprintf 案例))); // 画一条简单的状态说明顺带展示 %s 格式 wsprintf(g_szScreenInfo, _T(当前字体: %s), _T(微软雅黑)); TextOut(hdc, 16, 100, g_szScreenInfo, _tcslen(g_szScreenInfo)); SelectObject(hdc, hOldFont); DeleteObject(hFont); EndPaint(hWnd, ps); return 0; }这里有一个很典型的模式大家可能注意到了wsprintf 不只在处理消息时用在 WM_PAINT 内部也可以直接用。因为它只是个格式化函数不涉及绘图资源在哪里调用都行。不过要注意别在 WM_PAINT 里频繁格式化不变的内容徒增开销。上面代码里那句%s的写法就是演示运行时把字体名格式化进字符串再输出。4.4 从客户区中央显示文本一个更完整的综合版本上面的例子已经能跑了但文本全靠在左上角不够“案例味”。我把代码升级一下模拟一个“窗口中央显示动态统计信息”的版本顺便展示怎么计算文本尺寸让它在客户区居中。居中文本的一个笨办法是先假设文本画出来差不多多少宽然后硬编码 x。但这个方案一旦字体变了、窗口大小变了位置就偏了。我比较推荐的做法用GetTextExtentPoint32量出文本长度用GetClientRect拿到窗口客户区尺寸然后 (客户区宽 - 文本宽) / 2 得到居中的 x。这样不管窗口怎么拉伸文本都在正中间。case WM_PAINT: { PAINTSTRUCT ps; HDC hdc BeginPaint(hWnd, ps); SetBkMode(hdc, TRANSPARENT); RECT rcClient; GetClientRect(hWnd, rcClient); HFONT hFont CreateFont(-28, 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, _T(微软雅黑)); HFONT hOldFont (HFONT)SelectObject(hdc, hFont); SetTextColor(hdc, RGB(60, 120, 200)); TCHAR szText[128]; wsprintf(szText, _T(窗口尺寸: %d x %d), rcClient.right, rcClient.bottom); SIZE size; GetTextExtentPoint32(hdc, szText, _tcslen(szText), size); int x (rcClient.right - size.cx) / 2; int y (rcClient.bottom - size.cy) / 2; TextOut(hdc, x, y, szText, _tcslen(szText)); SelectObject(hdc, hOldFont); DeleteObject(hFont); EndPaint(hWnd, ps); return 0; }这个程序配合WM_SIZE消息窗口大小改变时发InvalidateRect就能实现“窗口拉伸时文字实时居中”。每次窗口尺寸一变系统自动重绘TextOut 用最新的客户区尺寸重新计算居中坐标视觉上就像文字始终钉在窗口正中。GetTextExtentPoint32这个函数值得学会它返回的是选用当前字体时文本的精确像素宽高。不量一下直接画居中就无从谈起。4.5 让文本“活”起来的完整机制梳理综合上面几段你应该能感受到一个完整的“动态文本显示”闭环了事件发生鼠标移动、窗口尺寸变化、计时器触发、网络消息到达更新数据/缓冲区wsprintf 把新值塞进全局字符数组使界面区域失效InvalidateRect系统合并且发送 WM_PAINT窗口过程在 WM_PAINT 里用 TextOut 把缓冲区的文本画出来只要认清“数据更新”和“界面绘制”是解耦的所有动态显示的问题都会变得很清晰。你不需要在每次数据变化时立刻上屏重画交给系统的重绘机制去驱动就行。这也符合 Windows GUI 程序的事件驱动模型画不画、什么时候画由系统说了算你只需要在应该画的时候画对。5. 常见问题与排查技巧实录5.1 文本乱码编码不一致的老问题现象运行程序汉字全部变成诡异的符号或者问号英文正常。排查思路分几步一确认工程字符集。VS 里右键工程 - 配置属性 - 常规 - 字符集看看是“使用 Unicode 字符集”还是“使用多字节字符集”。二看字符串字面量是否带_T。如果你工程是 Unicode但写了你好而不是_T(你好)那这个字符串字面量是char*类型wsprintf的%s期望wchar_t*参数类型不匹配。输出乱码几乎是必然的。同理如果工程是多字节_T又会自动去掉L前缀所以一路_T打天下是最省心的。三检查传给 TextOut 的长度参数是不是字符数。_tcslen返回的是字符数不是字节数。如果你写sizeof(szBuf)这种字节数长度传给 TextOut遇到中文字符时字符数会被错误地多算一个汉字 2 字节但 1 字符TextOut 可能把结尾的 NULL 也算进去画偶尔还会把缓冲区的残余内容也画出来。我排查乱码问题的经验是不要只看显示代码先从数据本身查 —— 把g_szMousePos在调试器里展开看内容如果数据在内存里已经是乱码问题在格式化层面如果内存数据正确、画出来乱码问题在字体或 TextOut 参数层。5.2 文字“闪”或“抖”重绘太频繁压低无效化频率症状鼠标一扫过快文字就在原地闪烁或者窗口拖动时文字像在抖。原因基本是两个重绘频率太高 全区域无效化。鼠标移动事件极频繁如果每次 WM_MOUSEMOVE 都在没变化时也 InvalidateRect 整个客户区系统就会反复擦背景、重画肉眼看起来就是闪。修复方案我上面已经给了只有在坐标变化才触发重绘。更进一步的方案是只让文字所在区域失效或者用双缓冲背面上画完再整体 BitBlt 到屏幕。但双缓冲属于 GDI 进阶内容文本显示这种轻量场景一般不必要。还有一个容易被忽略的细节如果窗口类注册时背景画刷是(HBRUSH)GetStockObject(WHITE_BRUSH)那么每次 WM_PAINT 前系统都要用白色刷子刷一遍整个失效区域这个动作本身就会造成闪烁感。如果不希望每次都擦背景可以在注册窗口类时把背景设成NULL然后在 WM_PAINT 里自己控制何处画背景。不过对新手直接保持默认控制好重绘频率就够了。一个实用口诀能小范围重绘就别全窗口重绘能压低频率就别每次都画。5.3 数据变了但界面不更新忘记 InvalidateRect现象你在 WM_MOUSEMOVE 里更新了全局字符串但屏幕上的文字纹丝不动。因为你的 WM_PAINT 代码只在系统认为“有区域失效”的时候才会被调用。你改了字符串但系统不知道系统觉得客户区还是干净的没必要发 WM_PAINT。所以数据更新之后必须主动调用InvalidateRect(hWnd, NULL, TRUE)告诉系统“这块区域其实脏了请重画”。这个机制对新手来说有点绕记住一个铁律任何影响界面显示的数据更新后面跟一句 InvalidateRect。如果你想让更新立刻生效不等消息循环自然处理可以在 InvalidateRect 之后调用UpdateWindow(hWnd)这会直接给窗口发 WM_PAINT如果存在无效区域跳过消息队列排队。但一般场景里没必要这么着急。有时候你 InvalidateRect 也调了数据也格式化了界面还是不更新 —— 那就要怀疑是不是 WM_PAINT 分支里提前 return 了或者返回值不正确。WM_PAINT 处理完必须走return 0不能用break跳到 DefWindowProc否则系统会认为你没处理干净又给你发一次 WM_PAINT陷入无限重绘。很多“CPU 占用 100%”的奇怪问题就是这种原因。5.4 用表格速查TextOut wsprintf 常见问题清单症状可能原因解决方法汉字乱码编码不一致字面量没加 _T统一用_T确认工程字符集汉字乱码传到 TextOut 的长度是字节数改用_tcslen取字符数文字画完即消失TextOut 不在 WM_PAINT 里把绘制代码挪进 WM_PAINT 分支数据变了画面没变忘了 InvalidateRect数据更新后立即 InvalidateRect画面闪烁重绘太频繁/全区域重绘加坐标变化判断缩小失效区域文字后面有白块背景模式不透明SetBkMode(hdc, TRANSPARENT)程序崩溃缓冲区太小wsprintf 越界缓冲区给 256/512 字节换 StringCchPrintf全窗口刷新卡顿每次移动都全窗口重绘用具体 RECT 局部失效%s 显示乱码/崩溃ANSI/Unicode 混用%ls配宽字符%s搭配同一编码CPU 占用飙升WM_PAINT 无限循环检查是否错误使用 break 而非 return 05.5 一套稳得住的编程习惯建议这篇文章的主题是 TextOut 和 wsprintf 的具体用法但我更希望你带走的是几个能长期受用的习惯字符缓冲区先声明成TCHAR szBuf[...]并且容量宁大勿小这能省掉 80% 的文本乱码和越界问题。凡是格式化跟随字符串输出的地方统一用_tcslen而不是lstrlen、strlen混着用。Windows 编程里越统一越不容易出细碎问题。绘制代码全部收进 WM_PAINT数据代码放在事件处理里二者之间只通过全局缓冲区或窗口附加数据传递层次清晰调试方便。涉及性能的地方多想想“我真的需要整个窗口都重画吗”。刚开始性能不是问题但代码习惯养成了后面做大项目会少掉很多头发。我自己后来做自绘菜单、自定义列表控件时文本显示的核心思路还是这一套 —— 无非是 HDC 换成了内存 DC、TextOut 换成了 DrawText、格式化从 wsprintf 换成了更安全的函数但“事件更新数据 - 失效区域 - 重绘时绘制”这个框架一直没变。把 TextOut wsprintf 这章吃透等于给你的 Win32 基本功打了一根很扎实的地桩。6. 实操后分享这段代码还能怎么扩展案例本身已经完整跑通了我最后再分享几个我当时顺手做的扩展给想继续折腾的人一点方向感。第一个扩展把鼠标坐标的显示区域限定在客户区右下角的状态条风格区域里。做法是在 WM_PAINT 里先画一条细线或一个矩形模拟状态栏再把 TextOut 画在对应的矩形区域顶部。这样你的程序看起来就有一点点“正规工具”的味道了。第二个扩展把字体句柄从 WM_PAINT 里每次都 CreateFont/DeleteObject改成在 WM_CREATE 里创建一次、存成全局变量在 WM_DESTROY 里 DeleteObject。这样每次重绘少两次系统调用性能有微提升。但要注意字体对象在 WM_PAINT 之间要持续有效所以不能创建在栈上自动释放。第三个扩展试试把 wsprintf 换成swprintf_s或者StringCchPrintf观察一下安全性提升。特别是有一次我故意给%d传了一个字符串参数wsprintf 版本直接摸到了一个非法地址崩了安全版本立刻被编译器报出警告。这种对比会很直观地让你明白为什么现在新代码推荐安全函数。TextOut 和 wsprintf 这套组合说实话有点“古早味”但现在 Windows 桌面上大量老软件的 UI 依然是这么画出来的。学它不只是为了应付作业或面试题而是理解 GDI 文本描绘底层逻辑的一块敲门砖。掌握了它后面学 DirectUI、学双缓冲、学自绘控件都会顺很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑