WTL的历史和渊源就不介绍了。如果要谈论一门编程语言,基础的内容却必不可少--总得有些东西把所有的一切都串起来才行。
我从接触WTL到今天,大概有7-8年了。6年前,我决定放弃了使用多年的MFC彻底转向WTL,做这个决定的一个基本的原因就是自由,当你使用WTL编码,你可以感受到操作系统的律动,你会觉得自己在跟操作系统协作。你知道自己在做什么,也能够预计即将发生什么,这感觉相当好。
那么WTL都能做什么呢?答案是--任何事情。如果你觉得某样事情还不能做,那基本上只是表示你还有东西需要学,仅此而已。
我目前使用的库是WTL8.1,由于我必须使用window 2003 server版的操作系统,有些东西我甚至还没有去尝试。 好吧,我们还是进入正题,看看WTL究竟是怎么回事,他是怎么开始的,如何工作?
在开始之前,希望你已经了解Win32 windows编程,起码你知道,向windows注册一个窗口,消息循环,消息处理诸如此类的事情。如果你使用的是VS2008或者更高版本的开发环境,你可以生成一个win32程序看看。
WTL实际上必须完成那些基础工作,我们现在来看看她是怎么做的。
CAppModule _Module;
int Run(LPTSTR /*lpstrCmdLine*/ = NULL, int nCmdShow = SW_SHOWDEFAULT)
{
CMessageLoop theLoop;
_Module.AddMessageLoop(&theLoop);
CMainFrame wndMain;
if(wndMain.CreateEx() == NULL)
{
ATLTRACE(_T("Main window creation failed!\n"));
return 0;
}
wndMain.ShowWindow(nCmdShow);
int nRet = theLoop.Run();
_Module.RemoveMessageLoop();
return nRet;
}
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPTSTR lpstrCmdLine, int nCmdShow)
{
HRESULT hRes = ::CoInitialize(NULL);
// If you are running on NT 4.0 or higher you can use the following call instead to
// make the EXE free threaded. This means that calls come in on a random RPC thread.
// HRESULT hRes = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
ATLASSERT(SUCCEEDED(hRes));
// this resolves ATL window thunking problem when Microsoft Layer for Unicode (MSLU) is used
::DefWindowProc(NULL, 0, 0, 0L);
AtlInitCommonControls(ICC_COOL_CLASSES | ICC_BAR_CLASSES); // add flags to support other controls
hRes = _Module.Init(NULL, hInstance);
ATLASSERT(SUCCEEDED(hRes));
int nRet = Run(lpstrCmdLine, nCmdShow);
_Module.Term();
::CoUninitialize();
return nRet;
}
当你用WTL向导建立一个SDI程序,你会看到向导产生了这样的代码。开始的时候,你大概知道也许就可以了,但是总究有一天你会想去弄明白这些代码的意义。这个程序框架所带来的效果,就是你只需要专注去处理 CMainFrame 这个类就行了。就算你不管上面这些代码,程序也会运行的很好。
将目光放在_tWinMain ,因为这里是整个程序的入口点。
1. CoInitializeEx是Windows提供的API函数,为当前线程初始化COM库并设置并发模式。应用程序调用com库中的函数(除CoGetMalloc和内存分配函数)之前必须初始化com库。在应用程序中使用COM库,至少要调用一次CoInitializeEx函数(通常也就调用一次)。如果传入参数的并发标志相同,单个线程也可以多次调用该函数,但后来有效的调用将返回 S_FALSE。在正常关闭COM库情况下,每一个CoInitialize 或者CoInitializeEx的成功的调用(也包含返回S_FALSE的调用),都必须用通Uninitialize函数来结束。
使用函数 CoInitializeEx的代码的前面需要包含于处理标志 #define _WIN32_DCOM
基于线程的并发模式一旦设置,将不能再改变。一个线程上调用CoInitializeEx如果与原来调用设置的并发模式不一致,将会失败并返回RPC_E_CHANGED_MODE。
2. 注册通用控件(AtlInitCommonControls):
InitCommonControlsex
InitCommonControlsex
函数原形:BOOL WINAPI InitCommonControlsEx(LPINITCOMMONCONTROLSEX lpInitCtrls);
pInitCtrls参数指向一个INITCOMMONCONTROLSEX结构:
typedef struct tagINITCOMMONCONTROLSEX {
DWORD dwSize; // size of this structure结构长度
DWORD dwICC; // flags indicating which classes to be initialized需要初始化的类
} INITCOMMONCONTROLSEX, *LPINITCOMMONCONTROLSEX;
结构中的dwICC字段指定了需要注册的扩展通用控件类,与InitCommonControls注册所有它支持的通用控件类不同,InitCommonControlsEx函数只注册dwICC字段指明的扩展通用控件类,字段可以是下面取值的组合:
● ICC_BAR_CLASSES——注册工具栏、状态栏、Trackbar和Tooltip类。
● ICC_COOL_CLASSES——注册Rebar类。
● ICC_DATE_CLASSES——注册Date and Time Picker类。
● ICC_HOTKEY_CLASS——注册Hot Key类。
● ICC_INTERNET_CLASSES——注册IP Address Picker类。
● ICC_LISTVIEW_CLASSES——注册ListView和Header类。
● ICC_PAGESCROLLER_CLASS——注册Pager类。
● ICC_PROGRESS_CLASS——注册Progress Bar类。
● ICC_TAB_CLASSES——注册Tab和Tooltip类。
● ICC_TREEVIEW_CLASSES——注册TreeView和Tooltip类。
● ICC_UPDOWN_CLASS——注册Up-Down类。
● ICC_USEREX_CLASSES——注册ComboBoxEx类。
● ICC_WIN95_CLASSES——注册InitCommonControls函数注册的所有类。
InitCommonControlsEx函数是InitCommonControls函数的扩充
消息循环类:CMessageLoop
观察一下第一个函数Run,它在 _tWinMain 中被调用, 这个地方用来处理命令行和初始化问题,例如建立特定的窗口并设置窗口的状态。这个Run函数内部,首先建立一个一个消息循环对象,把它附加给实体全局变量,由于实体全局变量已经初始化, 也就是线程已经运行, 这时候定义 CMainFrame wndMain, 并且建立这个窗口,然后显示他,最后开启消息循环函数的Run,观察一下 CMessageLoop 分类里面的Run函数,对比win32程序,看上几遍,你应该就已经理解这里发生了什么事情了。
全局变量 CAppModule _Module
因为目前还不需要深究深层的运行的机制。这里就简单的说一下,能理解就可以了。所谓实体,其实就是一个进程,也就是指一个运行的程序。在windows下,它有自己的运行环境,有自己的独立内存空间,有自己的消息循环。大致看一下CAppModule 这个类就知道了。他有消息循环,系统设置改变通知,空闲处理,模块也有个初始化和释放的功能要调用,大致可以理解为创建线程和销毁线程的作用。而在这两者之间,就是消息循环了,我们编写应用的全部工作就在这个阶段。