ARTICLE DETAIL

资讯详情

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

VS2019配置WTL10完整指南:工具集、Unicode与常见错误解决

VS2019配置WTL10完整指南:工具集、Unicode与常见错误解决 简介WTL10.0 是微软 Windows Template Library 面向 C 开发者的最终版本专注解决 Visual Studio 2019 环境下轻量级 Windows 原生应用的构建需求。相比 MFC它更精简且类设计直接映射 Windows API并借助模板类提供高度定制性适合希望兼顾性能、可控性与代码可维护性的中高级 C 程序员使用 WTL 还能减少不必要的依赖使程序体积更小。资源共 296 个文件压缩包约 704KB核心文件包括 104 个头文件、39 个 CPP 源文件以及大量 bmp 位图、ico 图标、rc 资源脚本和 vcxproj/sln 工程文件构成一套体系完整、可编译、可参考的 WTL10 开发素材。压缩包内含安装向导、示例项目、头文件与库文件、API 文档和更新日志内容预览中的工具栏和位图资源则展示了典型界面组件用法同时支持 Unicode并能与 ATL、MFC 或 Boost 配合使用扩展场景丰富。目前已有325人学习下载适合刚转向 VS2019 并希望使用 WTL 搭建桌面程序的开发者参考。1. WTL10.0 最终版VS2019 老界面开发绕不开的这个库WTL10.0 被很多 Windows 客户端开发者当成“最后一个能干净跑在 VS2019 上的 WTL 版本”。之前我接了一个遗留项目代码还是 WTL9.1 写的里面全是自定义控件和自绘逻辑直接在 VS2019 里打开编译错误刷了上百条改了两天都不干净。后来换到 WTL10.0 的完整资源包平台工具集切到 v142、字符集改成 Unicode再补几个预处理宏整个项目半天就编过了。这份资源解决的就是这一类问题老 WTL 代码在 VS2019 下重新站起来或者新工程直接基于 WTL10 开工。它适合接手 WTL 老代码需要编译环境的人想在 VS2019 里新写 WTL 界面代码的人以及不喜欢 MFC 重量感也不愿意裸写 Win32 的 C 开发者。接下来不讲虚的直接按版本匹配、工程配置、踩坑排错、进阶技巧往下拆。2. 把 WTL10 用到 VS2019 之前先对清版本和工具集2.1 WTL、ATL 和 Win32为什么说 WTL 只是“半套框架”纯 Win32 写窗口要自己处理 WM_CREATE、WM_COMMAND、WM_PAINT每增加一个控件就要写一大段重复代码。WTL 做的事情是在 ATL 的基础上把这些重复劳动模板化。ATL 提供了CWindowImpl、CWinTraits这些窗口外壳WTL 则在其上提供了CWindow、CButton、CListBox等控件包装类。你写一个对话框只需要继承CDialogImplT然后消息映射表里挂宏。WTL 和 MFC 最大的差别是WTL 基本上全部由头文件实现没有独立的wtl.lib编译产物不依赖一个单独的 WTL 运行库。所以 VS2019 下配置 WTL10关键词不是“链接哪个库”而是“头文件路径、预处理宏、工具集版本”。这一点很多人想反了以为下载 WTL10 是为了拿到一个 lib实际你拿到的是几百个 .h 文件和一个完整的模板类体系。VS2019 自带的 ATL 版本是 14.x对应 v142 工具集和 WTL10 的预期环境基本一致。不过 ATL 组件在 VS2019 安装器里不是默认勾选的。如果你在安装时只装了“MSVC v142 生成工具”没有勾“适用于最新生成工具的 C ATL”后面#include atlbase.h直接报文件找不到。这个检查一定要放在第一步。2.2 核对头文件版本别只看文件夹名字WTL10 资源包解压后通常有Include、AppWiz、Samples三个目录。Include下的atlapp.h是整个库的入口。为了确认你拿到的是 WTL10 而不是 9.x我会新建一个临时工程放进下面这段代码#include atlbase.h #include atlapp.h #include atlgdi.h #include atlctrls.h #if defined(_WTL_VER) #if _WTL_VER 0x1000 #pragma message(WTL 10.x detected) #else #pragma message(WTL 9.x or below) #endif #endif #if defined(_ATL_VER) #pragma message(ATL version: _CRT_STRINGIZE(_ATL_VER)) #endif代码逻辑_WTL_VER是 WTL 头文件里定义的十六进制版本号0x1000 表示 10.0。_ATL_VER来自 ATL 头文件反映 VS2019 自带的 ATL 版本。_CRT_STRINGIZE把宏值变成字符串编译输出窗口里直接看到。核心是用编译器替我们验证版本不依赖记忆。如果输出窗口显示WTL 10.x detectedATL 版本是 14.x说明头文件路径没问题。最怕看到 9.x因为 WTL9.1 并没有针对 v142 工具集做适配直接编会触发大量模板实例化报错。这种情况要么换 WTL10要么继续用 VS2015 的老环境二选一没有第三条路。2.3 平台工具集与 SDKv142 是最省心的一档VS2019 的 C 工程可选 v140、v141、v142 三套工具集。WTL10 说“支持 vs2019”核心依据就是它的代码在 v142 下能正常解析。在项目属性常规 - 平台工具集里直接选Visual Studio 2019 (v142)。Windows SDK 版本选本机已安装的 10.0 最新版即可。VS 版本工具集ATL 版本WTL10 配置建议VS2015v14014.0.x可用需要额外装 Win10 SDKVS2017v14114.x可用建议关掉浮点弃用警告VS2019v14214.x目标环境无需补丁v141 也能编 WTL10但我在实践中遇到过_HAS_EXCEPTIONS相关的宏冲突。原因是 VS2017 的 STL 对异常处理的默认开关和 VS2019 不一致导致模板实例化时行为不同。所以不要试图用老工具集强行编译新工程把工具集统一改成 v142 是成本最低的做法。还有一点需要确认。VS2019 安装器里ATL 组件分 v142 和 v143 两档。v143 是 VS2022 的工具集WTL10 的代码在 v143 下也能编但没必要提前跨版本。我的习惯是只勾选 v142 ATL避免atlbase.h同时存在多个版本导致包含路径混乱。3. 把 WTL10 接进 VS2019从手改目录到属性表一次到位3.1 方法一附加包含目录和关键宏定义新建一个空 Windows 桌面向导工程后右键项目属性。在VC 目录 - 包含目录里把 WTL10 的Include路径加进去例如D:\dev\wtl10\Include然后在C/C - 预处理器 - 预处理器定义里加上_WTL_USE_CSTRING;_SECURE_ATL;_WTL_USE_WAIT_DIALOG_WTL_USE_CSTRING的作用是让 WTL 内部的CString使用ATL::CStringT否则老代码里很多CString还是窄字符版本遇到 Unicode 工程直接类型报错。_SECURE_ATL让 ATL 调用更安全的字符串版本压掉C4996这一族警告。_WTL_USE_WAIT_DIALOG是 WTL10 等待对话框的开关你不用CWaitDialog可以不加。加完定义后可以先编一个最简窗口验证。代码#include atlbase.h #include atlapp.h #include atlframe.h #include atlctrls.h #include atlctrlx.h class CMainWindow : public CWindowImplCMainWindow { public: BEGIN_MSG_MAP(CMainWindow) MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP() LRESULT OnPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL /*bHandled*/) { CPaintDC dc(m_hWnd); dc.DrawText(_T(WTL10 on VS2019), -1, CRect(10, 10, 200, 50)); return 0; } }; int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int) { CMessageLoop loop; CMainWindow wnd; wnd.Create(nullptr, CWindow::rcDefault, _T(WTL10)); wnd.ShowWindow(SW_SHOWNORMAL); return loop.Run(); }代码逻辑CWindowImpl接管窗口过程BEGIN_MSG_MAP到END_MSG_MAP构成消息映射这里只挂了一个WM_PAINT。_tWinMain是 Unicode/ANSI 通用的入口宏CMessageLoop是 WTL10 自带的消息循环包装。m_hWnd由CWindow基类维护不用手动存句柄。这个例子能跑通说明头文件路径和宏定义已经生效。3.2 方法二用属性表让多工程共享配置手动配置的问题在于每个新工程都要重来一遍而且容易在不同配置之间漏项。我一般会导出一份 WTL10.props 属性表放到公共目录。在 VS2019 的“属性管理器”里右键添加新项目属性表然后编辑 XML?xml version1.0 encodingutf-8? Project PropertyGroup WTLIncludeDir Condition$(WTLIncludeDir)$(USERPROFILE)\dev\WTL10\Include/WTLIncludeDir /PropertyGroup ItemDefinitionGroup ClCompile AdditionalIncludeDirectories$(WTLIncludeDir);%(AdditionalIncludeDirectories)/AdditionalIncludeDirectories PreprocessorDefinitions_SECURE_ATL1;_WTL_USE_CSTRING;%(PreprocessorDefinitions)/PreprocessorDefinitions /ClCompile Link AdditionalDependenciescomctl32.lib;htmlhelp.lib;%(AdditionalDependencies)/AdditionalDependencies /Link /ItemDefinitionGroup /Project参数说明WTLIncludeDir定义了 WTL 头文件根目录不同机器配置不同时在属性管理器窗口里直接改这个变量即可。AdditionalIncludeDirectories会把路径插入所有 .cpp 的编译命令行。comctl32.lib是公共控件的链接依赖htmlhelp.lib是 HTML Help 支持如果代码里没用HtmlHelp相关类可以把后者删掉。属性表比手动配置强在两点第一它跟.vcxproj分离不会污染工程文件第二团队其他人拉代码后只需要改一行路径变量不用逐个检查四个配置Debug/Release × x86/x64。如果你们团队的机器统一甚至可以把这个路径固定为C:\WTL10\Include直接用环境变量写死。3.3 Debug 和 Release预处理宏的差别WTL10 的代码本身不区分 Debug/Release但 CRT 行为会影响运行期表现。Debug 配置默认定义_DEBUGRelease 配置默认定义NDEBUG这两者会让ATLASSERT和CStringT内部引用计数行为不一致。绝大多数情况下不需要改。真正需要手动处理的是 Release 配置下的_CRT_SECURE_NO_WARNINGS。老 WTL 代码经常直接用wcscpy、sprintf这类函数VS2019 在 Release 下不会把它们当成错误但会给 C4996 警告。如果你们团队把警告视为错误编译直接失败。处理方式是在 Release 配置的预处理器定义里加_CRT_SECURE_NO_WARNINGS不要把这个宏放到 Debug否则一些调试信息输出会被吞掉。Debug 下我更愿意保留安全函数警告因为那能提醒你改掉老代码里不安全的写法。3.4 字符集切换从 MultiByte 到 Unicode 的迁移VS2019 新建工程默认使用 Unicode而不少 WTL10 示例代码里混用了窄字符字面量。最直接的做法是在stdafx.h或包含 WTL 头文件之前定义#define UNICODE #define _UNICODE #include tchar.h然后检查所有字符串字面量是否包了_T()。如果手里代码是从 WTL8 直接跳过来的最好先把源文件扫描一遍把所有char*相关的控件设置改成CStringW。WTL10 自带的Samples大多已经完成了这个迁移可以参考它们的写法。有个容易被忽略的地方WM_NOTIFY事件透传时LPNMHDR里的字符串字段长度会受字符集影响。如果父窗口是 Unicode子控件通知里携带的按钮文本却是 ANSI 缓冲读取时会出现乱码。这种问题编译期不报运行期才显示排查成本高。所以在配置工程时就把 Unicode 定死不要在代码里再混用char和wchar_t。4. 避坑排查VS2019 WTL10 常见问题的五个现场4.1 现象打开老工程后atlapp.h找不到或路径明明加了仍报错原因WTL10 的解压目录可能放在含空格的路径比如D:\Program Files\WTL10\IncludeVS2019 的包含路径解析在某些情况下不能正确处理带空格路径中的嵌套引用。还有可能是把路径加到了“VC 目录”而不是“C/C - 常规 - 附加包含目录”两个位置在 VS2019 里作用域不完全一样。解决把 WTL10 目录移到无空格的纯英文路径比如D:\wtl10\Include然后在“VC 目录 - 包含目录”里填一次同时在“附加包含目录”里也填一次。不要认为只加一处就够VS2019 的“VC 目录”是给外部工具用的编译器优先读“附加包含目录”。4.2 现象编译报C2065: CMainFrame 未声明或C3861: DefWindowProc 找不到标识符原因WTL 头文件有严格的包含顺序。单独的atlapp.h并不包含所有框架类CMainFrame在atlframe.h里DefWindowProc模板在atlwin.h里。很多老源文件只 include 了atlapp.h直接编报错。解决统一使用 WTL10 的Inc/atlapp.h作为入口并在其后面按依赖顺序补全。推荐最小集合#include atlbase.h #include atlapp.h #include atlwin.h #include atlframe.h #include atlctrls.h #include atlctrlx.h顺序不能乱。atlbase.h必须在最前它是 ATL 的基础atlapp.h定义应用类和消息循环atlwin.h提供窗口封装atlframe.h才是框架窗口类控件类在atlctrls.h和atlctrlx.h。如果你用到 GDI 自绘在atlctrls.h前加atlgdi.h。4.3 现象链接错误LNK2019: unresolved external symbol _WtlWakeWaitThunk原因_WTL_USE_WAIT_DIALOG宏打开了等待对话框支持但工程没有链接对应的处理逻辑。WTL10 中等待对话框是模板实现的依赖WaitDialog的相关 thunk 符号这些符号在 Debug/Release 下需要的依赖库不同。常见的是缺少comctl32.lib或uxtheme.lib。解决检查预处理器定义。如果确实用到CWaitDialog在链接器附加依赖项里加comctl32.lib;uxtheme.lib如果没有用到等待对话框最简单的方式是把_WTL_USE_WAIT_DIALOG从预处理器定义里删掉不用补任何库。我这里遇到过的项目十有八九是复制来的属性表无脑带着这个宏实际代码里压根没 new 过CWaitDialog。4.4 现象程序运行后窗口闪现就退出没有异常也没有弹窗原因运行入口写错。WTL10 默认消息循环是CMessageLoop如果工程入口函数被错写成了wWinMain且返回局部变量主窗口创建完立刻销毁。最常见的是_tWinMain的返回值直接return 0没跑loop.Run()。解决对照 WTL10 Samples 的Minimal工程入口统一写成int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int) { CMessageLoop loop; CMainWindow wnd; if (wnd.Create(nullptr, CWindow::rcDefault, _T(MainWindow)) nullptr) return 1; wnd.ShowWindow(SW_SHOWNORMAL); wnd.UpdateWindow(); return loop.Run(); }注意_tWinMain的第四个参数如果用了LPSTR而不是LPTSTR在 Unicode 构建下会触发类型不匹配导致入口解析失败。VS2019 默认的发出点wWinMainCRTStartup会优先调用宽字符版入口这里必须写LPTSTR。4.5 现象编译通过但按钮文本或标签是乱码或者GetWindowText取出来的字符串少一半原因字符集没有统一。VS2019 工程是 Unicode但 WTL10 的CButton包装类默认使用TCHAR如果你手动调用了一个char*版本的SetWindowTextA而项目又同时定义了UNICODEAPI 参数类型不匹配。更隐蔽的是WTL 的CToolTip等类内部使用CTempBufferTCHAR在 Mixed 字符集下可能按char实例化导致存不下宽字符。解决强制设置项目字符集为“使用 Unicode 字符集”并在生成的所有新代码里只使用CString由_WTL_USE_CSTRING决定的CStringTTCHAR。暂时不能改的老文件用以下代码隔离#ifdef UNICODE #define AppSetText(ctrl, text) ctrl.SetWindowText(_T(text)) #else #define AppSetText(ctrl, text) ctrl.SetWindowTextA(text) #endif不要小看这个布局问题我见过线上程序在中文系统上按钮文字变问号最后查到是源代码文件里写死了char name[32]而不是TCHAR name[32]。5. 进阶技巧用消息反射让子控件自己处理事件标准 WTL 的控件事件都通过父窗口的消息映射处理比如按钮点击父窗口收到WM_COMMAND后根据控件 ID 分支。当控件多时父窗口的消息映射越来越长维护性变差。WTL10 支持一条更干净的路REFLECT_NOTIFICATIONS()。它把控件发来的通知反射给控件自身处理父窗口只在控件没处理时才接手。使用步骤子控件类继承CWindowImpl并声明自己的消息映射然后父窗口消息映射里加上REFLECT_NOTIFICATIONS()。示例如下class CMyButton : public CWindowImplCMyButton, CButton { public: BEGIN_MSG_MAP(CMyButton) REFLECTED_COMMAND_CODE_HANDLER(BN_CLICKED, OnClicked) END_MSG_MAP() LRESULT OnClicked(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL /*bHandled*/) { SetWindowText(_T(已点击)); return 0; } };父窗口只需要class CDlg : public CDialogImplCDlg { public: BEGIN_MSG_MAP(CDlg) REFLECT_NOTIFICATIONS() END_MSG_MAP() };这样按钮的点击逻辑收在按钮类内部父窗口不认知具体业务分支。这个技巧特别适用于自定义控件库你写一个CColorButton把颜色变化、绘制逻辑、点击状态都封装进自身父窗口只管放置控件。验证方式是编译完后在按钮上点击看到控件标题变化同时父窗口的WM_COMMAND处理没有触发。这个手段不是 WTL10 独有的但 WTL10 的REFLECTED_COMMAND_CODE_HANDLER宏比老版本更完善参数多了一个bHandled引用你可以在控件处理完后把bHandled设为连续交接。从那以后我每次搭建新的 WTL10 项目都会先做一遍这种最小反射测试确认宏链路没问题再往工程里加业务代码。这个小习惯帮我少掉了至少十次“按钮点击没反应”的排查。WTL 的坑基本都在头文件顺序、字符集和预处理器宏这三个领域把它们按下述口诀走一遍“包含路径只写一次字符集只选 Unicode工具集统一 v142属性表一个变量管所有”大部分问题都能避开。希望这个资源和整理的避坑列表能帮你在 VS2019 上快速跑通 WTL10。本文还有配套的精品资源点击获取
返回列表