当前位置: 首页 > news >正文

通用GUI编程技术——Win32 原生编程实战(五十五)——系统托盘

通用GUI编程技术——Win32 原生编程实战(五十五)——系统托盘

仓库已经开源!喜欢的话点个⭐!仓库Win32和Win32图形栈的部分目前已完成教程,力争做一个完备的GUI教程!

欢迎各位大佬前来参观:https://github.com/Charliechen114514/anatomy_gui

上一篇文章我们聊了 Hook 机制——拦截系统级别的键盘和鼠标输入。Hook 是一种底层能力,用好了非常强大,但大部分时候你不会天天用它。今天我们要聊的东西则恰恰相反——几乎每个正经的桌面程序都会用到,但很多人不知道里面有多少细节:系统托盘(System Tray / Notification Area)。你的程序怎么最小化到托盘?怎么显示托盘图标和右键菜单?怎么弹出气泡通知?任务栏崩溃后怎么恢复图标?这些问题今天全部搞定。


为什么需要系统托盘

系统托盘(也叫通知区域)是 Windows 任务栏右下角的那个区域,里面放着时钟、音量、网络状态等图标。很多应用程序也会在这里放一个图标,常见的有:

  • 后台常驻程序:杀毒软件、输入法、云同步工具——它们不需要一直显示主窗口,但需要在后台运行,用户需要时可以通过托盘图标调出。
  • 最小化到托盘:下载管理器、音乐播放器——用户点击关闭按钮时不是退出程序,而是最小化到托盘继续工作。
  • 状态通知:邮件客户端收到新邮件时弹出气泡通知、系统更新时提示用户。

从技术角度说,系统托盘本质上就是一个图标 + 一个回调消息。系统不会帮你管理窗口、不会帮你创建菜单——所有这些都得你自己处理。这也就意味着有很多细节需要注意。


环境说明

在我们正式开始之前,先明确一下我们这次动手的环境:

  • 平台:Windows 10/11
  • 开发工具:Visual Studio 2019 或更高版本(Community 版本就行)
  • 编程语言:C++(C++17 或更新)
  • 项目类型:桌面应用程序(Win32 项目)
  • 额外依赖:Shell32.lib

第一步——Shell_NotifyIcon 与 NOTIFYICONDATA

系统托盘的所有操作都通过一个函数完成:Shell_NotifyIcon

BOOLShell_NotifyIcon(DWORD dwMessage,// 操作类型PNOTIFYICONDATA lpData// 数据结构);

四种操作

dwMessage含义
NIM_ADD添加托盘图标
NIM_MODIFY修改托盘图标(更新图标、提示文字等)
NIM_DELETE删除托盘图标
NIM_SETVERSION设置通知接口版本(推荐设置)

NOTIFYICONDATA 结构

这个结构体随着 Windows 版本演进变得越来越长。这里列出最常用的字段:

// 使用最新的结构体版本typedefstruct{DWORD dwSize;// 结构体大小 = sizeof(NOTIFYICONDATA)HWND hWnd;// 接收回调消息的窗口UINT uID;// 图标 ID(一个窗口可以有多个托盘图标)UINT uFlags;// 指定哪些字段有效UINT uCallbackMessage;// 自定义回调消息 IDHICON hIcon;// 图标句柄WCHAR szTip[128];// 工具提示文字(鼠标悬停时显示)DWORD dwState;// 图标状态DWORD dwStateMask;// 状态掩码WCHAR szInfo[256];// 气泡通知文字union{UINT uTimeout;// 气泡超时时间(毫秒)UINT uVersion;// 版本号(NIM_SETVERSION 时用)};WCHAR szInfoTitle[64];// 气泡标题DWORD dwInfoFlags;// 气泡图标类型GUID guidItem;// 图标 GUID(用于识别)HICON hBalloonIcon;// 自定义气泡图标}NOTIFYICONDATA;

uFlags 常用标志

标志含义
NIF_MESSAGEuCallbackMessage 有效
NIF_ICONhIcon 有效
NIF_TIPszTip 有效
NIF_INFO气泡通知相关字段有效
NIF_SHOWTIP显示工具提示(Win2000+ 要求显式启用)

⚠️ 注意

dwSize 必须正确:用sizeof(NOTIFYICONDATA)初始化。如果大小不正确,Shell_NotifyIcon会失败。不要手动填写这个值。


第二步——添加和删除托盘图标

添加托盘图标

#defineWM_TRAYICON(WM_APP+100)#defineID_TRAY_ICON1BOOLAddTrayIcon(HWND hwnd,HINSTANCE hInst){NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP;nid.uCallbackMessage=WM_TRAYICON;nid.hIcon=LoadIcon(NULL,IDI_APPLICATION);// 使用系统默认图标wcscpy_s(nid.szTip,L"托盘示例程序");if(!Shell_NotifyIcon(NIM_ADD,&nid)){returnFALSE;}// 设置通知版本为 NOTIFYICON_VERSION_4// 这样回调消息的 wParam/lParam 语义更清晰nid.uVersion=NOTIFYICON_VERSION_4;Shell_NotifyIcon(NIM_SETVERSION,&nid);returnTRUE;}

删除托盘图标

voidRemoveTrayIcon(HWND hwnd){NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;Shell_NotifyIcon(NIM_DELETE,&nid);}

⚠️ 注意

程序退出前必须删除托盘图标。如果你不调用Shell_NotifyIcon(NIM_DELETE, ...),图标会一直留在托盘里,直到用户把鼠标移上去——这时系统才会发现你的程序已经不在了,然后删除图标。这会显得很不专业。


第三步——处理托盘回调消息

当用户在托盘图标上操作时(单击、双击、右键等),系统会向你指定的窗口发送uCallbackMessage消息。

NOTIFYICON_VERSION_4 的消息格式

如果设置了NOTIFYICON_VERSION_4(推荐),回调消息的参数如下:

caseWM_TRAYICON:{// wParam = 图标 ID(即 uID)// lParam = 鼠标事件或通知事件switch(LOWORD(lParam)){caseWM_LBUTTONUP:// 左键单击——通常用来还原/显示主窗口break;caseWM_RBUTTONUP:// 右键单击——通常用来显示弹出菜单break;caseWM_LBUTTONDBLCLK:// 左键双击——通常用来还原/显示主窗口break;caseNIN_BALLOONUSERCLICK:// 用户点击了气泡通知break;caseNIN_BALLOONTIMEOUT:// 气泡通知超时消失break;caseNIN_POPUPOPEN:// 鼠标悬停在图标上,弹出提示break;caseNIN_SELECT:// 通知区域图标被选中(键盘导航)break;}return0;}

右键菜单

右键点击托盘图标时显示弹出菜单是最常见的交互模式。这里有几个坑需要注意:

voidShowTrayContextMenu(HWND hwnd){POINT pt;GetCursorPos(&pt);HMENU hMenu=CreatePopupMenu();AppendMenu(hMenu,MF_STRING,IDM_RESTORE,L"显示主窗口");AppendMenu(hMenu,MF_SEPARATOR,0,NULL);AppendMenu(hMenu,MF_STRING,IDM_SETTINGS,L"设置...");AppendMenu(hMenu,MF_SEPARATOR,0,NULL);AppendMenu(hMenu,MF_STRING,IDM_EXIT,L"退出");// 重要:必须设置前台窗口,否则菜单可能不会正确关闭SetForegroundWindow(hwnd);// 显示弹出菜单TrackPopupMenu(hMenu,TPM_RIGHTBUTTON|TPM_BOTTOMALIGN|TPM_LEFTALIGN,pt.x,pt.y,0,hwnd,NULL);// 重要:再次设置前台窗口,确保菜单能正确响应SetForegroundWindow(hwnd);DestroyMenu(hMenu);}

⚠️ 注意

SetForegroundWindow 的必要性:如果不调用SetForegroundWindow,弹出菜单可能不会在用户点击其他地方时自动关闭。这是 Windows 的一个已知行为——TrackPopupMenu 需要调用它的线程拥有前台焦点。在托盘图标上右键时,焦点在 Shell 上而不是你的程序上,所以需要先抢一下前台。


第四步——最小化到托盘

"关闭按钮不退出,而是最小化到托盘"是托盘程序最常见的行为模式。

核心思路

  1. 用户点击关闭按钮时,拦截 WM_CLOSE
  2. 隐藏主窗口(而不是销毁)
  3. 添加托盘图标
  4. 用户双击托盘图标时,显示主窗口并删除托盘图标
LRESULT CALLBACKWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){staticBOOL g_inTray=FALSE;switch(uMsg){caseWM_CLOSE:// 不销毁窗口,而是隐藏并最小化到托盘if(!g_inTray){AddTrayIcon(hwnd,((LPCREATESTRUCT)GetWindowLongPtr(hwnd,GWLP_WNDPROC))->hInstance);g_inTray=TRUE;}ShowWindow(hwnd,SW_HIDE);return0;// 不调用 DefWindowProc,阻止销毁caseWM_TRAYICON:{switch(LOWORD(lParam)){caseWM_LBUTTONDBLCLK:caseWM_LBUTTONUP:// 还原窗口ShowWindow(hwnd,SW_SHOW);SetForegroundWindow(hwnd);RemoveTrayIcon(hwnd);g_inTray=FALSE;break;caseWM_RBUTTONUP:ShowTrayContextMenu(hwnd);break;}return0;}caseWM_COMMAND:{switch(LOWORD(wParam)){caseIDM_RESTORE:ShowWindow(hwnd,SW_SHOW);SetForegroundWindow(hwnd);RemoveTrayIcon(hwnd);g_inTray=FALSE;break;caseIDM_EXIT:RemoveTrayIcon(hwnd);DestroyWindow(hwnd);break;}return0;}caseWM_DESTROY:RemoveTrayIcon(hwnd);PostQuitMessage(0);return0;}returnDefWindowProc(hwnd,uMsg,wParam,lParam);}

第五步——气泡通知

气泡通知(Balloon Notification)是从托盘图标弹出的一个小气泡框,用于向用户显示提示信息。

显示气泡

voidShowBalloonNotification(HWND hwnd,constwchar_t*title,constwchar_t*text,DWORD infoFlags){NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;nid.uFlags=NIF_INFO;nid.dwInfoFlags=infoFlags;wcscpy_s(nid.szInfoTitle,title);wcscpy_s(nid.szInfo,text);Shell_NotifyIcon(NIM_MODIFY,&nid);}// 使用示例ShowBalloonNotification(hwnd,L"提示",L"操作已成功完成!",NIIF_INFO);ShowBalloonNotification(hwnd,L"警告",L"磁盘空间不足",NIIF_WARNING);ShowBalloonNotification(hwnd,L"错误",L"无法连接到服务器",NIIF_ERROR);

dwInfoFlags 图标类型

图标
NIIF_NONE无图标
NIIF_INFO信息(蓝色 i)
NIIF_WARNING警告(黄色 !)
NIIF_ERROR错误(红色 X)
NIIF_USER使用 hBalloonIcon 字段的自定义图标

气泡 vs Toast

在 Windows 10/11 上,气泡通知可能会被系统转换为 Toast 通知(从屏幕右侧弹出)。这取决于用户的系统设置。你无法控制这个转换——如果你的目标是 Windows 10+,建议直接使用 Windows.UI.Notifications API(Toast 通知框架),它是更现代的通知方式。


第六步——WM_TASKBARCREATED:处理任务栏重启

这是一个很多人忽略但非常重要的细节。

Windows 的任务栏(explorer.exe)偶尔会崩溃并重启。当它重启时,所有托盘图标都会消失——但你的程序还在运行,用户再也看不到你的图标了。

解决方案:注册一个自定义消息WM_TASKBARCREATED。当任务栏重启时,系统会向所有顶级窗口广播这个消息。你收到后重新添加托盘图标即可。

// 在全局或静态变量中保存消息 IDUINT g_uTaskbarRestart=0;// 在 WinMain 中注册g_uTaskbarRestart=RegisterWindowMessage(L"TaskbarCreated");// 在 WndProc 中处理// 由于这是注册的消息,不能用 switch-case,必须用 ifif(uMsg==g_uTaskbarRestart){// 任务栏重启了,重新添加托盘图标if(g_inTray){AddTrayIcon(hwnd,hInstance);}return0;}

注意:RegisterWindowMessage每次调用返回同一个值(对于同一个字符串),所以可以在任何地方调用。


第七步——完整示例

这个示例把今天所有知识整合起来:一个窗口,关闭时最小化到托盘,有右键菜单,支持双击还原,处理任务栏重启。

#ifndefUNICODE#defineUNICODE#endif#include<windows.h>#include<shellapi.h>#pragmacomment(lib,"shell32.lib")#defineWM_TRAYICON(WM_APP+100)#defineID_TRAY_ICON1// 菜单命令#defineIDM_RESTORE2001#defineIDM_ABOUT2002#defineIDM_EXIT2003// 全局变量UINT g_uTaskbarRestart=0;BOOL g_inTray=FALSE;HWND g_hWnd=NULL;// 前向声明BOOLAddTrayIcon(HWND hwnd);voidRemoveTrayIcon(HWND hwnd);voidShowTrayContextMenu(HWND hwnd);BOOLAddTrayIcon(HWND hwnd){NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;nid.uFlags=NIF_ICON|NIF_MESSAGE|NIF_TIP|NIF_SHOWTIP;nid.uCallbackMessage=WM_TRAYICON;nid.hIcon=LoadIcon(NULL,IDI_APPLICATION);wcscpy_s(nid.szTip,L"托盘示例 - 双击还原窗口");if(!Shell_NotifyIcon(NIM_ADD,&nid))returnFALSE;nid.uVersion=NOTIFYICON_VERSION_4;Shell_NotifyIcon(NIM_SETVERSION,&nid);returnTRUE;}voidRemoveTrayIcon(HWND hwnd){NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;Shell_NotifyIcon(NIM_DELETE,&nid);}voidShowTrayContextMenu(HWND hwnd){POINT pt;GetCursorPos(&pt);HMENU hMenu=CreatePopupMenu();AppendMenu(hMenu,MF_STRING,IDM_RESTORE,L"还原窗口");AppendMenu(hMenu,MF_SEPARATOR,0,NULL);AppendMenu(hMenu,MF_STRING,IDM_ABOUT,L"关于");AppendMenu(hMenu,MF_SEPARATOR,0,NULL);AppendMenu(hMenu,MF_STRING,IDM_EXIT,L"退出");SetForegroundWindow(hwnd);TrackPopupMenu(hMenu,TPM_RIGHTBUTTON,pt.x,pt.y,0,hwnd,NULL);SetForegroundWindow(hwnd);DestroyMenu(hMenu);}LRESULT CALLBACKWndProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){// 处理任务栏重启消息(不能用 switch)if(uMsg==g_uTaskbarRestart){if(g_inTray)AddTrayIcon(hwnd);return0;}switch(uMsg){caseWM_CREATE:{// 创建提示文本CreateWindowEx(0,L"STATIC",L"点击窗口关闭按钮最小化到系统托盘。\r\n\r\n"L"托盘图标右键菜单可还原或退出。\r\n"L"双击托盘图标还原窗口。",WS_CHILD|WS_VISIBLE|SS_LEFT,20,20,340,80,hwnd,NULL,((LPCREATESTRUCT)lParam)->hInstance,NULL);return0;}caseWM_CLOSE:// 隐藏窗口并添加托盘图标if(!g_inTray){AddTrayIcon(hwnd);g_inTray=TRUE;// 首次最小化时显示气泡提示NOTIFYICONDATA nid={};nid.dwSize=sizeof(NOTIFYICONDATA);nid.hWnd=hwnd;nid.uID=ID_TRAY_ICON;nid.uFlags=NIF_INFO;nid.dwInfoFlags=NIIF_INFO;wcscpy_s(nid.szInfoTitle,L"托盘示例");wcscpy_s(nid.szInfo,L"程序已最小化到系统托盘,双击图标可还原");Shell_NotifyIcon(NIM_MODIFY,&nid);}ShowWindow(hwnd,SW_HIDE);return0;caseWM_TRAYICON:{switch(LOWORD(lParam)){caseWM_LBUTTONDBLCLK:ShowWindow(hwnd,SW_SHOW);SetForegroundWindow(hwnd);RemoveTrayIcon(hwnd);g_inTray=FALSE;break;caseWM_RBUTTONUP:ShowTrayContextMenu(hwnd);break;}return0;}caseWM_COMMAND:{switch(LOWORD(wParam)){caseIDM_RESTORE:ShowWindow(hwnd,SW_SHOW);SetForegroundWindow(hwnd);RemoveTrayIcon(hwnd);g_inTray=FALSE;break;caseIDM_ABOUT:MessageBox(hwnd,L"系统托盘示例程序\r\n版本 1.0\r\n\r\n"L"演示 Shell_NotifyIcon 的用法",L"关于",MB_OK|MB_ICONINFORMATION);break;caseIDM_EXIT:RemoveTrayIcon(hwnd);DestroyWindow(hwnd);break;}return0;}caseWM_DESTROY:RemoveTrayIcon(hwnd);PostQuitMessage(0);return0;}returnDefWindowProc(hwnd,uMsg,wParam,lParam);}intWINAPIwWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PWSTR pCmdLine,intnCmdShow){g_uTaskbarRestart=RegisterWindowMessage(L"TaskbarCreated");WNDCLASS wc={};wc.lpfnWndProc=WndProc;wc.hInstance=hInstance;wc.lpszClassName=L"TrayDemoClass";wc.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);wc.hCursor=LoadCursor(NULL,IDC_ARROW);wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);RegisterClass(&wc);g_hWnd=CreateWindowEx(0,L"TrayDemoClass",L"系统托盘示例",WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX,CW_USEDEFAULT,CW_USEDEFAULT,400,180,NULL,NULL,hInstance,NULL);if(g_hWnd){ShowWindow(g_hWnd,nCmdShow);UpdateWindow(g_hWnd);MSG msg={};while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}}return0;}

代码要点解析

  1. WM_CLOSE 拦截:不调用 DefWindowProc(它会 DestroyWindow),而是隐藏窗口并添加托盘图标。首次最小化时还弹一个气泡通知。

  2. NOTIFYICON_VERSION_4:设置后,回调消息的 lParam 低 16 位是鼠标消息,高 16 位是图标坐标(某些场景下)。推荐总是设置这个版本。

  3. SetForegroundWindow:显示右键菜单前后各调用一次,确保菜单行为正常。

  4. WM_TASKBARCREATED:用 RegisterWindowMessage 注册,在 WndProc 中用 if(不是 switch)检查。

  5. WM_DESTROY 兜底:即使走了 IDM_EXIT 之外的路径(比如 Task Manager 强制关闭),WM_DESTROY 也会确保删除托盘图标。


常见陷阱

陷阱一:cbSize / dwSize 字段错误

NOTIFYICONDATA 结构体在不同 Windows SDK 版本中大小不同。必须用sizeof(NOTIFYICONDATA)初始化,不要手动填写数值。

陷阱二:程序退出未删除图标

在 WM_DESTROY 中一定要调用Shell_NotifyIcon(NIM_DELETE, ...)。否则图标会留在托盘里,直到用户把鼠标移上去触发系统清理——很不专业。

陷阱三:hIcon 资源管理

NOTIFYICONDATA.hIcon是共享引用——系统会复制图标,所以你可以在设置后安全地DestroyIcon(如果你是自己 LoadImage 创建的)。但如果你传的是通过LoadIcon加载的系统图标(如IDI_APPLICATION),不要DestroyIcon——那些是系统资源。

陷阱四:64 位/32 位跨进程拖放

64 位程序的托盘图标右键菜单如果使用了 drag-drop 功能,需要额外的消息过滤处理。这是一个冷门但棘手的兼容性问题。


后续可以做什么

到这里,系统托盘的知识就讲完了。你现在应该能够添加/删除托盘图标、处理托盘回调消息(单击、双击、右键菜单)、显示气泡通知、正确处理任务栏重启、实现"最小化到托盘"的行为模式。

下一篇文章,我们会聊一个在文件操作场景中非常实用的功能——拖放(Drag & Drop)。你将学会如何让你的窗口接受文件拖入(WM_DROPFILES)以及如何实现完整的 OLE 拖放协议(IDropTarget)。

在此之前,建议你做一些练习巩固今天的知识:

  1. 基础练习:修改示例,给托盘图标使用自定义图标(从资源文件加载),而不是系统默认图标
  2. 进阶练习:实现一个"番茄钟"托盘程序——设置 25 分钟倒计时,时间到了弹出气泡通知,右键菜单可以暂停/重置/退出
  3. 挑战练习:让托盘图标动态变化(比如在"工作"和"休息"状态之间切换不同图标),并通过图标变化反映当前状态

相关资源

  • Shell_NotifyIcon function - Microsoft Learn
  • NOTIFYICONDATA structure - Microsoft Learn
  • Taskbar Notifications - Microsoft Learn
  • RegisterWindowMessage function - Microsoft Learn

相关阅读

  1. 现代Qt开发教程(新手篇)1.15——正则与文本处理 - 相似度 100%
  2. 通用GUI编程技术——Win32 原生编程实战(五十四)——Hook 机制 - 相似度 100%
  3. 通用GUI编程技术——图形渲染实战(四十四)——D3D12命令列表、队列与围栏:GPU同步核心 - 相似度 100%
http://www.jsqmd.com/news/890167/

相关文章:

  • 如何用BilibiliDown高效提取B站无损音频:4步实现音乐收藏
  • 南京黄金闲置快速变现,福运来免费上门回收省心靠谱 - 黄金回收
  • 辟谣科普|别再混淆!巴马百年≠百岁人饮用水,二者无任何关联 - 中媒介
  • 轻量级CNN在电信日志分类中超越大语言模型的实践与思考
  • GHelper华硕笔记本性能优化终极指南:轻量控制工具完整使用教程
  • CNN-LSTM混合模型在漏洞检测中的应用与实战
  • 如何在5分钟内用jsPsych创建你的第一个在线行为实验?终极指南
  • 40nm芯片设计实战:搞定SRAM宏模块的电源布线,避开M4层这个‘禁区’
  • 2026新榜单:朔州CMA甲醛检测治理公司及洁净室公共卫生检测报告排行榜(2026版) - 金诚回收
  • Trelby完整指南:免费开源剧本创作工具的终极使用教程
  • 西谷制冷是做什么的?
  • 知识图谱与Transformer融合:构建可解释的智能医疗对话系统
  • 数据科学家必备的时序信号处理实战指南
  • ARM QoS-400与I/O虚拟化:解决实时系统内存争用的软硬件协同方案
  • RimWorld Mod开发:别再混淆了!游戏里的Comp组件和Unity的Component根本不是一回事
  • 2026长沙封阳台及系统门窗测评榜单|本地门店实景实测靠谱推荐 - 涂伟
  • 海康工业相机Bayer转RGB实战:用OpenCV和Halcon处理图像格式的3种方法对比
  • 用ESP32-CAM和ST7789屏做个迷你监控器:手把手教你显示OV2640图像(附完整代码)
  • FPGA入门实战:基于Alchitry Au与Vivado的VHDL计数器设计与烧录全流程
  • AI气象预测革命:UT-GraphCast数据集与图神经网络技术解析
  • 2026年超声波明渠流量计十大国产品牌综合实力排名与专业选型指南 - 仪表品牌排行榜
  • Zephyr-7B实战指南:DPO对齐、GQA加速与生产级微调部署
  • 基于BERT与任务清晰度特征的众包软件开发周期预测模型实践
  • Docker Build Secrets 实战:构建时密钥零持久化安全方案
  • 3分钟掌握Book118文档下载器:免费获取可预览文档的终极指南
  • 3分钟学会iOS应用签名:这个免费工具让你告别复杂命令行!
  • 软件开发领域工作流重构
  • 如何在Windows和Linux上快速解锁VMware的macOS支持:完整指南
  • 全纯嵌入法在交直流混合电网潮流计算中的统一建模与效率优化
  • 书匠策AI到底是个啥?一个论文科普博主的“拆机式“深度测评