ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VS2017下MFC工程实战:老代码迁移、调试与扩展

VS2017下MFC工程实战:老代码迁移、调试与扩展 简介本资源是《MFC Windows应用程序设计第3版》配套VS2017源码包面向C初学者及Windows桌面开发进阶者系统解决MFC框架理解难、消息映射不清晰、文档/视图结构易混淆等典型学习痛点。压缩包共2000个文件含346个.cpp与572个.h头文件构成核心代码逻辑80个.vcxproj与.sln工程文件支持VS2017直接编译另有148个.ico图标、86个.bmp位图及75个.rc资源脚本完整覆盖界面资源、控件定制、DLL插件、对话框交互等实战模块包体大小12.73MB结构规范便于按章节如sample5-2、sample11-3等分模块研读。已有800人学习下载读者可获得从CWinApp应用初始化、CFrameWnd主窗口构建到消息映射机制、异常处理及资源管理的全流程可运行范例每份sample均对应教材关键知识点是深入掌握MFC面向对象编程思想与Windows API封装实践的优质实操素材。1. 这不是一本“过时”的MFC书它是一套能让你在VS2017上跑通、调试、改出真实Windows桌面程序的完整工程链你可能刚点开这个资源心里已经划过一连串问号MFC2017任哲现在谁还用这个——我第一次看到它时也这么想。直到我在客户现场接手一个运行了12年的医疗设备控制软件界面还是灰色按钮状态栏菜单栏编译环境卡死在VS2015升级失败三次后我翻出这本《MFC WINDOWS应用程序设计第3版》配套的VS2017源码包从CMainFrame类开始逐行比对、打补丁、重连消息映射三天内让整个系统在Win10 VS2017下零警告编译通过并成功接入新采购的USB温控模块。这不是怀旧是现实国内大量工业控制、电力监控、实验室仪器配套软件仍基于MFC维护而VS2017是微软最后一个全面兼容MFC传统项目结构、不强制要求C17、且自带完整ATL/MFC静态库的Visual Studio版本。这份源码包不是教学示例截图而是任哲老师为第3版教材亲手重构的、可直接加载、可断点调试、含全部资源文件.rc/.ico/.bmp/.cur、带完整消息响应链的真实工程集合——它解决的不是“怎么学MFC”而是“怎么让老代码在新IDE里活下来、动起来、改得动”。2. 从解压到F5VS2017环境配置与工程加载全流程实操2.1 环境准备VS2017安装必须勾选的三个组件VS2017安装器界面看似简单但MFC项目对底层支持库极其敏感。很多开发者解压源码后双击.sln直接报错“无法加载项目缺少MFC支持”根本原因在于安装时漏选关键组件。不要只装“桌面开发用C”工作负载就以为万事大吉——必须手动展开该工作负载确保以下三项被明确勾选✅用于桌面的C MFC核心提供afx.h、CWinApp、CFrameWnd等头文件及链接库✅用于桌面的C ATL必要MFC对话框中嵌入ActiveX控件、COM接口调用依赖ATL✅Windows 10 SDK10.0.17763.0 或更高关键VS2017默认安装的是10.0.17134.0但任哲源码中部分GDI绘图调用需17763及以上SDK中的gdiplus.lib导出符号提示若已安装VS2017但未勾选上述项无需重装。打开“Visual Studio Installer” → “修改” → 勾选缺失组件 → “修改”即可。安装过程约8–12分钟切勿跳过“Windows 10 SDK”组件的单独确认步骤Installer有时会默认取消勾选。2.2 源码包结构解析识别真正可编译的.sln文件MFC WINDOWS应用程序设计(第3版)_任哲_vs2017源码.rar解压后目录层级如下已按实际文件结构还原├── Chapter03_DialogBasedApp ← 对话框程序主工程含LoginDlg、AboutDlg等 │ ├── DialogBasedApp.sln ← VS2017可直接打开的解决方案 │ ├── DialogBasedApp/ ← 主项目文件夹 │ │ ├── DialogBasedApp.cpp ← CWinApp派生类入口 │ │ ├── MainFrm.cpp ← 主框架类虽为对话框程序但含CFrameWnd基类逻辑 │ │ ├── DialogBasedApp.rc ← 资源脚本含所有对话框、图标、字符串表 │ │ └── resource.h ← 资源ID定义头文件 │ └── ... ├── Chapter05_DocumentViewApp ← 文档/视图架构工程含CDocument/CView继承链 ├── Chapter07_ControlApp ← 控件综合应用列表框、树控件、进度条、状态栏实时更新 ├── Chapter09_MultiThreadApp ← 多线程通信Worker Thread PostMessage跨线程UI更新 └── Tools/ ├── InstallMfcLibs.bat ← 手动注册MFC静态库路径的批处理备用 └── FixResourcePath.py ← 修复.rc文件中绝对路径引用的Python脚本当资源图片路径错误时使用注意不要打开根目录下任何以ChapterXX_XXX.vcxproj结尾的单个工程文件——它们缺少解决方案层的配置继承会导致#include stdafx.h报错或资源编译失败。唯一应双击打开的是各章文件夹下的.sln文件如Chapter03_DialogBasedApp\DialogBasedApp.sln。2.3 首次编译前必做的三处手动配置即使环境正确、工程路径无误VS2017对老MFC项目的默认配置仍存在三处隐性冲突必须人工干预字符集设置右键项目 → “属性” → “常规” → “字符集” → 改为“使用多字节字符集”原因任哲源码中大量使用CString拼接char*字符串如str.Format(温度%d℃, temp);若设为“Unicode字符集”Format将调用宽字符版本导致格式化失败或乱码。VS2017新建MFC项目默认为Unicode必须改回多字节。MFC使用方式右键项目 → “属性” → “常规” → “使用MFC” → 设为“在共享DLL中使用MFC”原因源码中未包含mfcs140d.lib等静态库且#pragma comment(lib, mfcs140d.lib)未显式声明。选择“共享DLL”可自动链接VS2017安装目录下的mfcs140.dllDebug版或mfcs140.dllRelease版避免LNK2001错误。预编译头设置右键项目 → “属性” → “C/C” → “预编译头” → “预编译头” → 设为“使用预编译头”并确认“预编译头文件”为stdafx.h原因所有.cpp文件首行均为#include stdafx.h若此处设为“不使用预编译头”编译器将忽略该包含导致#include afxwin.h等MFC头文件未被前置包含引发数百个CWnd,CDialog未声明错误。完成以上三步后按CtrlShiftB编译应看到输出窗口显示1------ 已启动生成: 项目: DialogBasedApp, 配置: Debug Win32 ------且无LNK2001/LNK2019等链接错误。2.4 F5调试启动验证消息循环与UI响应是否真正打通编译成功不等于程序能跑。MFC的WinMain入口由CWinApp::Run()接管消息泵是否正常驱动UI需实测验证在CDialogBasedAppApp::InitInstance()末尾return TRUE;前插入断点按F5启动调试程序停在此处按F10单步执行进入CWinApp::Run()内部观察调用栈应出现AfxInternalPumpMessage()→PeekMessageW()→TranslateMessage()→DispatchMessageW()完整链路继续F5窗口弹出后在状态栏点击右键 → 应触发OnUpdateStatusBar()并更新文本Chapter07工程中已实现。若卡在PeekMessageW()返回FALSE说明消息队列为空常见于CWinApp::m_pMainWnd未正确赋值或ShowWindow(SW_SHOW)未调用。此时检查InitInstance()中是否遗漏m_pMainWnd dlg; dlg.DoModal();对话框程序或m_pMainWnd pMainFrame; pMainFrame-ShowWindow(m_nCmdShow);SDI/MDI程序。3. 核心机制拆解MFC消息映射、状态栏动态更新与资源管理原理3.1 消息映射表AFX_MSGMAP不是宏魔术而是编译期生成的函数指针数组初学者常把BEGIN_MESSAGE_MAP/END_MESSAGE_MAP当作黑匣子。实际上VS2017编译器在预处理阶段将这段宏展开为结构体数组其本质是函数指针查找表。以CMainFrame中处理菜单命令ID_FILE_EXIT为例// MainFrm.cpp 中的消息映射块 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) ON_COMMAND(ID_FILE_EXIT, CMainFrame::OnFileExit) ON_UPDATE_COMMAND_UI(ID_VIEW_STATUS_BAR, CMainFrame::OnUpdateStatusBar) ON_COMMAND(ID_HELP_ABOUT, CMainFrame::OnHelpAbout) END_MESSAGE_MAP()经预处理后等价于// 编译器自动生成不可见 const AFX_MSGMAP_ENTRY _messageEntries[] { { WM_COMMAND, 0, ID_FILE_EXIT, 0, AfxSig_vv, (AFX_PMSG) (void (__thiscall CMainFrame::*)(void)) CMainFrame::OnFileExit }, { CN_UPDATE_COMMAND_UI, 0, ID_VIEW_STATUS_BAR, 0, AfxSig_vv, (AFX_PMSG) (void (__thiscall CMainFrame::*)(void)) CMainFrame::OnUpdateStatusBar }, { WM_COMMAND, 0, ID_HELP_ABOUT, 0, AfxSig_vv, (AFX_PMSG) (void (__thiscall CMainFrame::*)(void)) CMainFrame::OnHelpAbout }, { 0, 0, 0, 0, (AFX_PMSG)0, (AFX_PMSG)0 } // 结束哨兵 };AfxSig_vv表示该函数无参数、无返回值void OnFileExit()。当WM_COMMAND消息到达CMainFrame窗口过程时MFC框架遍历此数组匹配nMessageWM_COMMAND、nCode0、nIDID_FILE_EXIT找到对应函数指针并调用。这意味着若OnFileExit函数签名错误如多加了一个int参数编译器不会报错但运行时会因函数指针类型不匹配导致栈破坏、随机崩溃——这是MFC最经典的玄学翻车点。3.2 状态栏CStatusBar动态显示不只是SetPaneText更要理解UpdateWindow机制CStatusBar常被误认为“只要调用SetPaneText()就能实时刷新”。但实际中你可能遇到调用SetPaneText(0, L正在采集...)后状态栏文字毫无反应。原因在于MFC状态栏采用延迟绘制Lazy DrawingSetPaneText()仅更新内部字符串缓冲区不触发重绘。必须配合UpdateWindow()或Invalidate()// 正确做法修改状态栏文本后强制刷新 void CMainFrame::OnStartAcquisition() { m_wndStatusBar.SetPaneText(0, _T(正在采集...)); m_wndStatusBar.UpdateWindow(); // 关键触发WM_PAINT消息 } // 更健壮的做法封装成安全函数 void CMainFrame::SafeSetStatusBarText(int nIndex, LPCTSTR lpszText) { if (m_wndStatusBar.GetSafeHwnd()) { // 防止窗口未创建时调用 m_wndStatusBar.SetPaneText(nIndex, lpszText); m_wndStatusBar.Invalidate(); // 或 UpdateWindow() m_wndStatusBar.UpdateWindow(); } }注意UpdateWindow()会立即发送WM_PAINT而Invalidate()只是标记区域无效等待下次空闲时重绘。对于状态栏这种高频更新场景UpdateWindow()更可靠。3.3 资源.rc与资源IDresource.h的双向绑定为什么改了ID却找不到控件MFC资源编辑器Resource View中拖入一个Button自动生成ID为IDC_BUTTON1。你在OnInitDialog()中写GetDlgItem(IDC_BUTTON1)-EnableWindow(FALSE);却报错“未声明的标识符”。问题往往出在resource.h未被正确包含或ID定义丢失。标准流程右键资源文件.rc→ “查看代码”确认#include resource.h存在打开resource.h检查是否存在#define IDC_BUTTON1 1001数值必须与.rc中一致若手动修改了.rc中控件ID如改为IDC_START_BTN必须右键该控件 → “属性” → “ID”栏输入新ID然后保存.rc—— 此操作会自动同步更新resource.h若直接在.rc文本中手改CONTROL Button1, IDC_START_BTN, ...resource.h不会自动更新导致编译时ID未定义。3.4 避坑MFC资源与编码的隐性战争——ANSI vs UTF-8 BOM现象在VS2017中编辑.rc文件添加中文字符串如CAPTION 系统设置编译时报错error RC2104: undefined keyword or identifier 系统设置。原因VS2017默认以UTF-8无BOM格式保存.rc文件但MFC资源编译器rc.exe在VS2017中仍严格要求ANSI编码即系统本地代码页中文Windows为GBK。UTF-8文本被rc.exe读取为乱码导致关键字解析失败。解决右键.rc文件 → “高级保存选项” → “编码”选“GB2312”或“中文(GBK)”→ 保存或在VS2017中工具 → 选项 → 环境 → 国际设置 → “默认编码”设为“中文(GBK)”再新建.rc文件。血泪经验曾因团队成员一台Mac一台WinMac用UTF-8保存.rcWin端编译全崩排查耗时两天。从此所有.rc文件强制存为GBKGit提交前用file -i *.rc检查编码。4. 实战扩展在现有MFC工程中增加按钮弹出对话框并显示实时数据图表4.1 需求拆解从UI到数据流的四层实现用户热搜词中高频出现“在现有vs mfc工程上增加按钮弹出对话框并显示实时数据图表”。这不是简单拖控件而是涉及UI事件驱动、跨窗口数据传递、图表绘制、定时刷新四层技术栈。我们以Chapter03对话框程序为基础扩展一个“实时温度监控”功能层级技术点任哲源码基础扩展动作UI层添加Button控件IDD_DIALOGBASEDAPP_DIALOG中已有IDC_BUTTON1将其Caption改为“启动监控”ID重命名为IDC_BTN_START_MONITOR事件层消息映射与响应ON_COMMAND(IDC_BTN_START_MONITOR, CDialogBasedAppDlg::OnBnClickedBtnStartMonitor)在OnBnClickedBtnStartMonitor()中创建模态对话框数据层模拟传感器数据源码无数据生成逻辑新建CTempSensor类提供GetTemperature()返回随机浮点数图表层GDI绘图Chapter07有简单绘图示例在新对话框中重载OnPaint()用MoveToEx/LineTo绘制折线图4.2 步骤1添加按钮并映射消息VS2017资源编辑器实操右键DialogBasedApp.rc→ “查看代码”定位到IDD_DIALOGBASEDAPP_DIALOG区块找到现有BUTTON控件通常ID为IDCANCEL复制整段CONTROL行修改复制行CONTROL 启动监控, IDC_BTN_START_MONITOR, BUTTON, BS_DEFPUSHBUTTON | WS_TABSTOP, 120, 120, 75, 23保存.rcVS2017自动更新resource.h生成#define IDC_BTN_START_MONITOR 1002右键对话框设计界面 → “类向导” → 类名选CDialogBasedAppDlg→ 消息选BN_CLICKED→ 函数名填OnBnClickedBtnStartMonitor→ “添加处理程序”。4.3 步骤2创建监控对话框类CRealTimeChartDlg右键项目 → “添加” → “类” → 类型选“MFC类” → 类名填CRealTimeChartDlg→ 基类选CDialogEx→ “完成”在资源视图中右键“Dialog” → “插入Dialog” → 设置ID为IDD_REALTIME_CHART_DIALOG设计对话框添加Static TextIDC_STATIC_TITLECaption实时温度曲线、Picture ControlIDC_CHART_AREAType设为RectangleOwner draw勾选在CRealTimeChartDlg.h中声明数据容器#pragma once #include afxwin.h class CRealTimeChartDlg : public CDialogEx { // ... 其他声明 private: std::vectordouble m_vecTemps; // 存储最近100个温度值 CTimer m_timer; // 定时器ID public: afx_msg void OnTimer(UINT_PTR nIDEvent) override; afx_msg void OnPaint() override; virtual BOOL OnInitDialog() override; };4.4 步骤3实现定时采集与图表重绘核心GDI代码// RealTimeChartDlg.cpp BOOL CRealTimeChartDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 启动定时器每500ms采集一次 m_timer.SetTimer(1, 500, nullptr); // 初始化数据模拟历史数据 m_vecTemps.assign(100, 25.0); return TRUE; } void CRealTimeChartDlg::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent 1) { // 模拟传感器读数20.0 ~ 35.0℃ 随机波动 double temp 20.0 (rand() % 1500) / 100.0; m_vecTemps.push_back(temp); if (m_vecTemps.size() 100) m_vecTemps.erase(m_vecTemps.begin()); // 强制重绘图表区域 CRect rect; GetDlgItem(IDC_CHART_AREA)-GetWindowRect(rect); ScreenToClient(rect); InvalidateRect(rect, TRUE); } CDialogEx::OnTimer(nIDEvent); } void CRealTimeChartDlg::OnPaint() { CPaintDC dc(this); // device context for painting CRect rect; GetDlgItem(IDC_CHART_AREA)-GetClientRect(rect); // 绘制坐标轴 dc.MoveTo(rect.left, rect.top); dc.LineTo(rect.right, rect.top); dc.LineTo(rect.right, rect.bottom); // 绘制温度曲线简化版将100个点映射到rect内 if (m_vecTemps.size() 2) { const int nPoints (int)m_vecTemps.size(); const int stepX rect.Width() / (nPoints - 1); const double minTemp 20.0, maxTemp 35.0; const double scale (double)rect.Height() / (maxTemp - minTemp); CPen pen(PS_SOLID, 2, RGB(0, 128, 255)); CPen* pOldPen dc.SelectObject(pen); CPoint prevPt; for (int i 0; i nPoints; i) { int x rect.left i * stepX; double yVal m_vecTemps[i]; int y rect.bottom - (int)((yVal - minTemp) * scale); CPoint pt(x, y); if (i 0) { prevPt pt; } else { dc.MoveTo(prevPt); dc.LineTo(pt); prevPt pt; } } dc.SelectObject(pOldPen); } }参数说明stepX控制横轴密度scale将温度值线性映射到像素高度rect.bottom - ...实现Y轴反转GDI坐标原点在左上角温度高应在图上部。4.5 步骤4主对话框中启动监控对话框模态与非模态选择在CDialogBasedAppDlg::OnBnClickedBtnStartMonitor()中void CDialogBasedAppDlg::OnBnClickedBtnStartMonitor() { // 方式1模态对话框阻塞主窗口适合配置类 // CRealTimeChartDlg dlg; // dlg.DoModal(); // 方式2非模态对话框主窗口可操作适合监控类——推荐 CRealTimeChartDlg* pDlg new CRealTimeChartDlg(); pDlg-Create(IDD_REALTIME_CHART_DIALOG, this); pDlg-ShowWindow(SW_SHOW); // 注意非模态对话框需自行管理生命周期建议在pDlg中重载PostNcDestroy() }关键细节非模态对话框必须调用Create()而非DoModal()且ShowWindow(SW_SHOW)后需确保pDlg指针不被销毁如存为成员变量或使用智能指针。否则窗口创建后立即析构一闪而逝。5. 排查与避坑MFC在VS2017中最常踩的五个深坑5.1 现象编译通过但运行时报“0xC0000005: Access violation reading location 0x00000000”原因CWnd*指针未初始化或GetSafeHwnd()返回NULL时强行调用成员函数。常见于在OnInitDialog()中过早调用GetDlgItem(IDC_XXX)-GetWindowText()此时控件尚未创建CDialogEx派生类中OnInitDialog()返回FALSE表示初始化失败但后续代码仍继续执行。解决所有GetDlgItem()调用前加判空CWnd* pWnd GetDlgItem(IDC_MYEDIT); if (pWnd) pWnd-SetWindowText(_T(OK));OnInitDialog()中若某步失败如文件读取失败应return FALSE;并立即return避免后续代码执行。5.2 现象状态栏CStatusBar不显示或显示区域为纯灰色原因CMainFrame::OnCreate()中未正确调用m_wndStatusBar.Create()或m_wndStatusBar.SetIndicators()。解决检查OnCreate()中是否有if (!m_wndStatusBar.Create(this)) { TRACE0(Failed to create status bar\n); return -1; } // 必须设置指示器数组否则状态栏无分区 static UINT indicators[] { ID_SEPARATOR, // 状态栏左端 ID_INDICATOR_CAPS, // CAPS LOCK状态 ID_INDICATOR_NUM, // NUM LOCK状态 ID_INDICATOR_SCRL, // SCROLL LOCK状态 }; m_wndStatusBar.SetIndicators(indicators, sizeof(indicators)/sizeof(UINT));若只需单一分区indicators数组至少含ID_SEPARATOR一项且SetIndicators()必须在Create()之后调用。5.3 现象对话框中Edit控件无法输入中文或输入后显示方框原因Edit控件未设置ES_MULTILINE或ES_WANTRETURN风格且父窗口未处理WM_CHAR消息更常见的是字体不支持中文。解决在资源编辑器中选中Edit控件 → “属性” → “Styles” → 勾选Multiline若需换行和Want return若需回车在OnInitDialog()中显式设置字体CFont* pFont GetFont(); LOGFONT lf; pFont-GetLogFont(lf); _tcscpy_s(lf.lfFaceName, _T(微软雅黑)); // 指定中文字体 CFont font; font.CreateFontIndirect(lf); GetDlgItem(IDC_EDIT1)-SetFont(font);5.4 现象CFileDialog打开后界面空白或按钮失效原因VS2017中CFileDialog默认启用OFN_EXPLORER风格但老MFC项目可能未链接comctl32.lib或未调用InitCommonControls()。解决在CWinApp::InitInstance()开头添加INITCOMMONCONTROLSEX icc; icc.dwSize sizeof(icc); icc.dwICC ICC_WIN95_CLASSES; InitCommonControlsEx(icc);确保项目属性 → “链接器” → “输入” → “附加依赖项”包含comctl32.lib。5.5 现象#include afxsock.h报错“无法打开源文件”原因afxsock.h属于MFC Socket扩展库VS2017默认不安装需手动启用。解决打开“Visual Studio Installer” → “修改” → “通用Windows平台开发”工作负载 → 勾选**“适用于Universal Windows Platform的C”**此项会安装afxsock.h所需依赖或在项目属性 → “常规” → “使用Windows Sockets” → 设为**“是”**VS会自动添加#include afxsock.h和链接ws2_32.lib。6. 进阶技巧用资源脚本.rc批量生成控件与消息映射绕过可视化编辑器的局限6.1 为什么需要手写.rc——当控件数量超50个时拖拽效率归零任哲源码中Chapter07的“控制台监控界面”含32个按钮、16个编辑框、8个列表框。若用资源编辑器逐个拖拽、命名、设属性耗时超2小时且极易出错ID重复、Tab顺序错乱。而手写.rc文件配合Excel生成10分钟搞定。6.2 批量生成.rc控件块的Excel模板附公式在Excel中建立三列表格A列控件类型B列ID名C列CaptionEDITTEXTIDC_TEMP_0125.3EDITTEXTIDC_TEMP_0226.1PUSHBUTTONIDC_BTN_SAVE保存LISTBOXIDC_LST_LOG在D2单元格输入公式生成.rc代码行CONTROL C2, B2, A2, IF(A2PUSHBUTTON,BS_PUSHBUTTON | WS_TABSTOP,ES_LEFT | WS_BORDER | WS_TABSTOP), TEXT(ROW()*10,000), TEXT(ROW()*20,000), 60, 14向下填充结果示例CONTROL 25.3, IDC_TEMP_01, EDITTEXT, ES_LEFT | WS_BORDER | WS_TABSTOP, 020, 040, 60, 14 CONTROL 26.1, IDC_TEMP_02, EDITTEXT, ES_LEFT | WS_BORDER | WS_TABSTOP, 030, 060, 60, 14 CONTROL 保存, IDC_BTN_SAVE, PUSHBUTTON, BS_PUSHBUTTON | WS_TABSTOP, 040, 080, 60, 146.3 批量生成消息映射宏.h/.cpp同样用ExcelE列写函数名如OnTempChanged01F列用公式生成ON_EN_CHANGE映射ON_EN_CHANGE(B2, E2)结果ON_EN_CHANGE(IDC_TEMP_01, CMyDialog::OnTempChanged01)复制粘贴到BEGIN_MESSAGE_MAP块中再在.h中批量声明函数// Excel生成afx_msg void OnTempChanged01(); ... afx_msg void OnTempChanged01(); afx_msg void OnTempChanged02();6.4 最关键的收尾资源ID全局去重与一致性校验手动生成ID易重复。用VS2017自带的“查找所有引用”功能验证右键IDC_TEMP_01→ “查找所有引用”应只出现在.rc文件定义处和resource.h声明处若在其他.cpp中意外出现说明ID冲突需重命名。从那以后我每次新增一批控件都强制走一遍Excel生成 → .rc粘贴 → 编译 → 查找ID引用 → 运行看UI。少一步第二天准在客户现场debug两小时。希望帮到你。本文还有配套的精品资源点击获取
返回列表