公司动态

Visual C++实战编程100例完整源码解析:从环境搭建到核心代码精讲

📅 2026/7/15 5:05:48
Visual C++实战编程100例完整源码解析:从环境搭建到核心代码精讲
1. 项目概述与核心价值如果你是一名在Windows平台上摸爬滚打多年的C开发者或者正打算从其他语言转向Windows桌面应用开发那么“Visual C实战编程100例”这个名字你一定不会陌生。这本书尤其是它的第2版几乎可以看作是许多程序员在Windows桌面开发领域的“启蒙老师”或“案头工具书”。它不像那些大部头的理论著作而是直接甩给你100个实实在在的、从简单到复杂的例子涵盖了从最基础的对话框、控件使用到文件操作、数据库访问再到图形图像、网络通信乃至多线程等几乎所有桌面开发的核心场景。这种“以例带学”的方式对于解决实际开发中“这个功能怎么实现”的具体问题效率极高。然而很多朋友在学习和使用这本书时常常会遇到一个尴尬的局面书看懂了例子也照着敲了但一旦想在自己的项目中复用某个功能或者想深入理解某个例子的设计思路和代码细节时就感觉隔了一层纱。书上的代码片段是割裂的你需要自己脑补整个项目的结构、头文件的包含关系、资源的配置甚至是一些关键的编译设置。这正是“完整源码解析”这个工作的核心价值所在——它不是简单地打包一份源代码给你而是像一个经验丰富的搭档带你逐行、逐项目地拆解这100个例子告诉你每一行代码为什么这么写每一个项目设置背后的考量是什么以及如何将这些“零件”灵活地组装到你自己的“机器”里。简单来说这次解析的目标群体非常明确正在使用或打算深入学习Visual C进行Windows桌面应用开发的程序员。无论你是刚入门的新手想通过实例快速上手还是有一定经验的中级开发者希望深化对MFC、ATL或Win32 API的理解并积累可复用的代码模块甚至是资深开发者需要一份可靠的、经过验证的代码库作为参考这份完整的源码解析都能提供巨大的帮助。它解决的不仅仅是“怎么做”的问题更是“为什么这么做”以及“如何做得更好、更稳”的问题。2. 源码环境搭建与项目结构深度剖析拿到一份完整的、包含100个实例的Visual C源码第一步绝不是急着打开某个.cpp文件就开始看。一个混乱的、依赖缺失的工程会让你寸步难行。正确的打开方式是从全局入手搭建好可编译、可调试的环境并彻底理解这套源码的“骨架”。2.1 开发环境与依赖库的精准配置首先我们必须明确一个核心前提这本书的实例主要基于经典的MFCMicrosoft Foundation Classes框架部分涉及Win32 API和ATL。因此你的开发环境必须是Microsoft Visual Studio并且版本不能太低。虽然书名为“Visual C”但现代开发强烈建议使用Visual Studio 2019 或 2022的社区版免费。它们对旧版VC项目有很好的兼容性支持。注意当你用高版本VS打开旧版如VC6.0或VS2008的.dsp或.vcproj项目文件时VS会启动“项目升级向导”。务必仔细阅读升级报告重点检查字符集设置旧项目默认是“多字节字符集”而VS新项目默认是“Unicode字符集”。如果源码中有大量硬编码的中文字符串升级到Unicode可能会导致乱码。你需要在项目属性 - 常规 - 字符集中根据源码情况选择“使用多字节字符集”或“使用Unicode字符集”。运行库链接检查项目属性 - C/C - 代码生成 - 运行库。旧项目可能链接的是/MT或/MTd静态链接而升级后可能变成了/MD或/MDd动态链接。如果运行时提示找不到MSVCRxxx.DLL就需要调整此项确保与你的部署环境匹配。平台工具集升级后平台工具集通常会变为最新的如v143。如果遇到奇怪的编译错误可以尝试在项目属性 - 常规 - 平台工具集中选择稍旧但更稳定的版本如v142。其次是关于Microsoft Visual C Redistributable的“坑”。很多新手在运行编译好的程序时会弹窗提示“找不到MSVCP140.dll”或“VCRUNTIME140_1.dll丢失”。这不是你的代码问题而是目标机器上没有安装对应版本的VC运行库。你需要根据编译时使用的工具集版本去微软官网下载并安装对应的Visual C Redistributable包。例如使用VS2019工具集v142编译的Release版本程序通常需要安装“Microsoft Visual C 2015-2019 Redistributable”。一个稳妥的做法是在你自己开发的程序安装包中直接打包并静默安装对应的运行库。对于学习阶段的这100个例子更简单的方法是在项目属性中将运行库改为/MT静态链接这样编译器会把必要的C运行时库代码直接打包进你的.exe文件生成的文件会变大但可以免去目标机器安装运行库的麻烦特别适合在本机测试和分发给没有开发环境的朋友。2.2 百例源码的目录结构与设计哲学一套优秀的实战源码其目录结构本身就应该体现清晰的设计思路。这100个例子通常不会杂乱地堆在一个文件夹里。一个合理的组织方式可能是按功能模块或技术主题进行分卷VisualCpp100Examples/ ├── Volume1_Basics/ # 基础篇对话框、控件、消息映射 │ ├── Example01_HelloMFC/ │ ├── Example02_ButtonDialog/ │ └── ... ├── Volume2_DataIO/ # 数据篇文件、注册表、数据库 │ ├── Example21_FileReadWrite/ │ ├── Example22_RegistryOp/ │ └── ... ├── Volume3_GDI_GUI/ # 图形篇GDI、图形绘制、图像处理 │ ├── Example41_DrawLines/ │ ├── Example42_ImageProcessing/ │ └── ... ├── Volume4_Advanced/ # 高级篇多线程、网络、COM │ ├── Example71_ThreadDemo/ │ ├── Example72_WinSockChat/ │ └── ... └── Common/ # 公共资源自定义头文件、库、图标等 ├── Include/ ├── Lib/ └── Resources/这样的结构好处显而易见主题明确你可以快速定位到自己想学习的技术领域。独立性每个例子在理想情况下应该是自包含的不依赖其他例子的特定代码除了Common目录下的公共设施方便单独抽取和学习。渐进性从Basics到Advanced难度是递进的符合学习曲线。在解析每个例子时我们首先要看的就是它的“门面”——解决方案.sln和项目文件.vcxproj。用文本编辑器如VS Code打开.vcxproj文件你可以看到这个项目的所有“基因”引用了哪些头文件路径AdditionalIncludeDirectories、链接了哪些库AdditionalDependencies、预处理器定义了哪些宏PreprocessorDefinitions。理解这些你就掌握了让这个项目成功编译的全部秘密。3. 核心实例分类与关键技术点精讲这100个例子覆盖面极广我们可以将其归纳为几个核心大类每一类都对应着Windows桌面开发中的一个关键技能树。下面我将挑选每个类别中最具代表性、也最容易出问题的例子进行深度解析。3.1 用户界面与消息机制从“Hello World”到自定义控件实例基于对话框的程序与按钮事件响应这是几乎所有MFC初学者的起点。一个简单的对话框上面一个按钮点击后弹出一个消息框。代码可能只有十几行但背后涉及的MFC框架机制却非常深刻。// Example01_HelloMFC.cpp 中的按钮点击事件处理函数 void CExample01Dlg::OnBnClickedOk() { // TODO: 在此添加控件通知处理程序代码 AfxMessageBox(_T(你好MFC)); // CDialogEx::OnOK(); // 注意这里没有调用基类的OnOK因为不想关闭对话框 }关键技术点解析消息映射宏ON_BN_CLICKED(IDOK, CExample01Dlg::OnBnClickedOk)。这是MFC将Windows消息WM_COMMAND通知码BN_CLICKED与你的成员函数连接起来的桥梁。理解消息映射表BEGIN_MESSAGE_MAP...END_MESSAGE_MAP是理解MFC事件驱动的核心。资源与控件ID按钮的ID如IDOK定义在资源文件.rc中。MFC通过资源编辑器将界面元素和代码逻辑解耦。常见坑点在代码中硬编码控件ID数值或者修改了资源ID后没有同步更新消息映射宏导致事件无法响应。字符集与_T()宏_T(你好MFC)这个宏是为了兼容多字节和Unicode编译环境。在Unicode设置下_T(“x”)扩展为L“x”在多字节下扩展为“x”。始终使用_T()来包裹字符串字面量是写出跨字符集兼容代码的好习惯。实例列表控件CListCtrl的高级应用列表控件是展示数据的利器但用好它需要不少技巧。一个完整的例子应该包括动态创建列、插入数据项、设置行高、自定义绘制Owner Draw、虚拟列表Virtual List支持大数据量等。实操心得避免在OnInitDialog中直接插入大量数据这会导致界面卡顿。正确的做法是在对话框初始化后开启一个工作线程加载数据然后通过PostMessage发送自定义消息到UI线程在消息处理函数中更新列表。自定义绘制实现交替行背景色这是提升列表可读性的常用技巧。你需要处理NM_CUSTOMDRAW通知消息。在OnNMCustomdrawList函数中判断pNMCD-dwDrawStage当它为CDDS_ITEMPREPAINT时根据行索引的奇偶性设置pLVCD-clrTextBk背景色并返回CDRF_NEWFONT以应用新属性。虚拟列表LVS_OWNERDATA当你有成千上万行数据时切勿一次性插入。启用虚拟列表风格后控件只会在需要显示某一行时向你请求数据通过LVN_GETDISPINFO通知。你只需维护一个数据数组并在回调函数中填充请求项的信息即可内存占用极低。3.2 数据持久化文件、注册表与数据库实例INI配置文件读写虽然现在更推荐使用XML或JSON但INI文件因其简单直观在小型配置存储中仍有市场。Windows提供了WritePrivateProfileString和GetPrivateProfileString等API但MFC例子中更常见的是封装一个CIniFile类。深度解析 一个健壮的CIniFile类不应该只简单包装API。它应该处理路径自动处理相对路径和绝对路径。默认将INI文件放在应用程序同级目录或%APPDATA%下。缓存机制对于频繁读取的配置项可以在类内部维护一个std::map缓存避免反复磁盘IO。类型安全提供ReadInt,ReadString,ReadBool,WriteInt等一系列重载函数避免调用者进行繁琐的类型转换。线程安全如果配置可能在多线程环境中被读写需要对文件操作或缓存访问加锁如使用CRITICAL_SECTION。实例使用ADO访问数据库访问数据库是业务系统的核心。例子中可能会演示如何使用ADOActiveX Data Objects连接Access或SQL Server。关键步骤与避坑指南初始化COM库ADO基于COM所以在使用前必须调用CoInitialize(NULL)程序退出前调用CoUninitialize()。最好在应用类CWinApp派生类的InitInstance和ExitInstance中完成。连接字符串的构建这是最容易出错的地方。连接SQL Server的字符串可能长这样CString strConn; strConn.Format(_T(ProviderSQLOLEDB.1;Integrated SecuritySSPI;Persist Security InfoFalse;Initial CatalogMyDB;Data Source.\\SQLEXPRESS));如果使用SQL Server身份验证则需要包含User IDsa;Passwordxxx;。重要安全提示绝对不要将密码硬编码在源码中应该从配置文件或让用户输入。错误处理必须检查每一个ADO调用Open,Execute的返回值HRESULT。ADO的错误信息非常详细可以通过_com_error异常对象或Connection对象的Errors集合获取。try { m_pRecordset-Open(...); } catch (_com_error e) { CString errMsg; errMsg.Format(_T(数据库错误%s), (LPCTSTR)e.Description()); AfxMessageBox(errMsg); }资源释放务必在不再需要时显式地关闭并释放Recordset和Connection对象-Close()并将智能指针_RecordsetPtr,_ConnectionPtr置为NULL以便COM引用计数归零防止内存泄漏。3.3 图形图像与多媒体处理实例使用GDI进行图像缩放与保存GDI是GDI的增强版提供了更简单的接口来处理图像、渐变、抗锯齿等。一个典型的例子是加载一张图片缩放后显示在Picture Control上并保存为新文件。核心代码流程// 1. 初始化GDI GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; GdiplusStartup(gdiplusToken, gdiplusStartupInput, NULL); // 2. 加载图像 Image* pImage new Image(Linput.jpg); // 3. 计算缩放尺寸并绘制到内存位图 int newWidth pImage-GetWidth() / 2; int newHeight pImage-GetHeight() / 2; Bitmap* pThumbnail new Bitmap(newWidth, newHeight); Graphics* pGraphics Graphics::FromImage(pThumbnail); pGraphics-DrawImage(pImage, 0, 0, newWidth, newHeight); // 4. 显示这里需要获取Picture Control的DC进行绘制 // 5. 保存 CLSID clsidJpeg; GetEncoderClsid(Limage/jpeg, clsidJpeg); // 需要实现此函数获取编码器CLSID pThumbnail-Save(Loutput.jpg, clsidJpeg, NULL); // 6. 清理资源 delete pGraphics; delete pThumbnail; delete pImage; GdiplusShutdown(gdiplusToken);注意事项GDI的初始化和关闭必须在程序启动和退出时成对调用GdiplusStartup和GdiplusShutdown。通常放在CWinApp派生类的InitInstance和ExitInstance中最合适。资源泄漏GDI对象Image*,Graphics*,Bitmap*是C对象必须用delete释放不同于GDI的句柄需要用DeleteObject。忘记释放是常见的泄漏源。编码器CLSID保存为不同格式JPEG, PNG, BMP需要不同的编码器CLSID。GetEncoderClsid是一个需要自己实现的辅助函数通过枚举ImageCodecInfo来查找。实例简单的动画绘制双缓冲技术在窗口中绘制连续动画如一个移动的小球如果直接在主窗口DC上绘制会出现严重的闪烁。双缓冲技术是解决这个问题的标准方案。双缓冲实现原理在内存中创建一个与窗口客户区兼容的位图CBitmap。创建一个与内存位图关联的内存设备上下文CDC。将所有绘图操作先在内存DC上完成。最后用BitBlt或StretchBlt函数将内存DC中的内容一次性“贴”到窗口DC上。void CAnimationView::OnDraw(CDC* pDC) { // 1. 获取客户区矩形 CRect rect; GetClientRect(rect); // 2. 创建内存DC和兼容位图 CDC memDC; CBitmap memBitmap; memDC.CreateCompatibleDC(pDC); memBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); CBitmap* pOldBitmap memDC.SelectObject(memBitmap); // 3. 先用背景色填充内存DC消除上一帧残留 memDC.FillSolidRect(rect, RGB(255, 255, 255)); // 4. 在内存DC上绘制当前帧例如一个圆形 CBrush brush(RGB(255, 0, 0)); CBrush* pOldBrush memDC.SelectObject(brush); memDC.Ellipse(m_ballPos.x - 20, m_ballPos.y - 20, m_ballPos.x 20, m_ballPos.y 20); memDC.SelectObject(pOldBrush); // 5. 一次性拷贝到屏幕DC pDC-BitBlt(0, 0, rect.Width(), rect.Height(), memDC, 0, 0, SRCCOPY); // 6. 清理SelectObject恢复原对象是良好习惯 memDC.SelectObject(pOldBitmap); // memBitmap和memDC在析构时会自动清理 }通过一个定时器SetTimer不断更新m_ballPos并触发Invalidate()重绘就能实现流畅的动画效果。关键点所有绘制计算都在内存DC上完成最后只进行一次屏幕拷贝从而消除了中间过程的闪烁。3.4 进程、线程与网络通信实例使用工作者线程执行耗时任务在GUI程序中绝不能在主线程UI线程中执行耗时操作如大量计算、网络下载、大文件复制否则界面会“卡死”。必须使用工作者线程。MFC中创建工作者线程的标准做法// 1. 定义线程函数全局函数或静态成员函数 UINT MyThreadProc(LPVOID pParam) { // 将参数转换为需要的数据结构 CMyTaskData* pData (CMyTaskData*)pParam; // 执行耗时任务... for (int i 0; i pData-nIterations; i) { // 模拟工作 Sleep(100); // 如何向UI线程报告进度 } // 线程结束 return 0; // 线程退出码 } // 2. 在UI线程如按钮点击事件中启动线程 void CMyDialog::OnBnClickedStartThread() { CMyTaskData* pData new CMyTaskData; // 必须动态分配线程负责删除 pData-nIterations 100; // AfxBeginThread 创建并启动线程 CWinThread* pThread AfxBeginThread(MyThreadProc, pData); if (pThread) { // 可以保存pThread用于后续查询状态 m_pMyThread pThread; } }线程间通信的几种方式PostMessage / SendMessage最常用。工作者线程不能直接调用UI对象的方法但可以通过::PostMessage向主窗口发送自定义消息WM_USER XXX并在主窗口的消息映射中处理它更新进度条或显示结果。// 在线程中 ::PostMessage(pData-hWndNotify, WM_MY_THREAD_PROGRESS, (WPARAM)progress, 0);事件Event用于线程同步。例如UI线程可以创建一个事件CreateEvent工作者线程在完成特定阶段后SetEventUI线程通过WaitForSingleObject带超时来等待。内存共享与锁如果线程间需要交换复杂数据可以使用共享内存同一进程内就是全局变量或堆对象但必须用临界区CRITICAL_SECTION、互斥量Mutex或信号量Semaphore来保护防止数据竞争。重要安全提示资源清理传递给线程的参数如上面的pData必须在线程函数内delete或者在UI线程中等待线程结束WaitForSingleObject后再清理。切勿在UI线程中假设线程已结束而提前删除数据。不要在线程中操作UI对象绝对禁止在工作线程中直接调用UpdateData、SetWindowText或访问任何MFC UI控件的成员变量这会导致不可预知的崩溃。所有UI更新必须通过消息传递回主线程执行。实例基于WinSock的简单聊天程序这个例子会涵盖Socket编程的基础创建套接字、绑定、监听、连接、发送和接收数据。一个典型的例子包含一个服务器端和一个客户端。服务器端核心步骤WSAStartup初始化WinSock库。socket创建监听套接字SOCK_STREAM对应TCP。bind将套接字绑定到本地IP和端口如INADDR_ANY和8888。listen开始监听连接请求。accept在一个循环中接受客户端连接。accept是阻塞调用通常会为每个接受的客户端连接创建一个新的线程或使用I/O复用模型来处理。在新线程中使用recv接收该客户端的数据使用send发送数据。通信结束后closesocket线程结束。客户端核心步骤WSAStartup。socket。connect连接到服务器地址和端口。连接成功后就可以用send和recv与服务器通信。通信结束closesocket。避坑经验字节序问题网络字节序是Big-Endian而x86主机是Little-Endian。发送整型、短整型等多字节数据前要用htons主机到网络短整型、htonl主机到网络长整型转换接收后要用ntohs、ntohl转换回来。TCP粘包/拆包TCP是流式协议没有消息边界。recv一次可能收到半条消息、一条完整消息或多条消息。必须在应用层定义协议。最简单的方式是“长度内容”先发送一个固定长度的包头如4字节整数表示后续数据的长度再发送实际数据。接收方先收满4字节解析出长度N再循环接收直到收满N字节的数据。非阻塞与超时默认的Socket是阻塞的recv会一直等到有数据。在实际应用中需要设置接收超时setsockoptwithSO_RCVTIMEO或使用非阻塞模式I/O多路复用select避免程序在某个连接上“卡死”。WSA清理最后一定要调用WSACleanup。4. 编译、调试与代码移植中的实战问题即使有了完整的源码从“能编译”到“能稳定运行”再到“能融入自己的项目”还有很长的路要走。这部分分享的都是血泪教训换来的经验。4.1 编译错误与警告的排查心法面对上百个项目的源码编译时遇到错误是家常便饭。除了常见的语法错误、缺少头文件、链接库错误外还有一些“经典”难题“无法找到Windows.h”或“MFC相关头文件找不到”原因项目包含目录Include Directories设置不正确或者没有安装对应版本的MFC开发组件。解决首先确认VS安装时勾选了“使用C的桌面开发”和对应的MFC组件。然后在项目属性 - VC目录 - 包含目录中添加$(WindowsSdkDir)Include\$(WindowsSDKVersion)\um和$(WindowsSdkDir)Include\$(WindowsSDKVersion)\shared等系统路径。对于MFC路径通常是$(VC_IncludePath)和$(WindowsSDK_IncludePath)这些宏通常已正确设置。链接错误“LNK2001: 无法解析的外部符号”原因声明了函数或类但没有找到其实现体.obj或.lib文件。系统性排查检查库依赖项目属性 - 链接器 - 输入 - 附加依赖项是否添加了所需的.lib文件例如使用WinSock需要ws2_32.lib使用GDI需要gdiplus.lib。检查库目录项目属性 - VC目录 - 库目录确保.lib文件所在的路径已添加。检查代码函数签名包括调用约定__stdcall,__cdecl在声明和定义处是否完全一致C函数是否有extern “C”修饰检查运行时库如果项目A编译为静态库/MT而项目B主程序使用动态库/MD链接时也可能出错。确保所有项目的“代码生成 - 运行时库”设置一致。“error C2664: ‘函数名’ : 不能将参数 1 从 ‘const char [xx]’ 转换为 ‘LPCWSTR’”原因这是字符集不匹配的典型错误。项目设置为Unicode但代码中传递了多字节字符串char*。解决使用_T()宏包裹字符串字面量或者使用宽字符版本L”string”。对于字符串变量使用TCHAR、CStringMFC或std::wstring来保持一致性。4.2 调试技巧让Bug无处遁形利用“输出”窗口除了设置断点养成使用OutputDebugString函数输出调试信息的习惯。在VS的输出窗口调试 - 窗口 - 输出中你可以看到这些信息这对于跟踪在多线程环境下或断点难以设置的场景中的程序流非常有用。#ifdef _DEBUG CString strMsg; strMsg.Format(_T(“线程 %d 进入函数XXX参数%d\n”), GetCurrentThreadId(), param); OutputDebugString(strMsg); #endif内存泄漏检测在Debug模式下MFC和CRT库提供了强大的内存泄漏检测功能。在stdafx.h开头定义#define _CRTDBG_MAP_ALLOC #include stdlib.h #include crtdbg.h并在CWinApp::InitInstance开头调用_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);。程序退出时输出窗口会显示所有未释放的内存块信息及其分配处的行号。检查句柄和GDI对象泄漏对于GDI对象HBITMAP,HPEN,HFONT等和内核对象HANDLE可以使用Windows自带的工具GDIView和Process Explorer来监控你的进程看看这些对象的数量是否在程序运行过程中只增不减。4.3 代码移植与集成从例子到产品学习这100个例子的最终目的是将其中的技术点应用到自己的项目中。直接复制粘贴代码往往不行你需要做“移植手术”。功能模块化抽取不要复制整个对话框类。分析例子中实现目标功能的核心函数比如一个CIniFile类的读写方法一个图像缩放函数一个Socket通信线程函数将这些函数和其依赖的少量辅助函数、头文件抽取出来放入你自己项目的相应模块中。依赖解耦例子中的代码可能依赖一些全局变量或特定的资源ID。移植时要将这些依赖参数化通过函数参数或类构造函数传入。例如一个绘图函数原来直接使用全局的g_hWnd获取DC应该改为接收一个HDC或CDC*参数。错误处理强化书中的例子为了简洁错误处理可能比较弱。移植到生产环境必须加入完备的错误检查、资源释放和异常处理逻辑。例如文件打开失败、内存分配失败、网络连接断开等情况都应有明确的处理路径和用户提示。编码风格统一例子的编码风格命名、缩进、注释可能与你团队的标准不符。在集成前最好按照自己项目的规范进行重构以保证代码库的一致性。5. 进阶思考超越100例构建自己的知识体系这100个例子是一座宝库但宝库的价值在于被你吸收和运用。当你通过解析和练习掌握了这些“招式”后下一步就是思考如何“内化”和“创新”。第一横向对比与抽象。例如你学习了用ADO操作数据库也学习了用ODBC。不妨思考一下它们的接口设计有何异同各自的优缺点和适用场景是什么抽象出一个“数据库操作层”的接口让业务代码不依赖于具体的数据库访问技术。这就是从“会用”到“懂原理”的飞跃。第二深入底层原理。MFC的DoModal函数是如何实现模态对话框的消息泵Message PumpGetMessage-TranslateMessage-DispatchMessage这个循环在MFC的哪个角落里CWinApp、CFrameWnd、CDocument、CView这几个核心类是如何协作的带着这些问题去阅读MFC的部分源码例如MFC\SRC目录下的WinCore.cpp,DlgCore.cpp你会对Windows GUI编程有颠覆性的认识。第三关注现代替代方案。虽然MFC和Win32 API依然强大且稳定但微软也在不断推出新的框架如用于现代UI的WinUI 3以及跨平台的.NET MAUI。了解这些新技术的发展方向思考如何将你在经典桌面开发中积累的架构思想和问题解决能力迁移过去而不是局限于特定的API。例如多线程编程的思想、网络通信的协议设计、图形绘制的双缓冲技术这些核心概念在任何GUI框架下都是相通的。最后也是最重要的动手、动手、再动手。不要满足于看懂代码。尝试去修改例子给聊天程序加上用户列表给图像处理程序加上批量处理功能把单线程的文件搜索改成多线程。在修改和调试的过程中你会遇到书上没写的问题而解决这些问题的过程才是你真正成长的时刻。这100个例子是地图和工具箱而真正的探险和建造需要你自己去完成。