深入剖析WTL.doc
|
|
|
- 脂簪 路
- 8 years ago
- Views:
Transcription
1 深入剖析 WTL Win32 模型 WTL 是 Windows Template Library 的缩写 最初,WTL 是由微软的 ATL(Active Template Librar y) 小组成员开发的一个 SDK 例子 主要是基于 ATL 的对 Win32 API 的封装 从 2.0 后, 功能逐步完善, 成为了一个完整的支持窗口的框架 (windows framework) 与 MFC 相比较, 功能并没有 MFC 完善 比如 MFC 支持 doc/view 架构, 而 WTL 并不支持 同时,WT L 也没有 Microsoft 的官方支持 但是,WTL 是基于模版 (template) 的, 其应用程序最小只有 24KB, 同 时不象 MFC, 依赖 DLL(MFC 需要 MFC42.DLL) WTL 系列文章对 WTL 进行了深入剖析, 希望能方便您对 WTL 有一个深入的理解, 从而能得心应手的开发 出高质量的 Windows 应用程序 Win32 的线程模型 为了便于以后的探讨, 首先看一下 Win32 的线程模型 一个 Win32 应用程序 ( 或进程 ) 是由一个或多个并发的线程组成的, 其中第一个启动的线程称为主线程 Win32 定义了两种类型的线程, 界面线程和工作线程 Win32 的每个进程可以有一个或多个界面线程和 / 或多个工作线程 界面线程拥有一个或多个窗口, 拥有一个消息队列和其它属于界面线程的元素 工作线 程就是一般的线程, 它没有窗口, 没有消息队列 界面线程通常有一个或几个窗口 当某一个窗口有消息时, 界面线程会调用相应的窗口函数 (Windows Pr ocess) 来处理该事件 由于某消息循环由它界面线程处理, 同时不必在乎是哪个线程发送消息的, 因此, Windows 会保证线程间的同步问题 对于工作线程, 线程间的同步必须由程序员来实现 尽可能避免死锁和竞争出现 Win32 应用程序模型
2 Win32 应用程序可以分成两大类 : 控制台程序 (console application) 和窗口界面程序 (windows GUI ap plication) 控制台程序的入口函数是 main(), 窗口界面程序的入口函数是 WinMain() 入口函数就是程序的主线程的运行起点 这里讨论的开发框架 (Framework) 是针对窗口界面程序的 窗口界面程序通常分成以下几类 :SDI, MDI, multi-sdi, 和 Dialog 应用程序 SDI(Single Document Interface) 应用程序通常只有一个主窗口 ( 通常是一个框架窗口,Frame Windo w) 框架窗口包含菜单 工具栏 状态栏和称为视 (View) 的客户工作区 multi-sdi(multiple Threads SDI) 应用程序有框架个主窗口 比如 IE 浏览器, 使用 " 文件 / 新建 / 窗口 " 命 令后, 会出现另一个 IE 窗口 MDI(Multiple Document Interface) 应用程序有一个主框架窗口, 但有多个子框架窗口 每个子窗口都 有自己的视 (View) Dialog 应用程序是基于对话框的 通常一个简单的 SDI 应用程序由两个函数组成 一个是应用程序入口函数 WinMain(), 另一个是该应用程 序窗口的窗口函数 程序 ( 主线程 ) 从入口函数开始运行 在该函数中, 首先是注册并创建一个主窗口 然后, 启动消息循环 消息循环中, 根据不同的消息, 将消息发送到窗口函数中处理 当消息是退出消息时, 该入口函数会退出 消息循环, 然后结束程序 下面是一个最简单的 Windows 界面应用程序
3 // 应用程序入口函数 int APIENTRY WinMain(HINSTANCE hinstance, HINSTANCE hprevinstance, LPSTR lpcmdline, int ncmdshow) MSG msg; //1. 注册窗口类 WNDCLASSEX wcex; wcex.cbsize = sizeof(wndclassex); wcex.style = CS_HREDRAW CS_VREDRAW; wcex.lpfnwndproc = (WNDPROC)WndProc; // 指定窗口函数 wcex.cbclsextra = 0; wcex.cbwndextra = 0; wcex.hinstance = hinstance; wcex.hicon = LoadIcon(hInstance, (LPCTSTR)IDI_HELLOWORLD); wcex.hcursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrbackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszmenuname = (LPCSTR)IDC_HELLOWORLD; wcex.lpszclassname = szwindowclass; wcex.hiconsm = LoadIcon(wcex.hInstance,(LPCTSTR)IDI_SMALL); RegisterClassEx(&wcex); //2. 创建一个该类型的窗口 HWND hwnd; hwnd = CreateWindow(szWindowClass,szTitle,
4 WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hinstance, NULL); if (!hwnd) return FALSE; //3. 按 ncmdshow 所指定的方式显示窗口 ShowWindow(hWnd, ncmdshow); UpdateWindow(hWnd); //4. 启动消息循环, 将消息发送给相应的窗口函数 while (GetMessage(&msg, NULL, 0, 0)) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; // 窗口函数 LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) PAINTSTRUCT ps; HDC hdc; char* szhello = "Hello, world!"; switch (message) case WM_PAINT: hdc = BeginPaint(hWnd, &ps);
5 RECT rt; GetClientRect(hWnd, &rt); DrawText(hdc, szhello, strlen(szhello),&rt,dt_center); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); // 退出应用程序 break; default: return DefWindowProc(hWnd, message, wparam, lparam); return 0; 上面程序的执行过程如下 : 1 注册窗口类 在使用 CreateWindwo() 或 CreateWindowEx() 创建窗口时, 必须提供一个标识窗口类的字符串 该窗口 类定义了一些窗口的基本属性 其中一个重要的工作是向操作系统提供窗口函数 该函数是回调函数, 用 于处理发送给该窗口的消息 在上面程序中, 仅仅简单的处理了两个消息 一个是向窗口区域画出 "Hello world." 字符串 另一个是当 窗口撤消时, 向应用程序发送 " 退出应用程序 " 消息 2 创建窗口
6 3 显示窗口 4 启动消息循环, 分发并处理消息 while (GetMessage(&msg, NULL, 0, 0)) TranslateMessage(&msg); DispatchMessage(&msg); 在上述消息循环代码中, 调用 GetMessage() 从线程的消息队列中取出一条消息 如果消息为 0( 由窗口 函数处理 "WM_DESTROY" 消息时发送的 "PostQuitMessage(0)"), 会退出消息循环 然后调用 TranslateMessage() 翻译消息 翻译后, 再调用 DispatchMessage() 将该消息分发至相应的窗口函数进行处理 ( 实际上 DispatchMes sage() 将该消息作为参数调用对应的窗口的窗口函数, 这就是分发的实质 ) 深入剖析 WTL 如何封装 Windows 界面程序 首先还是让我们来看看 WTL 是怎样封装应用程序线程的 和 ATL 类似,WTL 使用一个 _Module 全局变量来保存全局数据, 并通过它来引用应用程序级的代码 在 WTL 中, 该变量是 CAppModule 或 CServerAppModule 的实例 后者通常作为 COM 服务器的应用程 序 每个应用程序都有一个或多个界面线程组成 首先剖析一下 WTL 是怎样管理只有一个界面线程的 ( 除了 Mutli-SDI 应用程序 )
7 单个界面线程的封装 先看应用程序的入口函数 CAppModule _Module; 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)); // 程序逻辑, 调用全局函数 Run() int nret = Run(lpstrCmdLine, ncmdshow); // 终止模块
8 _Module.Term(); ::CoUninitialize(); return nret; 在上面的代码中,_Module 是一个全局变量, 这里是 CAppModule 的一个实例 而 CAppModule 类是 对应用程序的封装 它封装了诸如初始化模块等功能 一个 _Module 还维持一个消息循环 Map 入口函数名为 _twinmain() 当使用 UNICODE 时, 编译器会将它替换为 wwinmain(), 否则, 为 WinM ain() 入口函数其实就是主线程 (_Module) 的起始点 在该函数中, 最关键的逻辑是调用了全局函数 Run(), 它是核心程序逻辑所在 下面来看一下这个函数 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);
9 int nret = theloop.run(); _Module.RemoveMessageLoop(); return nret; 该函数创建了一个 CMessageLoop 实例, 该实例包含了这个线程的消息循环 这些消息循环都放在模块 的全局消息循环中, 通过线程的 ID 来索引 这样, 该线程的其它代码就能访问得到 每一个应用程序维护一个消息循环队列 Map, 应用程序中的每个线程都通过 "_Module.AddMessageLoo p(&theloop);", 把该线程的消息循环加入到 _Module 的消息循环 Map 中 消息循环对象包含了消息过滤和空闲处理 每个线程都可以加入空闲处理代码和消息过滤 我们将在后面更加详细地探讨消息循环 这里仅需要知道, 线程是通过创建一个消息循环对象 CMessage Loop 来包装消息循环的 多个界面线程的封装 那么再看看, 当一个应用程序有多个界面线程时,WTL 是怎样对这些线程进行管理的 通常一个 Mutli-SDI 应用程序包含多个界面线程 象 IE 浏览器就是这样的应用程序 每个主窗口都在一个 单独的线程中运行, 每个这样的线程都有一个消息处理 WTL App Wizard 通过为程序的主线程创建一个 Thread Manager 类的实例来实现的 class CThreadManager
10 public: // thread init param struct _RunData LPTSTR lpstrcmdline; int ncmdshow; ; static DWORD WINAPI RunThread(LPVOID lpdata); DWORD m_dwcount; //count of threads HANDLE m_arrthreadhandles[maximum_wait_objects - 1]; CThreadManager() : m_dwcount(0) DWORD AddThread(LPTSTR lpstrcmdline, int ncmdshow); void RemoveThread(DWORD dwindex); int Run(LPTSTR lpstrcmdline, int ncmdshow); ; 该类中,m_arrThreadHandles[MAXIMUM_WAIT_OBJECTS-1] 是用于存放运行的线程的句柄的数组 而 m_dwcount 是当前有多少线程的计数值 AddThread() 和 RemoveThread() 两个函数用于将线程从存放线程句柄的数组中增加或删除线程句柄 RunThread() 是线程的逻辑所在 它的主要工作是创建一个消息队列, 并把它加入主线程 (_Module) 的 消息队列 Map 中 它还创建该线程的主窗口 该函数的逻辑与前面描述的全局函数 Run() 相似 其实, 应该可以想到的 因为前面的 _Module 是应用程 序的主线程
11 下面是该函数的代码 : static DWORD WINAPI RunThread(LPVOID lpdata) CMessageLoop theloop; _Module.AddMessageLoop(&theLoop); _RunData* pdata = (_RunData*)lpData; CMainFrame wndframe; if(wndframe.createex() == NULL) ATLTRACE(_T("Frame window creation failed!\n")); return 0; wndframe.showwindow(pdata->ncmdshow); ::SetForegroundWindow(wndFrame); // Win95 needs this delete pdata; int nret = theloop.run(); _Module.RemoveMessageLoop(); return nret; 当使用 AddThread() 函数创建一个线程时, 就是创建一个 RunThread() 的线程 下面是 AddThread() 的代码
12 DWORD AddThread(LPTSTR lpstrcmdline, int ncmdshow) if(m_dwcount == (MAXIMUM_WAIT_OBJECTS - 1)) ::MessageBox(NULL, _T("ERROR: Cannot create ANY MORE threads!!!"), _T("test2"), MB_OK); return 0; _RunData* pdata = new _RunData; pdata->lpstrcmdline = lpstrcmdline; pdata->ncmdshow = ncmdshow; DWORD dwthreadid; HANDLE hthread = ::CreateThread(NULL, 0, RunThread, pdata, 0, &dwthreadid); if(hthread == NULL) ::MessageBox(NULL, _T("ERROR: Cannot create thread!!!"), _T("Application"), MB_OK); return 0; m_arrthreadhandles[m_dwcount] = hthread; m_dwcount++; return dwthreadid;
13 在上述代码的语句 : HANDLE hthread = ::CreateThread(NULL, 0, RunThread, pdata, 0, &dwthreadid); 中,RunThread 作为参数传递给 CreateThread() 那么应用程序是怎样通过 CThreadManager 类来管理多个线程的呢? 先看一下现在应用程序的入口函数 ( 主线程 ) 的逻辑 : 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
14 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 = 0; // BLOCK: Run application // 注意该处和前面应用程序的差别 // 这里创建一个 CThreadManager 类的实例, 然后调用该类的 Run() 函数 CThreadManager mgr; nret = mgr.run(lpstrcmdline, ncmdshow); _Module.Term(); ::CoUninitialize(); return nret; 在这个入口函数 ( 主线程 ), 创建了一个 CThreadManager 类的实例 然后调用该类的 Run() 函数 看一下 Run() 做了什么事情 int Run(LPTSTR lpstrcmdline, int ncmdshow)
15 MSG msg; // force message queue to be created ::PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); // 创建一个界面线程 为该应用程序的第一个界面线程 该界面线程的主体 // 逻辑 RunThread() 是创建主窗口, 并创建消息循环对象 AddThread(lpstrCmdLine, ncmdshow); int nret = m_dwcount; DWORD dwret; while(m_dwcount > 0) dwret = ::MsgWaitForMultipleObjects(m_dwCount, m_arrthreadhandles, FALSE, INFINITE, QS_ALLINPUT); if(dwret == 0xFFFFFFFF) ::MessageBox(NULL, _T("ERROR: Wait for multiple objects failed!!!"), _T("test2"), MB_OK); else if(dwret >= WAIT_OBJECT_0 && dwret <= (WAIT_OBJECT_0 + m_dwcount - 1)) RemoveThread(dwRet - WAIT_OBJECT_0); else if(dwret == (WAIT_OBJECT_0 + m_dwcount)) ::GetMessage(&msg, NULL, 0, 0);
16 if(msg.message == WM_USER) AddThread("", SW_SHOWNORMAL); else ::MessageBeep((UINT)-1); else ::MessageBeep((UINT)-1); return nret; 该函数首先创建一个界面线程, 这是这个应用程序的第一个界面线程 通过调用 AddThread() 创建界面线 程, 指定 RunThread() 为线程的主体逻辑 它的主要任务是创建一个框架窗口, 然后创建一个消息循环对 象, 并把该对象加入到主线程 _Module 的消息循环 Map 中 随后, 该函数调用 MsgWaitForMultipleObjects(), 进入等待状态 ( 以 INFINITE 为时间参数 ) 有三种 情况可以使该函数返回 一种是 Wait for multiple objects failed, 是出错情况 一种是某一个查询的线程结束 此时调用 RemoveThread() 将该线程句柄删除 最后一种是某一线程接收到 WM_USER 消息 此时, 调用 AddThread() 创建另一个界面线程 上面的逻辑一直运行, 直到所有的界面线程都结束为止
17 现在, 您是否对如何封装 Windows 界面程序有一定的了解了呢? 如果是, 我们接下来就将讨论 WTL 的消 息循环 深入剖析 WTL WTL 消息循环机制详解收藏 WTL 消息循环机制实现了消息过滤和空闲处理机制 消息过滤 首先看一下 CMessageLoop 的核心逻辑 CMessageLoop.Run() 的代码 : int CMessageLoop.Run() BOOL bdoidle = TRUE; int nidlecount = 0; BOOL bret; for(;;) while(!::peekmessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bdoidle) if(!onidle(nidlecount++)) bdoidle = FALSE; bret = ::GetMessage(&m_msg, NULL, 0, 0); if(bret == -1)
18 ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n")); continue; // error, don't process else if(!bret) ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n")); break; // WM_QUIT, exit message loop if(!pretranslatemessage(&m_msg)) ::TranslateMessage(&m_msg); ::DispatchMessage(&m_msg); if(isidlemessage(&m_msg)) bdoidle = TRUE; nidlecount = 0; return (int)m_msg.wparam; 在上面的代码中, 有三个需要注意的地方
19 消息循环中, 首先调用 PeekMessage() 判断消息队列中是否有消息 如果没有, 则调用 OnIdle() 函数 这 就是调用空闲处理 第二个注意点是, 如果有消息, 则调用 GetMessage() 得到消息 然后做判断, 如果是错误返回, 则对消 息并不进行处理 然后再判断是否是 WM_QUIT 消息, 如果是, 则退出消息循环, 从而结束该界面线程 接下来是第三个注意点 在 TranslateMessage() 消息之前, 调用了成员函数 PreTranslateMessage() 这 为在 TranslateMessage() 之前对消息进行处理提供了机会 PreTranslateMessage() 会遍历 CMessageLoop 中所有 CMessageFilterd 对象的 PreTranslateMessage () 函数, 直到其中一个返回为 TRUE 或它们都返回为 FALSE 当有一个返回为 TRUE 时, 即对消息处理了, 那么, 就不再调用 TranslateMessage(), 而是进入下一个循环 这种消息过滤机制提供了一种在不同窗口之间传递消息的机制 CMessageFilter 是一个 C++ 的接口, 即只定义了抽象虚拟函数 class CMessageFilter public: virtual BOOL PreTranslateMessage(MSG* pmsg) = 0; ; 这样, 任何类想要实现消息过滤, 只需实现这个接口 在 C++ 中就采用继承 然后再实现 PreTranslate Message() 函数即可 ATL/WTL App Wizard 生成的框架窗口中实现 PreTranslateMessage() 的代码如下 :
20 BOOL CMainFrame::PreTranslateMessage(MSG* pmsg) if(cframewindowimpl<cmainframe>::pretranslatemessage(pmsg)) return TRUE; return m_view.pretranslatemessage(pmsg); 这种消息过滤机制的好处是任何实现了 CMessageFilter 接口的对象, 都可以接受消息过滤 程序通过 AddMessageFilter() 和 RemoveMessageFilter() 把这些对象加入到 CMessageLoop 中 空闲处理 空闲处理的机制和消息过滤类似 这里不再介绍 我们要把主要经历放在 WTL 的框架窗口分析上 稍后, 我们将进入这部分内容 深入剖析 WTL WTL 框架窗口分析 (1) 收藏 WTL 的基础是 ATL WTL 的框架窗口是 ATL 窗口类的继承 因此, 先介绍一下 ATL 对 Windows 窗口的 封装 由第一部分介绍的 Windows 应用程序可以知道创建窗口和窗口工作的逻辑是 : 1 注册一个窗口类 2 创建该类窗口
21 3 显示和激活该窗口 4 窗口的消息处理逻辑在窗口函数中 该函数在注册窗口类时指定 从上面的逻辑可以看出, 要封装窗口主要需解决怎样封装窗口消息处理机制 对于窗口消息处理机制的封装存在两个问题 一是, 为了使封装好的类的窗口函数对外是透明的, 我们就会想到, 要将窗口函数的消息转发到不同的类的实例 那么怎样将窗口函数中的消息转发给封装好的类的实例? 因为所有封装好的类窗口的窗口函数只有一个, 即一类窗口只有一个窗口函数 而我们希望的是将消息发送给某个类的实例 问题是窗口函数并不知道是哪个实例 它仅仅知道的是 HWND, 而不是类的实例的句柄 因此, 必须有一种办法, 能通过该 HWND, 找到与之相对应的类的实例 二是, 假设已经解决了上面的问题 那么怎样将消息传递给相应的类的实例 通常的办法是采用虚函数 将每个消息对应生成一个虚函数 这样, 在窗口处理函数中, 对于每个消息, 都调用其对应的虚函数即可 但这样, 会有很多虚函数, 使得类的虚函数表十分巨大 为此, 封装窗口就是要解决上面两个基本问题 对于第二个问题,ATL 是通过只定义一个虚函数 然后, 通过使用宏, 来生成消息处理函数 对于第一个问题,ATL 通过使用动态改变 HWND 参数方法来实现的 ATL 对窗口的封装 图示是 ATL 封装的类的继承关系图 从图中可以看到有两个最基本的类 一个是 CWindow, 另一个是 C MessageMap
22 CWindows 是对 Windows 的窗口 API 的一个封装 它把一个 Windows 句柄封装了起来, 并提供了对该 句柄所代表的窗口的操作的 API 的封装 CWindow 的实例是 C++ 语言中的一个对象 它与实际的 Windows 的窗口通过窗口句柄联系 创建一个 CWindow 的实例时并没有创建相应的 Windows 的窗口, 必须调用 CWindow.Create() 来创建 Windows 窗口 也可以创建一个 CWindow 的实例, 然后将它与已经存在的 Windows 窗口挂接起来 CMessageMap 仅仅定义了一个抽象虚函数 ProcessWindowMessage() 所有的包含消息处理机制 的窗口都必须实现该函数 通常使用 ATL 开发程序, 都是从 CWindowImplT 类派生出来的 从类的继承图可以看出, 该类具有一般 窗口的操作功能和消息处理机制 在开发应用程序的时候, 你必须在你的类中定义 消息映射 BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar) COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
23 CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() 我们知道一个窗口函数的通常结构就是许多的 case 语句 ATL 通过使用宏, 直接形成窗口函数的代码 消息映射是用宏来实现的 通过定义消息映射和实现消息映射中的消息处理句柄, 编译器在编译时, 会为 你生成你的窗口类的 ProcessWindowMessage() 消息映射宏包含消息处理宏和消息映射控制宏 BEGIN_MSG_MAP() 和 END_MSG_MAP() 每个消息映射都由 BEGIN_MSG_MAP() 宏开始 我们看一下这个宏的定义 : #define BEGIN_MSG_MAP(theClass) \ public: \ BOOL ProcessWindowMessage(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam, LRESULT& lresult, DWORD dwmsgmapid = 0) \ \ BOOL bhandled = TRUE; \ hwnd; \ umsg; \ wparam; \ lparam; \
24 lresult; \ bhandled; \ switch(dwmsgmapid) \ \ case 0: 一目了然, 这是函数 ProcessWindowMessage() 的实现 与 MFC 的消息映射相比,ATL 的是多么的简单 记住越是简单越吸引人 需要注意的是 dwmsgmapid 每个消息映射都有一个 ID 后面会介绍为什么要用这个 于是可以推断, 消息处理宏应该是该函数的函数体中的某一部分 也可以断定 END_MSG_MAP() 应该定义 该函数的函数结尾 我们来验证一下 : #define END_MSG_MAP() \ break; \ default: \ ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), dwmsgmapid); \ ATLASSERT(FALSE); \ break; \ \ return FALSE; \
25 深入剖析 WTL WTL 框架窗口分析 (2) ATL 的消息处理宏 消息映射的目的是实现 ProcessWindowMessage() ProcessWindowMessage() 函数是窗口函数的关键 逻辑 一共有三种消息处理宏, 分别对应三类窗口消息 普通窗口消息 ( 如 WM_CREATE), 命令消息 (WM _COMMANS) 和通知消息 (WM_NOTIFY) 消息处理宏的目的是将消息和相应的处理函数 ( 该窗口的成员函数 ) 联系起来 普通消息处理宏 有两个这样的宏 :MESSAGE_HANDLER 和 MESSAGE_RANGE_HANDLER 第一个宏将一个消息和一个消息处理函数连在一起 第二个宏将一定范围内的消息和一个消息处理函数连 在一起 消息处理函数通常是下面这样的 : LRESULT MessageHandler(UINT umsg, WPARAM wparam, LPARAM lparam, BOOL& bhandled); 注意最后一个参数 bhandled 它的作用是该处理函数是否处理该消息 如果它为 FALSE, 消息 MAP 的 其它处理函数会来处理这个消息
26 我们看一下 MESSAGE_HANDLE 的定义 : #define MESSAGE_HANDLER(msg, func) \ if(umsg == msg) \ \ bhandled = TRUE; \ lresult = func(umsg, wparam, lparam, bhandled); \ if(bhandled) \ return TRUE; \ 在上面的代码中, 首先判断是否是想要处理的消息 如果是的, 那么调用第二个参数表示的消息处理函数 来处理该消息 注意 bhandled, 如果在消息处理函数中设置为 TRUE, 那么, 在完成该消息处理后, 会进入 return TRU E 语句, 从 ProcessWindowMessage() 函数中返回 如果 bhandled 在调用消息处理函数时, 设置为 FALSE, 则不会从 ProcessWindowMessage 中返回, 而 是继续执行下去 命令消息处理宏和通知消息处理宏 命令消息处理宏有五个 COMMAND_HANDLER,COMMAND_ID_HANDLER,COMMAND_CODE _HANDLER,COMMAND_RANGE_HANDLER 和 COMMAND_RANGE_CODE_HANDLER 通知消息处理宏有五个 --NOTIFY_HANDLER,NOTIFY_ID_HANDLER,NOTIFY_CODE_HANDLER,N
27 OTIFY_RANGE_HANDLER 和 NOTIFY_RANGE_CODE_HANDLER 我们不再详细分析 通过上面的分析, 我们知道了 ATL 是怎样实现窗口函数逻辑的 那么 ATL 是怎样封装窗口函数的呢? 为了 能理解 ATL 的封装方法, 还必须了解 ATL 中的窗口 subclass 等技术 我们将在分析完这些技术之后, 再 分析 ATL 对窗口消息处理函数的封装 扩展窗口类的功能 我们知道 Windows 窗口的功能由它的窗口函数指定 通常在创建 Windows 应用程序时, 我们要开发一 个窗口函数 通过定义对某些消息的相应来实现窗口的功能 在每个窗口处理函数的最后, 我们一般用下面的语句 : default: return DefWindowProc(hWnd, message, wparam, lparam); 它的意思是, 对于没有处理的消息, 我们将它传递给 Windows 的确省窗口函数 Windows 除了提供这个缺省的窗口函数, 还为某些标准的控制提供了一些预定义的窗口函数 我们在注册窗口类的时候, 指定了该窗口类的窗口处理函数 扩展窗口类的功能, 就是要改变窗口函数中对某些消息的处理逻辑
28 下面我们来看几种扩展窗口功能的技术, 以及看看 ATL 是怎样实现的 派生和 CHAIN_MSG_MAP() 很自然, 我们会在一个窗口类的基础上派生另一个 然后通过定义不同的消息处理函数 下面是一个简单的实例 ( 该例子摘自 MSDN) class CBase: public CWindowImpl< CBase > // simple base window class: shuts down app when closed BEGIN_MSG_MAP( CBase ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy ) END_MSG_MAP() LRESULT OnDestroy( UINT, WPARAM, LPARAM, BOOL& ) PostQuitMessage( 0 ); return 0; ; class CDerived: public CBase // derived from CBase; handles mouse button events BEGIN_MSG_MAP( CDerived ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnButtonDown ) CHAIN_MSG_MAP( CBase ) // chain to base class END_MSG_MAP()
29 LRESULT OnButtonDown( UINT, WPARAM, LPARAM, BOOL& ) ATLTRACE( "button down\n" ); return 0; ; 深入剖析 WTL WTL 框架窗口分析 (3) 在上面的例子中,CDerived 从 CBase 中派生出来 CDerived 类通过定义一个 WM_LBUTTONDOWN 消 息处理函数来改变 CBase 类代表的窗口的功能 这样,CBase 类的消息映射定义了一个 ProcessWindowMessage() 函数, 而 CDerived 类的消息映射也 定义了一个 ProcessWindowMessage() 函数 那么, 我们在窗口处理函数逻辑中怎样把这两个类的 ProcessWindowMessage() 连起来呢?( 想想为什 么要连起来?) 在 CDerived 的消息映射中, 有一个宏 CHAIN_MSG_MAP() 它的作用就是把两个类对消息的处理连起来 看一下这个宏的定义 : #define CHAIN_MSG_MAP(theChainClass) \ \ if(thechainclass::processwindowmessage(hwnd, umsg, wparam, lparam, lresult)) \
30 return TRUE; \ 很简单, 它仅仅调用了基类的 ProcessWindowMessage() 函数 也就是说,CDerived 类的 ProcessWindowMessage() 包含两部分, 一部分是调用处理 WM_LBUTTOND OWN 的消息处理函数, 该函数是该类的成员函数 第二部分是调用 CBase 类的 ProcessWindowMessa ge() 函数, 该函数用于处理 WM_DESTROY 消息 在后面对窗口函数的封装中, 我们会知道, 对于其他消息处理,CDerived 会传递给缺省窗口函数 派生和 ALT_MSG_MAP() 如果我们希望在 CBase 类上再派生一个新的窗口类 该类除了要对 WM_RBUTTONDOWN 做不同的处理 外, 还希望 CBase 对 WM_DESTROY 消息的响应与前一个例子不同 比如希望能提示关闭窗口信息 那怎么处理呢?ATL 提供了一种机制, 它由 ALT_MSG_MAP() 实现 它使得一个类的消息映射能处理多个 Windows 窗口类 下面是具体的示例 : // in class CBase: BEGIN_MSG_MAP( CBase ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy1 ) ALT_MSG_MAP( 100 ) MESSAGE_HANDLER( WM_DESTROY, OnDestroy2 ) END_MSG_MAP()
31 ALT_MSG_MAP() 将消息映射分成两个部分 每个部分的消息映射都有一个 ID 上面的消息映射的 ID 分 别为 0 和 100 分析一下 ALT_MSG_MAP(): #define ALT_MSG_MAP(msgMapID) \ break; \ case msgmapid: 很简单, 它结束了前面的 msgmapid 的处理, 开始进入另一个 msgmapid 的处理 那么, 在 CDerived 类的消息映射中, 是怎样将两个类的 ProcessWindowMessage() 函数的逻辑连在一起 的呢? // in class CDerived: BEGIN_MSG_MAP( CDerived ) CHAIN_MSG_MAP_ALT( CBase, 100 ) END_MSG_MAP() 这里使用 CHAIN_MSG_MAP_ALT() 宏 它的具体定义如下 : #define CHAIN_MSG_MAP_ALT(theChainClass, msgmapid) \
32 \ if(thechainclass::processwindowmessage(hwnd, umsg, wparam, lparam, lresult, msgmapid)) \ return TRUE; \ 不再分析其原理 请参考前面对 CHAIN_MSG_MAP() 宏的分析 深入剖析 WTL WTL 框架窗口分析 (4) superclass 是一种生成新的窗口类的方法 它的中心思想是依靠现有的窗口类, 克隆出另一个窗口类 被 克隆的类可以是 Windows 预定义的窗口类, 这些预定义的窗口类有按钮或下拉框控制等等 也可以是一 般的类 克隆的窗口类使用被克隆的类 ( 基类 ) 的窗口消息处理函数 克隆类可以有自己的窗口消息处理函数, 也可以使用基类的窗口处理函数 需要注意的是,superclass 是在注册窗口类时就改变了窗口的行为 即通过指定基类的窗口函数或是自己 定义的窗口函数 这与后面讲到的 subclass 是不同的 后者是在窗口创建完毕后, 通过修改窗口函数的地 址等改变一个窗口的行为的 请看示例 ( 摘自 MSDN): class CBeepButton: public CWindowImpl< CBeepButton > public: DECLARE_WND_SUPERCLASS( _T("BeepButton"), _T("Button") ) BEGIN_MSG_MAP( CBeepButton ) MESSAGE_HANDLER( WM_LBUTTONDOWN, OnLButtonDown )
33 END_MSG_MAP() LRESULT OnLButtonDown( UINT, WPARAM, LPARAM, BOOL& bhandled ) MessageBeep( MB_ICONASTERISK ); bhandled = FALSE; // alternatively: DefWindowProc() return 0; ; // CBeepButton 该类实现一个按钮, 在点击它时, 会有响声 该类的消息映射处理 WM_LBUTTONDOWN 消息 其它的消息由 Windows 缺省窗口函数处理 在消息映射前面, 有一个宏 --DECLARE_WND_SUPERCLASS() 它的作用就是申明 BeepButton 是 Butt on 的一个 superclass 分析一下这个宏 : #define DECLARE_WND_SUPERCLASS(WndClassName, OrigWndClassName) \ static CWndClassInfo& GetWndClassInfo() \ \ static CWndClassInfo wc = \ \ sizeof(wndclassex), 0, StartWindowProc, \ 0, 0, NULL, NULL, NULL, NULL, NULL,
34 WndClassName, NULL, \ OrigWndClassName, NULL, NULL, TRUE, 0, _T("") \ ; \ return wc; \ 这个宏定义了一个静态函数 GetWndClassInfo() 这个函数返回了一个窗口类注册时用到的数据结构 CW ndclassinfo 该结构的详细定义如下 : struct _ATL_WNDCLASSINFOA WNDCLASSEXA m_wc; LPCSTR m_lpszorigname; WNDPROC pwndproc; LPCSTR m_lpszcursorid; BOOL m_bsystemcursor; ATOM m_atom; CHAR m_szautoname[13]; ATOM Register(WNDPROC* p) return AtlModuleRegisterWndClassInfoA(&_Module, this, p); ; struct _ATL_WNDCLASSINFOW
35 return AtlModuleRegisterWndClassInfoW(&_Module, this, p); ; typedef _ATL_WNDCLASSINFOA CWndClassInfoA; typedef _ATL_WNDCLASSINFOW CWndClassInfoW; #ifdef UNICODE #define CWndClassInfo CWndClassInfoW #else #define CWndClassInfo CWndClassInfoA #endif 这个结构调用了一个静态函数 AtlModuleRegisterWndClassInfoA(&_Module, this, p); 这个函数的用 处就是注册窗口类 它指定了 WndClassName 是 OrigWdClassName 的 superclass subclass subclass 是普遍采用的一种扩展窗口功能的方法 它的大致原理如下 在一个窗口创建完了之后, 将该窗口的窗口函数替换成新的窗口消息处理函数 这个新的窗口函数可以对 某些需要处理的特定的消息进行处理, 然后再将处理传给原来的窗口函数 注意它与 superclass 的区别
36 Superclass 是以一个类为原版, 进行克隆 既在注册新的窗口类时, 使用的是基类窗口的窗口函数 而 subclass 是在某一个窗口注册并创建后, 通过修改该窗口的窗口消息函数的地址而实现的 它是针对窗 口实例 看一个从 MSDN 来的例子 : class CNoNumEdit: public CWindowImpl< CNoNumEdit > BEGIN_MSG_MAP( CNoNumEdit ) MESSAGE_HANDLER( WM_CHAR, OnChar ) END_MSG_MAP() LRESULT OnChar( UINT, WPARAM wparam, LPARAM, BOOL& bhandled ) TCHAR ch = wparam; if( _T('0') <= ch && ch <= _T('9') ) MessageBeep( 0 ); else bhandled = FALSE; return 0; ; 这里定义了一个只接收数字的编辑控件 即通过消息映射, 定义了一个特殊的消息处理逻辑 然后, 我们使用 CWindowImplT. SubclassWindow() 来 subclass 一个编辑控件
37 class CMyDialog: public CDialogImpl<CMyDialog> public: enum IDD = IDD_DIALOG1 ; BEGIN_MSG_MAP( CMyDialog ) MESSAGE_HANDLER( WM_INITDIALOG, OnInitDialog ) END_MSG_MAP() LRESULT OnInitDialog( UINT, WPARAM, LPARAM, BOOL& ) ed.subclasswindow( GetDlgItem( IDC_EDIT1 ) ); return 0; CNoNumEdit ed; ; 上述代码中,ed.SubclassWindow( GetDlgItem( IDC_EDIT1 ) ) 语句是对 IDC_EDIT1 这个编辑控件进 行 subclass 该语句实际上是替换了编辑控件的窗口函数 由于 SubClassWindows() 实现的机制和 ATL 封装窗口函数的机制一样, 我们会在后面介绍 ATL 是怎么实 现它的 深入剖析 WTL WTL 框架窗口分析 (5) ATL 对窗口消息处理函数的封装 在本节开始部分谈到的封装窗口的两个难题, 其中第一个问题是怎样解决将窗口函数的消息转发到 HWN D 相对应的类的实例中的相应函数
38 下面我们来看一下,ATL 采用的是什么办法来实现的 我们知道每个 Windows 的窗口类都有一个窗口函数 LRESULT WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam); 在类 CWindowImplBaseT 中, 定义了两个类的静态成员函数 template <class TBase = CWindow, class TWinTraits = CControlWinTraits> class ATL_NO_VTABLE CWindowImplBaseT : public CWindowImplRoot< TBase > public: static LRESULT CALLBACK StartWindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam); static LRESULT CALLBACK WindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam); 它们都是窗口函数 之所以定义为静态成员函数, 是因为每个类必须只有一个窗口函数, 而且, 窗口函数 的申明必须是这样的
39 在前面介绍的消息处理逻辑过程中, 我们知道怎样通过宏生成虚函数 ProcessWindowsMessage(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam, LRESULT& lresult, DWORD dwms gmapid) 现在的任务是怎样在窗口函数中把消息传递给某个实例 ( 窗口 ) 的 ProcessWindowsMessage() 这是一 个难题 窗口函数是类的静态成员函数, 因此, 它不象类的其它成员函数, 参数中没有隐含 this 指针 注意, 之所以存在这个问题是因为 ProcessWindowsMessage() 是一个虚函数 而之所以用虚函数是考虑 到类的派生及多态性 如果不需要实现窗口类的派生及多态性, 是不存在这个问题的 通常想到的解决办法是根据窗口函数的 HWND 参数, 寻找与其对应的类的实例的指针 然后, 通过该指 针, 调用该实例的消息逻辑处理函数 ProcessWindowsMessage() 这样就要求存储一个全局数组, 将 HWND 和该类的实例的指针一一对应地存放在该数组中 ATL 解决这个问题的方法很巧妙 该方法并不存储这些对应关系, 而是使窗口函数接收 C++ 类指针作为参 数来替代 HWND 作为参数 具体步骤如下 : 在注册窗口类时, 指定一个起始窗口函数 创建窗口类时, 将 this 指针暂时保存在某处 Windows 在创建该类的窗口时会调用起始窗口函数 它的作用是创建一系列二进制代码 (thunk) 这 些代码用 this 指针的物理地址来取代窗口函数的 HWND 参数, 然后跳转到实际的窗口函数中 这是通过 改变栈来实现的
40 然后, 用这些代码作为该窗口的窗口函数 这样, 每次调用窗口函数时都对参数进行转换 在实际的窗口函数中, 只需要将该参数 cast 为窗口类指针类型 详细看看 ATL 的封装代码 1. 注册窗口类时, 指定一个起始窗口函数 在 superclass 中, 我们分析到窗口注册时, 指定的窗口函数是 StartWindowProc() 2. 创建窗口类时, 将 this 指针暂时保存在某处 template <class TBase, class TWinTraits> HWND CWindowImplBaseT< TBase, TWinTraits >::Create(HWND hwndparent, RECT& rcpos, LPCTSTR szwindowname, DWORD dwstyle, DWORD dwexstyle, UINT nid, ATOM atom, LPVOID lpcreateparam) ATLASSERT(m_hWnd == NULL); if(atom == 0) return NULL; _Module.AddCreateWndData(&m_thunk.cd, this); if(nid == 0 && (dwstyle & WS_CHILD)) nid = (UINT)this; HWND hwnd = ::CreateWindowEx(dwExStyle, (LPCTSTR)MAKELONG(atom, 0), szwindowname, dwstyle, rcpos.left, rcpos.top, rcpos.right - rcpos.left,
41 rcpos.bottom - rcpos.top, hwndparent, (HMENU)nID, _Module.GetModuleInstance(), lpcreateparam); ATLASSERT(m_hWnd == hwnd); return hwnd; 该函数用于创建一个窗口 它做了两件事 第一件就是通过 _Module.AddCreateWndData(&m_thunk.c d, this); 语句把 this 指针保存在 _Module 的某个地方 第二件事就是创建一个 Windows 窗口 3. 一段奇妙的二进制代码 下面我们来看一下一段关键的二进制代码 它的作用是将传递给实际窗口函数的 HWND 参数用类的实例 指针来代替 ATL 定义了一个结构来代表这段代码 : #pragma pack(push,1) struct _WndProcThunk DWORD m_mov; // mov dword ptr [esp+0x4], pthis (esp+0x4 is hwnd) DWORD m_this; // BYTE m_jmp; // jmp WndProc DWORD m_relproc; // relative jmp ;
42 #pragma pack(pop) #pragma pack(push,1) 的意思是告诉编译器, 该结构在内存中每个字段都紧紧挨着 因为它存放的是机 器指令 这段代码包含两条机器指令 : mov dword ptr [esp+4], pthis jmp WndProc MOV 指令将堆栈中的 HWND 参数 (esp+0x4) 变成类的实例指针 pthis JMP 指令完成一个相对跳转 到实际的窗口函数 WndProc 的任务 注意, 此时堆栈中的 HWND 参数已经变成了 pthis, 也就是说,W inproc 得到的 HWND 参数实际上是 pthis 上面最关键的问题是计算出 jmp WndProc 的相对偏移量 我们看一下 ATL 是怎样初始化这个结构的 class CWndProcThunk public: union _AtlCreateWndData cd;
43 _WndProcThunk thunk; ; void Init(WNDPROC proc, void* pthis) thunk.m_mov = 0x042444C7; //C C thunk.m_this = (DWORD)pThis; thunk.m_jmp = 0xe9; thunk.m_relproc = (int)proc - ((int)this+sizeof(_wndprocthunk)); // write block from data cache and // flush from instruction cache FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk)); ; ATL 包装了一个类并定义了一个 Init() 成员函数来设置初始值的 在语句 thunk.m_relproc = (int)proc - ((int)this+sizeof(_wndprocthunk)); 用于把跳转指令的相对地址设置为 (int)proc - ((int)this+sizeof (_WndProcThunk)) 上图是该窗口类的实例 ( 对象 ) 内存映象图, 图中描述了各个指针及它们的关系 很容易计算出相对地址
44 是 (int)proc - ((int)this+sizeof(_wndprocthunk)) 4. StartWindowProc() 的作用 template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) CWindowImplBaseT< TBase, TWinTraits >* pthis = (CWindowImplBaseT< TBase, TWinTraits >*)_Module.ExtractCreateWndData(); ATLASSERT(pThis!= NULL); pthis->m_hwnd = hwnd; pthis->m_thunk.init(pthis->getwindowproc(), pthis); WNDPROC pproc = (WNDPROC)&(pThis->m_thunk.thunk); WNDPROC poldproc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc); #ifdef _DEBUG // check if somebody has subclassed us already since we discard it if(poldproc!= StartWindowProc) ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n")); #else poldproc; // avoid unused warning
45 #endif return pproc(hwnd, umsg, wparam, lparam); 该函数做了四件事 : 一是调用 _Module.ExtractCreateWndData() 语句, 从保存 this 指针的地方得到该 this 指针 二是调用 m_thunk.init(pthis->getwindowproc(), pthis) 语句初始化 thunk 代码 三是将 thunk 代码设置为该窗口类的窗口函数 WNDPROC pproc = (WNDPROC)&(pThis->m_thunk.thunk); WNDPROC poldproc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc); 这样, 以后的消息处理首先调用的是 thunk 代码 它将 HWND 参数改为 pthis 指针, 然后跳转到实际的 窗口函数 WindowProc() 四是在完成上述工作后, 调用上面的窗口函数 由于 StartWindowProc() 在创建窗口时被 Windows 调用 在完成上述任务后它应该继续完成 Windows 要求完成的任务 因此在这里, 就简单地调用实际的窗口函数来处理 5. WindowProc() 窗口函数
46 下面是该函数的定义 : template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) CWindowImplBaseT< TBase, TWinTraits >* pthis = (CWindowImplBaseT< TBase, TWinTraits >*)hwnd; // set a ptr to this message and save the old value MSG msg = pthis->m_hwnd, umsg, wparam, lparam, 0, 0, 0 ; const MSG* poldmsg = pthis->m_pcurrentmsg; pthis->m_pcurrentmsg = &msg; // pass to the message map to process LRESULT lres; BOOL bret = pthis->processwindowmessage(pthis->m_hwnd, umsg, wparam, lparam, lres, 0); // restore saved value for the current message ATLASSERT(pThis->m_pCurrentMsg == &msg); pthis->m_pcurrentmsg = poldmsg; // do the default processing if message was not handled if(!bret) if(umsg!= WM_NCDESTROY) lres = pthis->defwindowproc(umsg, wparam,
47 lparam); else // unsubclass, if needed LONG pfnwndproc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC); lres = pthis->defwindowproc(umsg, wparam, lparam); if(pthis->m_pfnsuperwindowproc!= ::DefWindowProc && ::GetWindowLong(pThis->m_hWnd, GWL_WNDPROC) == pfnwndproc) ::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_pfnSuperWindowProc); // clear out window handle HWND hwnd = pthis->m_hwnd; pthis->m_hwnd = NULL; // clean up after window is destroyed pthis->onfinalmessage(hwnd); return lres; 首先, 该函数把 hwnd 参数 cast 到一个类的实例指针 pthis
48 然后调用 pthis->processwindowmessage(pthis->m_hwnd, umsg, wparam, lparam, lres, 0); 语 句, 也就是调用消息逻辑处理, 将具体的消息处理事务交给 ProcessWindowMessage() 接下来, 如果 ProcessWindowMessage() 没有对任何消息进行处理, 就调用缺省的消息处理 注意这里处理 WM_NCDESTROY 的方法 这和 subclass 有关, 最后恢复没有 subclass 以前的窗口函数 WTL 对 subclass 的封装 前面讲到过 subclass 的原理, 这里看一下是怎么封装的 template <class TBase, class TWinTraits> BOOL CWindowImplBaseT< TBase, TWinTraits >::SubclassWindow(HWND hwnd) ATLASSERT(m_hWnd == NULL); ATLASSERT(::IsWindow(hWnd)); m_thunk.init(getwindowproc(), this); WNDPROC pproc = (WNDPROC)&(m_thunk.thunk); WNDPROC pfnwndproc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)pProc); if(pfnwndproc == NULL) return FALSE; m_pfnsuperwindowproc = pfnwndproc; m_hwnd = hwnd; return TRUE;
49 没什么好说的, 它的工作就是初始化一段 thunk 代码, 然后替换原先的窗口函数 深入剖析 WTL WTL 框架窗口分析 (6) WTL 对框架窗口的封装 ATL 仅仅是封装了窗口函数和提供了消息映射 实际应用中, 需要各种种类的窗口, 比如, 每个界面线程 所对应的框架窗口 WTL 正是在 ATL 基础上, 为我们提供了框架窗口和其他各种窗口 所有的应用程序类型中, 每个界面线程都有一个框架窗口 (Frame) 和一个视 (View) 它们的概念和 MFC 中 的一样 图示是 WTL 的窗口类的继承图 WTL 框架窗口为我们提供了 :
50 一个应用程序的标题, 窗口框架, 菜单, 工具栏 视的管理, 包括视的大小的改变, 以便与框架窗口同步 提供对菜单, 工具栏等的处理代码 在状态栏显示帮助信息等等 WTL 视通常就是应用程序的客户区 它通常用于呈现内容给客户 WTL 提供的方法是在界面线程的逻辑中创建框架窗口, 而视的创建由框架窗口负责 后面会介绍, 框架窗 口在处理 WM_CREATE 消息时创建视 如果要创建一个框架窗口, 需要 : 从 CFrameWindowImpl 类派生你的框架窗口 加入 DECLARE_FRAME_WND_CLASS, 指定菜单和工具栏的资源 ID 加入消息映射, 同时把它与基类的消息映射联系起来 同时, 加入消息处理函数 下面是使用 ATL/WTL App Wizard 创建一个 SDI 应用程序的主框架窗口的申明 class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler
51 public: DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME) // 该框架窗口的视的实例 CView m_view; // 该框架窗口的命令工具行 CCommandBarCtrl m_cmdbar; virtual BOOL PreTranslateMessage(MSG* pmsg); virtual BOOL OnIdle(); BEGIN_UPDATE_UI_MAP(CMainFrame) UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP) UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP) END_UPDATE_UI_MAP() BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar) COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
52 CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() // Handler prototypes (uncomment arguments if needed): // LRESULT MessageHandler(UINT /*umsg*/, WPARAM /*wparam*/, LPARAM /*lparam*/, BOOL& /*bhandled*/) // LRESULT CommandHandler(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/) // LRESULT NotifyHandler(int /*idctrl*/, LPNMHDR /*pnmh*/, BOOL& /*bhandled*/) LRESULT OnCreate(UINT /*umsg*/, WPARAM /*wparam*/, LPARAM /*lparam*/, BOOL& /*bhandled*/); LRESULT OnFileExit(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/); LRESULT OnFileNew(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/); LRESULT OnViewToolBar(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/); LRESULT OnViewStatusBar(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/); LRESULT OnAppAbout(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/,
53 BOOL& /*bhandled*/); ; DECLARE_FRAME_WND_CLASS() 宏是为框架窗口指定一个资源 ID, 可以通过这个 ID 和应用程序的资源 联系起来, 比如框架的图标, 字符串表, 菜单和工具栏等等 WTL 视 通常应用程序的显示区域分成两个部分 一是包含窗口标题, 菜单, 工具栏和状态栏的主框架窗口 另一 部分就是被称为视的部分 这部分是客户区, 用于呈现内容给客户 视可以是包含 HWND 的任何东西 通过在框架窗口处理 WM_CREATE 时, 将该 HWND 句柄赋植给主窗 口的 m_hwndclien 成员来设置主窗口的视 比如, 在用 ATL/WTL App Wizard 创建了一个应用程序, 会创建一个视, 代码如下 : class CTestView : public CWindowImpl<CTestView> public: DECLARE_WND_CLASS(NULL) BOOL PreTranslateMessage(MSG* pmsg); BEGIN_MSG_MAP(CTestView) MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP()
54 // Handler prototypes (uncomment arguments if needed): // LRESULT MessageHandler(UINT /*umsg*/, WPARAM /*wparam*/, LPARAM /*lparam*/, BOOL& /*bhandled*/) // LRESULT CommandHandler(WORD /*wnotifycode*/, WORD /*wid*/, HWND /*hwndctl*/, BOOL& /*bhandled*/) // LRESULT NotifyHandler(int /*idctrl*/, LPNMHDR /*pnmh*/, BOOL& /*bhandled*/) LRESULT OnPaint(UINT /*umsg*/, WPARAM /*wparam*/, LPARAM /*lparam*/, BOOL& /*bhandled*/); ; 这个视是一个从 CWindowImpl 派生的窗口 在主窗口的创建函数中, 将该视的 HWND 设置给主窗口的 m_hwndclient 成员 LRESULT CMainFrame::OnCreate(UINT /*umsg*/, WPARAM /*wparam*/, LPARAM /*lparam*/, BOOL& /*bhandled*/) m_hwndclient = m_view.create(m_hwnd, rcdefault, NULL, WS_CHILD WS_VISIBLE WS_CLIPSIBLINGS WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);
55 return 0; 上述代码为主窗口创建了视 到此为止, 我们已经从 Win32 模型开始, 到了解 windows 界面程序封装以及 WTL 消息循环机制, 详细 分析了 WTL 通过我们的分析, 您是否对 WTL 有一个深入的理解, 并能得心应手的开发出高质量的 Win dows 应用程序? 别急, 随后, 我们还将一起探讨开发 WTL 应用程序的技巧 WTL 编程的十个技巧 介绍 这篇文章将介绍十个开发 WTL 应用程序的小技巧 这些技巧内容涉及从怎么控制和放置应用程序主窗口到怎么在控件中显示字符串和整数等系列问题 你可以到 ject.zip 下载使用这十个技巧的示例程序 十个技巧包括 : 设置主窗口的大小 启动时在屏幕中央显示主窗口 设置主窗口的最小 / 最大尺寸 动态加载主窗口标题 将工具栏设置成平面风格 设置对话框文字和背景的颜色 交换对话框按钮位置 设置平面风格的 ListView 头 在控件中显示整数 在控件中显示资源字符串 主窗口技巧
56 下面的技巧能够同时使用在 SDI 和 MDI 应用程序中 : 1. 设置窗口生成时的大小在程序.CPP 文件的 Run() 函数中使用下面的技术可以控制窗口生成时的大小尺寸 用你想要的窗口大小设置 rect 的值, 然后将这个值作为第二个函数传递给 CreateEx() 函数, 如下所示 : RECT rc = 0, 0, 380, 265; if(wndmain.createex(null, rc) == NULL) 2. 将主窗口在桌面中央显示要让主窗口在桌面中央显示, 只要在应用程序的.CPP 文件的 Run() 的函数的 ShowWindow() 命令前增加下面的一行代码 : wndmain.centerwindow(); 3. 设置最小 / 最大尺寸如果你想要控制你的主窗口的最大最小尺寸, 你要在在头文件 mainframe.h 的 CMainFrame 消息映射表中增加下面的消息处理过程 MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo) 完全实现功能, 还需要在文件中增加处理函数 : LRESULT OnGetMinMaxInfo(UINT, WPARAM, LPARAM lparam, BOOL&) // lparam 传递 MINMAXINFO 结构的指针 LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam; // 改变 size 结构中的相应的值为我们想要的窗口大小值 lpmmi->ptmintracksize.x = 200; // 最小宽度 lpmmi->ptmintracksize.y = 150; // 最小高度 lpmmi->ptmaxtracksize.x = 600; // 最大宽度 lpmmi->ptmaxtracksize.y = 450; // 最大高度 return 0; 4. 动态设置标题 是可以通过加载资源中的字符串生成 Cstring 对象, 然后通过这个对象实现动态设置窗口标题 把下面的
57 代码加入到 OnCreate() 函数中就可以完成实现这个功能 另外需要在项目中 #include atlmisc.h, 这个文件定义了 Cstring 类 你能够用 LoadString() 加载最长 255 个字符的字符串 CString str; str.loadstring(ids_editstring); SetWindowText(str); 5. Flat-style Toolbar 平面风格工具栏使用 WTL AppWizard 生成程序的时候, 如果没有选择 rebar 的话, 生成的工具栏是标准立体按钮 如果你想在没有 rebar 时工具栏有平面风格, 只要在主框架 OnCreate 函数的创建工具栏的代码后边增加以下代码就可以了 : CToolBarCtrl tool = m_hwndtoolbar; tool.modifystyle(0, TBSTYLE_FLAT); Dialog Tips 对话框技巧以下的技巧可以使用在对话框或者基于对话框的应用程序中 下边的图显示了我们的示例函数的 About 对话框, 其中使用了两个技巧 6. Dialog Text and Background Color 对话框文字和背景的颜色这个技巧提供了简单快速改变对话框的文字或背景颜色的方法 这篇文章示例程序的 About 对话框里, 我们使用 SetTextColor 设置文字颜色为白色 北京颜色使用 Stock brush 设置成黑色 第一步是在对话框消息映射表中增加以下的两行代码 : MESSAGE_HANDLER(WM_CTLCOLORDLG, OnCtrlColor) MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnCtrlColor) 第二步是在 OnCtrlColor 函数中改变文字和背景的颜色 将背景模式设置为透明, 这样可以正确显示静态控件和 group box 控件文字 接着, 将文字设置成我们想要的颜色, 最后设置背景 brush 在项目中增加 atlmisc.h 头文件, 因为 AtlGetStockBrush() 函数在这个头文件中定义 有几种 Stock br usk 可以选择 WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, 和 BLACK_BRUS H, 如果使用其它颜色, 你需要生成新的 brush, LRESULT OnCtrlColor(UINT, WPARAM, LPARAM, BOOL&) // 设置背景模式和文字颜色
58 SetBkMode((HDC)wParam, TRANSPARENT); // 透明背景 SetTextColor((HDC)wParam, RGB(255, 255, 255)); // 白色文字 return (LRESULT)AtlGetStockBrush(BLACK_BRUSH); 7. 动态交换按钮位置下面的代码来自 About 对话框的 OnInitDialog() 函数中, 用来交换 OK 和 Cancel 按钮的位置 其中关键点是将屏幕位置如何转换成相对于 Client 的位置 CButton bok = GetDlgItem(IDOK)); CButton bcancel = GetDlgItem(IDCANCEL)); // 取得按钮位置 RECT rcok, rccancel; bok.getwindowrect(&rcok); ScreenToClient(&rcOk); bcancel.getwindowrect(&rccancel); ScreenToClient(&rcCancel); // 交换按钮位置 bok.setwindowpos(null, &rccancel, SWP_NOZORDER SWP_NOSIZE); bcancel.setwindowpos(null, &rcok, SWP_NOZORDER SWP_NOSIZE); 控件技巧技巧 8 适用于报表类型的 listview 控件, 技巧 9 和 10 适用于接受 text 的任何控件, 如 edit 控件和 rich edit 控件等 8. 平面风格的 ListView 头将报表型的 listview 的头控件改成平面外观, 只要如下取得头控件对象, 并且修改它的类型 CHeaderCtrl hdr = MyListView.GetHeader(); hdr.modifystyle(hds_buttons, 0); 9. 显示整数在项目中增加 atlmisc.h 文件, 这个文件定义了 Cstring 类 然后使用下面的代码在控件中显示整数值
59 int nvalue = 9999; CString sinteger; sinteger.format("%i", nvalue); MyControl.SetWindowText(sInteger); 10. 显示资源字符串使用 atlmisc.h 头文件中的辅助函数 AtlLoadString 加载长度可以超过 255 个字符的字符串, 然后将这个字符串显示到控件中 示例程序中使用下面的代码实现在 edit 控将中显示字符串 当在资源字符串表中输入字符串时, 要使用 \r\n 来分行, 仅仅 \n 不能正确分行 TCHAR carray[1000]; AtlLoadString(IDS_EDITSTRING, carray, ); MyControl.SetWindowText(cArray); 附加技巧下面的技巧可以使用在所有的控件中 11. 缺省字体当一个控件被放置在对话框上时, 控件就采用对话框的缺省字体 然而, 当一个控件如视图或者分割面板被使用在窗口中时, 将使用 SYSTEM_FONT 字体, 这种字体不是很漂亮 要改变字体的话, 只要在工程中增加 atlmisc.h 文件, 然后调用 AtlGetStockFont 取得 truetype 字体 DEFAULT_GUI_FONT, 将控件设置成这种字体 : MyControl.SetFont(AtlGetStockFont(DEFAULT_GUI_FONT), TRUE); 使用条款本文章的示例程序是免费的, 你可以在任何地方使用 THIS SOFTWARE IS DISTRIBUTED AS-IS, WITHOUT WARRANTIES OF ANY KIND. WTL 流程分析 一个窗口从创建到销毁, 有这么几个主要过程 在 winmain 中 注册窗口类 创建窗口 进入消息循环
60 在 wndproc 中 处理消息 现在我们就是要挖掘出 wtl 中在何处处理这些东西, 怎么处理的 首先 : winmain 在哪里? winmain 在和工程名相同的 cpp 文件中 名字叫做 _twinmain int WINAPI _twinmain(hinstance hinstance, HINSTANCE /*hprevinstance*/, LPTSTR l pstrcmdline, int ncmdshow) HRESULT hres = ::CoInitialize(NULL); // If you are running on NT 4.0 or higher you can use the following call instead to ad. // make the EXE free threaded. This means that calls come in on a random RPC thre // HRESULT hres = ::CoInitializeEx(NULL, COINIT_MULTITHREADED); ATLASSERT(SUCCEEDED(hRes)); (MSLU) is used // this resolves ATL window thunking problem when Microsoft Layer for Unicode ::DefWindowProc(NULL, 0, 0, 0L); AtlInitCommonControls(ICC_COOL_CLASSES ICC_BAR_CLASSES); // add flags to s upport other controls hres = _Module.Init(NULL, hinstance); ATLASSERT(SUCCEEDED(hRes)); int nret = Run(lpstrCmdLine, ncmdshow); _Module.Term(); ::CoUninitialize(); return nret;
61 从这个函数中, 看不出什么, 基本上实质上的内容都被分配在别的函数中处理了 这里所说的别的 函数就是 Run(lpstrCmdLine, ncmdshow); 这个函数是我们自己写的, 就在这个 _twinmain 的上面 Run 的作用 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; 所以 从名字 MessageLoop 和 CreateEx 就可以猜测到这个 Run 就是创建窗口并进入消息循环的地方 winmain 进行必要的初始化, 主要的工作在 Run 中进行 Run 创建窗口并进入消息循环 窗口的创建 很容易就可以知道这么一段完成了窗口的创建 CMainFrame wndmain; if(wndmain.createex() == NULL)
62 ATLTRACE(_T("Main window creation failed!\n")); return 0; CMainFrame 定义在 MainFrm.h 中 class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI< CMainFrame>, public CMessageFilter, public CIdleHandler 可见这里使用了多继承, 这是一个普遍行为 主要继承于 CFrameWindowImpl, 而且这个是模板, 提供的参数就是 CMainFrame 后面可以发现, 这个参数在基类中用于强制类型转换, 算是向下转换 创建调用的是 wndmain.createex(), 这个函数在 CMainFrame 中找不到, 自然在其基类中有 这 个是 CFrameWindowImpl 中的 CreateEx(): HWND CreateEx(HWND hwndparent = NULL, _U_RECT rect = NULL, DWORD dw Style = 0, DWORD dwexstyle = 0, LPVOID lpcreateparam = NULL) TCHAR szwindowname[256]; szwindowname[0] = 0; ::LoadString(_Module.GetResourceInstance(), T::GetWndClassInfo().m_uCommon ResourceID, szwindowname, 256); HMENU hmenu=::loadmenu(_module.getresourceinstance(), MAKEINTRESOUR CE(T::GetWndClassInfo().m_uCommonResourceID)); T* pt = static_cast<t*>(this); HWND hwnd = pt->create(hwndparent, rect, szwindowname, dwstyle, dwe xstyle, hmenu, lpcreateparam); if(hwnd!=null) m_haccel=::loadaccelerators(_module.getresourceinstance(),makeintreso URCE(T::GetWndClassInfo().m_uCommonResourceID)); return hwnd; 等等, 我们在这里发现了一个奇异的行为
63 T* pt = static_cast<t*>(this); 这是什么, 强制类型转换, 而且是基于模板参数的类型转换 嗯, 这个就是 ATL 开发组偶然发明的仿真动态绑定 利用给基类提供派生类作为模板参数, 在函数调用的时候强制类型转换以在编译期间决定调用是哪个函数 这样作使得我们可以在派生类中改写基类中的函数, 并且免去了虚函数带来的代价 所以说 ram); pt->create(hwndparent, rect, szwindowname, dwstyle, dwexstyle, hmenu, lpcreatepa 调用的是派生类的 Create 函数, 虽然派生类并没有改写这个函数, 但是你可以这么作并获得灵活性 下面继续跟踪这个 Create 的行为, 不用寻找了, 这个函数就在 CreateEx 的上面一点 派生类没有 改写, 调用的就是基类中的版本 HWND Create(HWND hwndparent = NULL, _U_RECT rect = NULL, LPCTSTR szwi ndowname = NULL, DWORD dwstyle = 0, DWORD dwexstyle = 0, HMENU hm enu = NULL, LPVOID lpcreateparam = NULL) ATOM atom = T::GetWndClassInfo().Register(&m_pfnSuperWindowProc); dwstyle = T::GetWndStyle(dwStyle); dwexstyle = T::GetWndExStyle(dwExStyle); if(rect.m_lprect == NULL) rect.m_lprect = &TBase::rcDefault; return CFrameWindowImplBase< TBase, TWinTraits >::Create(hWndParent, rect.m_lprect, szwindowname, dwstyle, dwexstyle, hmenu, atom, lpcreateparam); 红色标记了两个重要的过程, 一个注册窗口类, 一个创建了窗口 先关注窗口类的注册 窗口类与注册 T::GetWndClassInfo().Register(&m_pfnSuperWindowProc); 这条代码完成了窗口类的注册 T 是传递给基类的参数, 也就是派生类 所以 T 就是 CMainFrame T::GetWndClassInfo() 表示, 这里调用的是类的静态函数 那么, 这个函数在哪里定义的呢? 我们要注意到 CMainFrame 定义中的这么 一行 : DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
64 显然, 这是一个宏 ( 你看看后面没有分号就知道了 ) 所以继续搜索这个宏的定义 #define DECLARE_FRAME_WND_CLASS(WndClassName, ucommonresourceid) \ static CFrameWndClassInfo& GetWndClassInfo() \ \ static CFrameWndClassInfo wc = \ \ sizeof(wndclassex), 0, StartWindowProc, \ Name, NULL, \ 0, 0, NULL, NULL, NULL, (HBRUSH)(COLOR_WINDOW + 1), NULL, WndClass NULL, NULL, IDC_ARROW, TRUE, 0, _T(""), ucommonresourceid \ ; \ return wc; \ ^_^, 我逮着你了 就是这个宏把一个静态函数搞进来了 这个静态函数就是根据参数产生一个类型 CFrameWndClassInfo 的静态变量, 并返回它 这个静态变量包含了 WNDCLASS 信息, 以及和创建窗口 时需要提供的一些信息 是一个所以 Register(&m_pfnSuperWindowProc); 调用的就是 CFrameWndClassInfo 中的 member function 所以, 我们要看看 CFrameWndClas sinfo 的定义了 : class CFrameWndClassInfo public: WNDCLASSEX m_wc; LPCTSTR m_lpszorigname; WNDPROC pwndproc; LPCTSTR m_lpszcursorid; BOOL m_bsystemcursor;
65 ATOM m_atom; TCHAR m_szautoname[5 + sizeof(void*) * 2]; // sizeof(void*) * 2 is the number of digits %p outputs UINT m_ucommonresourceid; ATOM Register(WNDPROC* pproc) if (m_atom == 0) ::EnterCriticalSection(&_Module.m_csWindowCreate); if(m_atom == 0) HINSTANCE hinst = _Module.GetModuleInstance(); if (m_lpszorigname!= NULL) ATLASSERT(pProc!= NULL); LPCTSTR lpsz = m_wc.lpszclassname; WNDPROC proc = m_wc.lpfnwndproc; WNDCLASSEX wc; wc.cbsize = sizeof(wndclassex); // try process local class first &wc)) if(!::getclassinfoex(_module.getmoduleinstance(), m_lpszorigname, // try global class if(!::getclassinfoex(null, m_lpszorigname, &wc))
66 ::LeaveCriticalSection(&_Module.m_csWindowCreate); return 0; memcpy(&m_wc, &wc, sizeof(wndclassex)); pwndproc = m_wc.lpfnwndproc; m_wc.lpszclassname = lpsz; m_wc.lpfnwndproc = proc; else _lpszcursorid); m_wc.hcursor = ::LoadCursor(m_bSystemCursor? NULL : hinst, m m_wc.hinstance = hinst; sses m_wc.style &= ~CS_GLOBALCLASS; // we don't register global cla if (m_wc.lpszclassname == NULL) wsprintf(m_szautoname, _T("ATL:%p"), &m_wc); m_wc.lpszclassname = m_szautoname; WNDCLASSEX wctemp; memcpy(&wctemp, &m_wc, sizeof(wndclassex)); e, &wctemp); m_atom = (ATOM)::GetClassInfoEx(m_wc.hInstance, m_wc.lpszclassnam if (m_atom == 0)
67 if(m_ucommonresourceid!= 0) // use it if not zero m_wc.hicon = (HICON)::LoadImage(_Module.GetResourceInsta nce(), MAKEINTRESOURCE(m_uCommonResourceID), IMAGE_ICON, 32, 32, LR_DEFAULTCOLO R); m_wc.hiconsm = (HICON)::LoadImage(_Module.GetResourceIn stance(), MAKEINTRESOURCE(m_uCommonResourceID), IMAGE_ICON, 16, 16, LR_DEFAULTCO LOR); m_atom = ::RegisterClassEx(&m_wc); ::LeaveCriticalSection(&_Module.m_csWindowCreate); if (m_lpszorigname!= NULL) ATLASSERT(pProc!= NULL); ATLASSERT(pWndProc!= NULL); *pproc = pwndproc; return m_atom; ; 不要管乱七八糟的一大堆, 关键部分就是 m_atom = ::RegisterClassEx(&m_wc); 显而易见, 这一句完成了真正的窗口类的注册 并用 m_atom 标记是否应注册过了 关于窗口类的注册, 我们还要留意很关键的一点, 那就是 wndproc 的地址 一路过来, 明显的就是 StartWindowProc 好的, 到此, 窗口类的注册已经完成了 下面 :
68 窗口的创建 CFrameWindowImplBase< TBase, TWinTraits >::Create(hWndParent, rect.m_lprect, szwindowname, dwstyle, dwexstyle, hmenu, atom, lpcreateparam); 这是这个函数的定义 : HWND Create(HWND hwndparent, _U_RECT rect, LPCTSTR szwindowname, DWO RD dwstyle, DWORD dwexstyle, _U_MENUorID MenuOrID, ATOM atom, LPVOID lpcreatepar am) ATLASSERT(m_hWnd == NULL); if(atom == 0) return NULL; _Module.AddCreateWndData(&m_thunk.cd, this); if(menuorid.m_hmenu == NULL && (dwstyle & WS_CHILD)) MenuOrID.m_hMenu = (HMENU)(UINT_PTR)this; if(rect.m_lprect == NULL) rect.m_lprect = &TBase::rcDefault; HWND hwnd=::createwindowex(dwexstyle, (LPCTSTR)(LONG_PTR)MAKEL ONG(atom, 0), szwindowname, dwstyle, rect.m_lprect->left, rect.m_lprect->top, rect.m_lprect->right - rect.m_lprect->left, rect.m_lprect->bottom-rect.m_lprect->top, hwndparent, MenuOrID.m_hMenu, _Module.GetModuleInstance(), lpcreateparam); ATLASSERT(m_hWnd == hwnd); return hwnd; if(atom == 0) return NULL; 检查窗口类是否已经正确注册了 然后是 CreateWindowEx 实质的创建工作 里面的参数窗口类 是 (LPCTSTR)(LONG_PTR)MAKELONG(atom, 0) 所以这里, 窗口类名没有被使用 注册窗口类时返 回的 atom 被用作相应的功能了 这个和 mfc 的做法很不一样 到现在为止, 窗口类已经注册并创建了一个窗口 我们回到 Run 中 :
69 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; 消息循环 AddMessageLoop 和 RemoveMessageLoop 把 theloop 挂到模块 ( 程序 ) 对象上或者取下 现在问题的核心是消息循环的处理 theloop.run(); 我们来看 CMessageLoop 的 Run 的定义 : int Run() BOOL bdoidle = TRUE; int nidlecount = 0; BOOL bret; for(;;)
70 le) while(!::peekmessage(&m_msg, NULL, 0, 0, PM_NOREMOVE) && bdoid if(!onidle(nidlecount++)) bdoidle = FALSE; bret = ::GetMessage(&m_msg, NULL, 0, 0); if(bret == -1) ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n")); continue; // error, don't process else if(!bret) ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n")); break; // WM_QUIT, exit message loop if(!pretranslatemessage(&m_msg)) ::TranslateMessage(&m_msg); ::DispatchMessage(&m_msg); if(isidlemessage(&m_msg)) bdoidle = TRUE; nidlecount = 0;
71 return (int)m_msg.wparam; 很简单, 就是用 PeekMessage 决定当前是否有消息需要处理, 然后在把需要处理的消息进行常规 的翻译和分发 其中有进行空闲时间处理的机会 然后消息循环已经开始了, 现在要关注是哪里处理消息? 消息的处理 前面都是小菜, 很清晰 到这里才遇到了大问题 我们回忆到 WNDCLASS 中的 wndproc 记录的是 StartWndProc 不论如何, 消息一开始进入的就是这个函数 抓住它, 就有希望 : template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >:: StartWindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) CWindowImplBaseT< TBase, TWinTraits >* pthis = (CWindowImplBaseT< TBase, T WinTraits >*)_Module.ExtractCreateWndData(); ATLASSERT(pThis!= NULL); pthis->m_hwnd = hwnd; pthis->m_thunk.init(pthis->getwindowproc(), pthis); WNDPROC pproc = (WNDPROC)&(pThis->m_thunk.thunk); (LONG)pProc); WNDPROC poldproc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, #ifdef _DEBUG // check if somebody has subclassed us already since we discard it if(poldproc!= StartWindowProc) n")); ATLTRACE2(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\ #else
72 poldproc; // avoid unused warning #endif return pproc(hwnd, umsg, wparam, lparam); 首先我们来看 SetWindowLong, 知道这个是干什么的吗?SetWindowLong 改变窗口的一些基本 属性 GWL_WNDPROC 表示要改变的是 wndproc 的地址 ^_^, 知道了为什么要先看这个了吧 这一 步就是要把 wndproc 改为 正确 的地方 也就是 pproc WNDPROC pproc = (WNDPROC)&(pThis->m_thunk.thunk); 这个就是执行了一次 StartWindowProc 之后,wndproc 将改为的函数 pthis 是从 _Module 中取 出来的 取出来的信息是窗口创建的时候记录的, 为了清晰, 先不管它, 反正它是一个 CWindowImplBa set 类型的指针 现在要明白 m_thunk 是啥子东西 m_thunk 定义在 CWindowImplBaseT 的基类中是类型为 CWn dprocthunk 的变量 我们来看 CWndProcThunk: class CWndProcThunk public: union _AtlCreateWndData cd; _WndProcThunk thunk; ; void Init(WNDPROC proc, void* pthis) #if defined (_M_IX86) thunk.m_mov = 0x042444C7; file://c C thunk.m_this = (DWORD)pThis; thunk.m_jmp = 0xe9; thunk.m_relproc = (int)proc - ((int)this+sizeof(_wndprocthunk));
73 #elif defined (_M_ALPHA) thunk.ldah_at = (0x279f0000 HIWORD(proc)) + (LOWORD(proc)>>15); thunk.ldah_a0 = (0x261f0000 HIWORD(pThis)) + (LOWORD(pThis)>>15); thunk.lda_at = 0x239c0000 LOWORD(proc); thunk.lda_a0 = 0x LOWORD(pThis); thunk.jmp = 0x6bfc0000; #endif // write block from data cache and file:// flush from instruction cache FlushInstructionCache(GetCurrentProcess(), &thunk, sizeof(thunk)); ; 恐怖吧, 居然出现了机器码 基本的思想是通过 Init 准备好一段机器码, 然后把机器码的地址作为 函数地址 这段机器码就干两件事情, 一个是把 wndproc 的 hwnd 参数替换为 pthis, 另外一个是跳转 到相应窗口的真实的 wndproc 中 pthis->getwindowproc() 这条代码返回的就是实际处理消息的地方 现在来看这个函数 : virtual WNDPROC GetWindowProc() return WindowProc; template <class TBase, class TWinTraits> LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >:: WindowProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) WinTraits >*)hwnd; CWindowImplBaseT< TBase, TWinTraits >* pthis = (CWindowImplBaseT< TBase, T
74 // set a ptr to this message and save the old value MSG msg = pthis->m_hwnd, umsg, wparam, lparam, 0, 0, 0 ; const MSG* poldmsg = pthis->m_pcurrentmsg; pthis->m_pcurrentmsg = &msg; // pass to the message map to process LRESULT lres; Param, lres, 0); BOOL bret = pthis->processwindowmessage(pthis->m_hwnd, umsg, wparam, l // restore saved value for the current message ATLASSERT(pThis->m_pCurrentMsg == &msg); pthis->m_pcurrentmsg = poldmsg; // do the default processing if message was not handled if(!bret) if(umsg!= WM_NCDESTROY) lres = pthis->defwindowproc(umsg, wparam, lparam); else // unsubclass, if needed C); LONG pfnwndproc = ::GetWindowLong(pThis->m_hWnd, GWL_WNDPRO lres = pthis->defwindowproc(umsg, wparam, lparam); if(pthis->m_pfnsuperwindowproc!= ::DefWindowProc && ::GetWindowLo ng(pthis->m_hwnd, GWL_WNDPROC) == pfnwndproc) pfnsuperwindowproc); ::SetWindowLong(pThis->m_hWnd, GWL_WNDPROC, (LONG)pThis->m_ // clear out window handle
75 HWND hwnd = pthis->m_hwnd; pthis->m_hwnd = NULL; // clean up after window is destroyed pthis->onfinalmessage(hwnd); return lres; 可见几经周折, 最终还是落到了派生类的 ProcessWindowMessage 中 这里同样使用了模拟虚函 数 还有一个问题是我在 CMainFrame 中并没有写 ProcessWindowMessage 啊? 但是你写了 BEGIN_MSG_MAP(CMainFrame) MESSAGE_HANDLER(WM_CREATE, OnCreate) COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit) COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew) COMMAND_ID_HANDLER(ID_FILE_OPEN, OnFileOpen) COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar) COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar) COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout) CHAIN_MSG_MAP(CUpdateUI<CMainFrame>) CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>) END_MSG_MAP() 这些东西其实就是 ProcessWindowMessage, 他们是宏 #define BEGIN_MSG_MAP(theClass) \ public: \ BOOL ProcessWindowMessage(HWND hwnd, UINT umsg, WPARAM wparam, LPA RAM lparam, LRESULT& lresult, DWORD dwmsgmapid = 0) \
76 \ BOOL bhandled = TRUE; \ hwnd; \ umsg; \ wparam; \ lparam; \ lresult; \ bhandled; \ switch(dwmsgmapid) \ \ case 0: #define MESSAGE_RANGE_HANDLER(msgFirst, msglast, func) \ if(umsg >= msgfirst && umsg <= msglast) \ \ bhandled = TRUE; \ lresult = func(umsg, wparam, lparam, bhandled); \ if(bhandled) \ return TRUE; \ #define COMMAND_ID_HANDLER(id, func) \ if(umsg == WM_COMMAND && id == LOWORD(wParam)) \ \ bhandled = TRUE; \ d); \ lresult = func(hiword(wparam), LOWORD(wParam), (HWND)lParam, bhandle if(bhandled) \
77 return TRUE; \ #define CHAIN_MSG_MAP(theChainClass) \ \ t)) \ if(thechainclass::processwindowmessage(hwnd, umsg, wparam, lparam, lresul return TRUE; \ #define END_MSG_MAP() \ break; \ default: \ wmsgmapid); \ ATLTRACE2(atlTraceWindowing, 0, _T("Invalid message map ID (%i)\n"), d ATLASSERT(FALSE); \ break; \ \ return FALSE; \ 至此, 一切都明白了 其实 WTL 只是 ATL 的窗口部分的扩展, 这里所分析的东西绝大部分是 ATL 中的 而且这部分内容在 ATL INTERNAL 中也有比较详细描述了 WTL 体系结构 绪论 WTL 最终来了, 而且提供了我所希望的功能. 我在 WTL Bytesize( 译文 ) 的文章列出 WTL 主要特征. 在本文中, 我将描述一下 WTL 的体系结构, 同时我会给出一些简单的例子来演示如何使用它的那些特征. 希望 能够对您有所帮助. WTL 应用程序的类型 WTL 有好几种应用程序类型, 供您在 AppWizard 选取.
78 下表对这些应用程序进行了描述. 这种弹性构成了 WTL 体系结构的一部分. 应用程序类型 描述 SDI Application 单文本界面 只有一个窗口 Multiple Threads SDI 单个进程拥有一个或多个窗口 MDI Application 多文本界面 在框架内, 您可以有零个或多个子窗口 Dialog Based 基于对话框模版 你可能还是首次听说多线程 SDI 应用程序, 但是不用担心, 它的概念很容易理解. 一个多线程 SDI 程序启动后它会有一个窗口, 窗口显示了一个文档. 当你想要程序要再创建一个文档时, 问题就出现了 --SDI 程序只能显示一个文档. 为了解决这个问题, 多线程 SDI 创建了另一个 SDI 窗口. 看起来是一个新的实例在运行, 实际上它不过是原来的进程创建了一个新的窗口, 并把它依附到进程的一个新线程. IE 的新建窗口就是这样做的. 除了多线程 SDI, 所有这些应用程序都可以作为 COM 服务器, 并且应用程序向导 (AppWizard) 为此提供了一个选项. 另外应用程序向导还可以让你指定该程序是否主持 ActiveX 控件. 令人费解的是, 不同的程序类型, 选取 "Host ActiveX Controls" 的地方不同. 除对话框应用程序外的其他类型在第一页上选取, 而对话框类型却放到第二页. 第二页的其他选项, 对对话框程序以外的类型都是可用的. 它们让你指定程序是否需要工具条 (tool bar), 状态条 (status bar) 和视窗口 (View Window). 如果选取了 "Toolbar" 选项, 你可以通过 "Rebar" 选择是否将工具条放入 IE Rebar 控件中. 如果你选取了 Rebar, 你就可以通过框架窗口 (frame window) 的成员 m_hwndtoolbar( 后边会有详细的描述 ) 来访问它. 你可以按照你的意愿, 在里边加入其他的工具条. 选取了 "Rebar" 后, 你可以决定是否选取 "Com mand Bar". Command bar 很像 CE 的 command bar 控件. 只是 WTL 是用一个类来实现, 而在 CE, co mmand bar 是一个系统窗口类 (system window class). Command bar 非常有用, 它能够把窗口也加入到工具条中去. 如果你选取了这个选项, 工具条和菜单都将被当做 toolbar 来实现. 这使菜单项也可以有关联的图标, 并且当你移动鼠标到一个菜单项上时, 该菜单项会被置成高亮. 从 Office 97 以来, Office 软件的菜单都具有上述特征. 第二页还有指定程序是否使用视的选项 ( 多半你想要使用 ), 同时你可以决定这些视如何实现. 下 表列出了所有可选的视.
79 视 描述 Generic Window 一个简单的窗口. 此类窗口允许程序员编写 WM_PAINT 消息的处理函数. 适用于需要 直接进行 paint 的文档. Form 这类视具有一个对话框模版. 适用于带 ActiveX 控件的窗口. 应用程序来操作这些控件. List Box 这个视是个 list box. 它最简单的形式意味着可以通过调用 AddString() 方法来添加字 符串. Edit 这个视是个 edit control. 本质上, 它提供了一个像 Notepad 一样的程序. List View 这个视是个 list view 通用控件. 用这个控件来显示相关的项 ( 比如, 控制面板是一个 Explorer 主持的 List View, 所有的项都是控制面板 applet). Tree View 这个视是个 tree view 通用控件. 这个适用于具有层次关系的数据, 比如, 可以用它来显 示数据库的 schema. 顶层分支为表和存储过程, 次级的分支为表中的字段. Rich Edit 这个视是个 rich edit 控件, 像 WordPad. HTML Page 这个视主持了一个 IE Web Browser 控件. 它把主持的一个 web page 当成一个视. 本文的例子需要一个对话框模版, 同时还需要菜单, 因此 Form view 是个理想的选择. 程序线程 跟 ATL 一样,WTL 程序也需要一个 _Module 全局变量来保存全局数据, 方便应用级代码访问. 在 W TL 中, 这个变量是 CAppModule 或 CServerAppModule 的实例, 后者在程序同时作为一个 COM 服务器 时用到. 每个应用程序具有一个或者多个 UI 线程.WTL 使用两种方式来管理这些线程. 如果应用程序只有一个 UI 线程 ( 除了多线程 SDI 以外, 其他程序类型默认只有一个 UI 线程 ), 线程调用全 局函数 run(): int Run(LPTSTR /*lpstrcmdline*/ = NULL, int ncmdshow = SW_SHOWDEFAULT) CMessageLoop theloop; _Module.AddMessageLoop(&theLoop); CMainFrame wndmain; if (wndmain.createex() == NULL)
80 ATLTRACE(_T("Main window creation failed!\n")); return 0; wndmain.showwindow(ncmdshow); int nret = theloop.run(); _Module.RemoveMessageLoop(); return nret; 线程的消息循环包含在 CMessageLoop 内部. 函数创建了一个 CMessageLoop 实例, 把它放入全局的消息循环映射 (message loop map) 数组. 以线程 ID 为索引, 线程中运行的其他的代码可以访问到这个实例. 消息循环对象包含了 message filter 和 idle handler. 运行在这个 UI 线程的 UI 元件 (UI elem ent) 可以有它自己的 idle handler, 在线程的消息队列为空时运行 译注 : 通过 CMessageLoop::AddIdle Handler() 把这个 UI 元件加入到 CMessageLoop 的 idle handler 数组中. CMessageLoop::Run() 包含了 UI 线程的主消息映射 (main message map). 下边是它的伪代码 : MSG m_msg; int CMessageLoop::Run() for (;;) while (!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE)) DoIdleHandlers(); bret = ::GetMessage(&m_msg, NULL, 0, 0); if(bret == -1) continue; else if(!bret) break; if (!DoMessageFilters(&m_msg)) ::TranslateMessage(&m_msg); ::DispatchMessage(&m_msg); return (int)m_msg.wparam; 可以看到, 这个函数推动着消息队列. 没有消息时, 运行注册到线程的 idle hander. 如果在队列中检测 到消息, 把它取出来, 传给每个 message filter. 如果消息没有被这些函数处理, 它将按照通常的方式, 发送到 目标窗口. 如果程序有超过一个的 UI 线程, 可以用 WTL 的线程管理器, 多线程 SDI 就是这样做的. 主线程作为一个 管理者线程, 它会为每个新窗口创建一个新的新线程. 主要流程如下 :
81 int nret = m_dwcount; DWORD dwret; while(m_dwcount > 0) dwret = ::MsgWaitForMultipleObjects(m_dwCount, m_arrthreadhandles, FALSE, INFINITE, QS_ALLINPUT); if(dwret >= WAIT_OBJECT_0 && dwret <= (WAIT_OBJECT_0 + m_dwcount - 1)) RemoveThread(dwRet - WAIT_OBJECT_0); else if(dwret == (WAIT_OBJECT_0 + m_dwcount)) ::GetMessage(&msg, NULL, 0, 0); if(msg.message == WM_USER) AddThread(_T(""), SW_SHOWNORMAL); 那些线程句柄放在一个数组中. 线程通过 AddThread() 加入到数组 ( 同时启动线程 ), RemoveThread() 从数组移走. wait 语句在两种情况下会被打断 : 线程死亡 ( 将线程从数组中移出 ) 或线程收到了 WM_USER 消息 ( 一个线程在一个新线程里新建了一个窗口 ). 线程管理者为程序中的一个类, 因此可以在循环中加入自己的 message handler, 比如, 当程序有不止一种窗口类型时. 创建一个新的窗口非常简单, 只需在任意一个窗口中调用 : ::PostThreadMessage(_Module.m_dwMainThreadID, WM_USER, 0, 0L); 这个循环会一直运行下去, 直到所有的 UI 线程都关闭了. UI 线程具有一个 thread procedure, 它跟单 UI 线 程的 Run() 方法一样. 不过, 由于线程管理者使用了 MsgWaitForMultipleObjects(), 这意味者最多只能有 MAXIMUM_WAIT_OBJECTS-1 个 UI 线程, 这也意味着最多只能创建 63 个窗口. 框架 WTL 实际上是两类窗口 : 框架窗口和视图窗口. 正如名字所暗示的那样, 框架窗口为窗口提供标题栏 (c aption bar) 和边框, 你的代码用它来处理工具条 (tool bar) 和菜单项命令. 你看到的程序窗口实际上是视图窗口, 视图覆盖了框架窗口的客户区. 客户区是指框架窗口没有被诸如状态条, 工具条之类的修饰部件所遮挡的部分. 线程会创建主框架窗口的一个实例, 创建视图的工作由主框架窗口的 WM_CREATE 消息处理函数完成. 对于 SDI 程序来说, 这个过程很简单. 把视图类的一个实例作为主框架类的一个成员, 调用视图类的 Create() 方法即可.MDI 程序稍微有些不同, MDI 主框架窗口通过 CMDIFrameWindowImpl<>::CreateMDICli ent() 建立一个名为 MDICLIENT 的窗口. 这个客户窗口将 CMDIChildWindowImpl<> 窗口当做它的子窗口, 子窗口有一个视图. 这也反映了这么一个事实,MDI 程序可以具有零个或者多个子窗口, 每个都有边框和标题栏. 框架窗口的 OnCreate() 很有意思, 让我看看 :
82 LRESULT OnCreate(UINT, WPARAM, LPARAM, BOOL&) // create command bar window HWND hwndcmdbar = m_cmdbar.create(m_hwnd, rcdefault, NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE); // attach menu m_cmdbar.attachmenu(getmenu()); // load command bar images m_cmdbar.loadimages(idr_mainframe); // remove old menu SetMenu(NULL); HWND hwndtoolbar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME, FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE); CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE); AddSimpleReBarBand(hWndCmdBar); AddSimpleReBarBand(hWndToolBar, NULL, TRUE); CreateSimpleStatusBar(); m_hwndclient = m_view.create(m_hwnd, rcdefault, NULL, WS_CHILD WS_VISIBLE WS_CLIPSIBLINGS WS_CLIPCHILDREN, WS_EX_CLIENTEDGE); UIAddToolBar(hWndToolBar); UISetCheck(ID_VIEW_TOOLBAR, 1); UISetCheck(ID_VIEW_STATUS_BAR, 1); CMessageLoop* ploop = _Module.GetMessageLoop(); ploop->addmessagefilter(this); ploop->addidlehandler(this); return 0; 这是从一个 SDI 程序拿来的一段代码, 该程序有一个基于 command bar 的工具条和一个状态条. 函数的第一行创建了一个 command bar 实例, 然后对它进行初始化, 在其中加入框架窗口的菜单和工具条位图. 这段代码先将菜单取出, 把所有的下拉菜单转换为工具条按钮, 并将菜单保存在一个变量中, 以备后用. 给人的感觉是菜单是由工具条实现的 - 那我们就把它叫做工具条菜单 (menu toolbar) 吧. 然后 Command Bar 将程序工具条的图标装入 image list 并将它们的 ID 保存在数组中. 当点击工具条菜单的按钮时,comman dbar 会找到对应的子菜单, 创建一个弹出菜单. Command bar 将子菜单项的 ID 和它保存的 ID 进行比较, 这些 ID 跟 image list 中的工具条按钮图标是相关联的. 如果比较成功, 则将关联的图标加到菜单项上去. 这意味着相同 ID 的菜单项和工具条按钮具有相同的图标. 接下来, 创建工具条并把它关联到 commandbar, 然后创建状态条和视图. 可以看到视图的 HWND 存放在 框架窗口的 m_hwndclient 变量中. 这个窗口句柄在框架窗口的 WM_SIZE handler 中会用到. 当框架窗 口改变大小时, 它告知视图改变自身, 于此同时也要考虑状态条和 command bar. 在下来的三行 ( 从调用 UIAddToolBar() 开始 ) 用来显示在运行时会改变状态的 UI 项 (UI item). 文章后面还 会重提这个话题. 最后, 访问消息循环 (message loop), 你应该还记得该消息循环存放在一全局数组中. G
83 etmessageloop() 取得当前线程的消息循环, 加入框架窗口的 message filter 和 idle handler, 分别默 认是 PreTranslateMessage() 和 OnIdle(). 框架窗口继承于以下类 : class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>, public CMessageFilter, public CIdleHandler 后两个抽象类宣称了框架窗口类实现了 PreTranslateMessage() 和 OnIdle(). 从 CUpdateUI<> 继承表 示框架类支持 UI update map. 视图 视图窗口看起来显得很简单 : class CMyView : public CWindowImpl<CMyView> public: DECLARE_WND_CLASS(NULL) BOOL PreTranslateMessage(MSG* pmsg) pmsg; return FALSE; BEGIN_MSG_MAP(CMyView) MESSAGE_HANDLER(WM_PAINT, OnPaint) END_MSG_MAP() LRESULT OnPaint(UINT, WPARAM, LPARAM, BOOL&) CPaintDC dc(m_hwnd); //TODO: Add your drawing code here return 0; ; 上面是一个 SDI 程序的视图类. 多线程 SDI 和 MDI 的视图类在本质上也跟这个一样, 但他们没有 PreTrans latemessage() 方法. SDI 程序就是使用这个函数, 赶在框架类处理消息之前把消息抓住. PreTranslateM essage() 在 SDI 的框架类中的实现是, 直接将消息转发给视图类. 这里显示的视图实际上没有做什么工作. 你应该自己在 OnPaint() 函数中加入画出文档内容的代码. 如果需 要支持输入, 如鼠标的点击和键盘的按键, 你应该加入相应消息处理函数到类和映射中. 可以看到这个窗口
84 是从 CWindowImpl<> 继承下来的, 如果你想让它基于一个 Win32 控件的话, 就应该从定义在 AtlCtrls.h 文件中某个 WTL 类继承. 如果想在基于 CWindowImpl<> 的类里加上滚动条, 那么你应该把基类换成 CScrollWindowImpl<>, 同 时把消息链给它 : class CMyView : public CScrollWindowImpl<CMyView> public: typedef CScrollWindowImpl<CMyView> parent; BEGIN_MSG_MAP(CMyView) CHAIN_MSG_MAP(parent) END_MSG_MAP() void DoPaint(CDCHandle dc) ; 基类保证窗口具备滚动条, 并提供滚动条消息的默认处理. 视图类不再有 WM_PAINT 的处理函数, 因为它已被 CScrollWindowImpl<> 处理. 根据滚动条的位置,CScrollWindowImpl<> 画出视图相对应的部分. 取而代之的是, 在你的类里实现 DoPaint(), 在这里你需要画出整个视图. 如果你想指定滚动的范围, 大小或起点, 你需要加上处理 WM_CREATE 消息的函数, 把这些初始化代码放到里边. 正如我先前所提到的, 框架窗口会改变视图窗口的大小, 以使它客户区未被状态条和工具条覆盖的部分为视图所填充. 在大多数情况下, 这样就够了. 但是当你想要一个具有 Windows Explorer 样子的程序时, 该怎么办呢? Windows Explorer 的窗口包含了一个 tree view 和一个 list view, 还有两者之间的分割条. WTL 的解决方案很简单 : 使用 splitter 窗口! 为此你需要改变一下框架窗口, 让它创建 splitter 窗口的一个实例作为它的视图. 例如, 在你的框架类里有 如下的数据成员 : CSplitterWindow m_view; CTreeViewCtrl m_tree; CListViewCtrl m_list; 你可以在 OnCreate() 创建一个 splitter 窗口 : // get the frame client rect, so that we set the splitter initial size // and we can get the splitter bar in the centre RECT rect; GetClientRect(&rect); m_hwndclient = m_view.create(m_hwnd, rect, NULL, WS_CHILD WS_VISIBLE); m_tree.create(m_view, rcdefault, NULL,
85 WS_CHILD WS_VISIBLE TVS_HASBUTTONS TVS_HASLINES TVS_LINESATROOT, WS_EX_CLIENTEDGE); m_list.create(m_view, rcdefault, NULL, WS_CHILD WS_VISIBLE LVS_REPORT, WS_EX_CLIENTEDGE); m_view.setsplitterpanes(m_tree, m_list); m_view.setsplitterpos(); Splitter 窗口如同一个视图, 将框架窗口作为它的父窗口. 在这段代码里, 我将框架窗口客户区的实际大小传给了 splitter 窗口. 我也可以在这里使用 rcdefault, 因为一旦框架窗口创建完成, 框架窗口就会转发 WM_ SIZE 消息给 splitter. 这样 splitter 可以马上改变自身的大小来填充框架. 然而, 当我准备使用不带参数的 S etsplitterpos(), 把分割条设置于窗口中线时, 出现了问题.Splitter 窗口使用它的大小来决定中线的位置, 由于 rcdefault 告诉窗口它的大小是 0( 因此中线的位置也是 0), 从而意味着分割条将出现在 z 最左边, 将左窗口隐藏了起来. 创建了 splitter 窗口后, 你需要创建那些你想要分割的窗口了. 它们将作为 splitter 窗口的子窗口被创建. 最后 你将这些子窗口通过 SetSplitterPanes() 加到 splitter 窗口中去, 并确定分割条的位置所在. UI Update 菜单项可以被设置为有效或无效, 可以带 check 记号或着像 radio 按钮一样, 在一组菜单项中同时有且只有一个能被 check. 此外, 菜单项还可以带图标和文字. 所有的这些状态都可以在运行时根据程序中的某个值进行改变. 工具条在某种程度上可以看做是菜单的易见形态, 因为它们的按钮可以个别地, 或者作为一组的一部分被置成有效或无效, 推入推出. UI update 机制允许你指定哪些 UI 元件 (UI element) 的状态可以在运行时改变. WTL 使用如下的 UI update 映射来实现这一功能 : BEGIN_UPDATE_UI_MAP(CMainFrame) UPDATE_ELEMENT(ID_FILE_SAVERESULTS, UPDUI_MENUPOPUP UPDUI_TOOLBAR) UPDATE_ELEMENT(ID_VIEW_TOOLBAR, UPDUI_MENUPOPUP) UPDATE_ELEMENT(ID_VIEW_STATUS_BAR, UPDUI_MENUPOPUP) END_UPDATE_UI_MAP() 这个例子指出三个菜单项在运行时有一个状态需要显示, 其中的一个, ID_FILE_SAVERESULTS, 还有一个工 具条按钮跟它相关联. WTL 通过建立一个数组来保存这些信息. 为此你需要完成两方面的工作 : 首先是 UI 元件的状态. 如果是菜单项, 你可以使用 UIEnable() 使能该菜单项, UISetCheck() 设置 c heck 记号, UISetText() 改变菜单的文字. 如果是工具条按钮, 那么你使用 UIEnable() 使能该按钮, UISetC heck() 或者 UISetRadio() 决定按钮是推入还是推出. 下边的代码根据是否有文本被选中, 来使能 Cut 菜单项和工具条按钮 : BOOL bselected = GetSelected(); UIEnable(ID_EDIT_CUT, bselected); 你可以把这样的代码放入相应处理函数中 ( 如一个菜单项的状态依赖于另一个菜单项的动作, 将它放入后者 的处理函数中 ), 或者放入 OnIdle() 方法, 通过检查某个类变量来决定元件的状态.
86 其次是确定各个 UI 元件是否都被更新了, 为此你需要调用 CUpdateUI<> 的某个方法将 UI 元件加入到列表 中. 主菜单已被自动加入, 但是其他的任何菜单和所有的工具条必须分别通过调用 UIAddMenuBar() 和 UI AddToolBar() 手动加入. 其他还有一堆事情要注意. 首先, 设置了工具条的状态后, 使用 UIUpdateToolBar() 以使工具条状态更新. 对于菜单, 你不需如此, 因为子菜单是动态生成的.UIUpdateMenuBar() 这个方法也存在, 但是它的作用是把菜单恢复到初始状态, 如果你改变过某些项的文字, 调用 UIUpdateMenuBar() 的结果可能不是你所期望的 ( 因为菜单项的文字会变成老的 ). 尽管还有一个方法 UISetRadio(), 但是还没有一个把几个菜单项或者工具条按钮当做 radio 按钮组 ( 也就是 说, 有一个而且只有一个被选中 ) 的机制. 如果你希望得到这样效果, 你必须自己编码, 不过它并不难. 对话框 ATL 的对话框支持一向很好, 对此 WTL 新增了通用对话框的封装. 本质上是为对话框加入了输入验证和回 调函数. 比如, 你想在用户改变年 Open 对话框中的文件夹时有所动作, 那么你应该从 CFileDialogImpl< > 继承一个类, 实现 OnFolderChange(): class CMyFileDialog : public CFileDialogImpl<CMyFileDialog> public: CMyFileDialog(BOOL b) : CFileDialogImpl<CMyFileDialog>(b) void OnFolderChange(LPOFNOTIFY lpon) char strfolder[max_path]; if (GetFolderPath(strFolder, sizeof(strfolder)) > 0) MessageBox(strFolder); ; 当文件夹的路径改变时,CFileDialogImpl<> 调用 OnFolderChange(). 该函数使用基类的 GetFol derpath(), 来取得新路径. 控件 WTL 为所有的 Win32 和通用控件提供了封装类, 包括 Windows 2000 新加入的. 虽然只是简单的包装, 但是它们使这些控件更加容易访问. 譬如, 你能记清楚从 List View 读出当前选定项的文字的消息和需要传的参数吗?( 实际上, 你需要发送两个消息, 一个是得到选定项的索引, 另一个是读出它的文字.) WTL 的作者为你完成了这些烦人的工作, 提供了一个简单的封装函数供你使用. 使用这些控件类有两种方法. 如果你的对话框里有一个控件, 你可以将控件的 HWND 依附到一个封装对
87 象, 使用封装类的方法来访问控件. 这种方法简化了你读写控件数据和处理 notification 消息的代码. 另外的用法是把这些类加到你的视图类的继承层次中去 : class CMyView : public CWindowImpl<CMyView, CListBox> 这表示 CWindowImpl<> 是从 CListBox 继承而来, 因此创建的窗口将是一个 list box ( 因为窗口类的名字是通过调用 CListBox::GetWndClassName() 得到的 ). 另外, ATL 的窗口机制会子类化这个窗口, 将发给它的消息路由到你的消息映射中去. 它保留了老的窗口函数, 这样, 你没有处理的消息将由老的窗口函数来处理. 当你的视图类从控件类继承时,WTL 就会使用这一技术. 在 notification 消息和子类化这个主题上, 有一点很值得指出, 那就是当某事件发生时, 绝大多数窗口控件都会发送 notification 消息给它们的父窗口. 让你窗口来处理这些 notification 消息要比子类化一个已存在控件窗口 ( 或子类化一个已存在的类, 然后建立一个实例 ), 从而在控件之前取得消息好得多. 譬如, 你想处理按钮的 click 事件, 你所需要做的只是处理 BN_CLICKED notification. 它将由按钮发送给你的窗口类. 另外的一种方法是从 CContainedWindow<> 子类化 BUTTON 窗口来处理 click 消息. 我之所以说这个是因为一个知名的 ATL 鼓吹者给我一份代码里就是这么做的. 他的代码取得一个简单的按 钮 click 事件所花的时间是别人的 3 到 4 倍, 因为他子类化了按钮控件, 而不是简单的处理 BN_CLICKED n otification. WTL 还提供了一些新的控件, 在 win32 中没有对等者. 你已经看到过一个 -- command bar, 实际上还有 其他一些非常有用类 : 类 描述 CBitmapButton 这是一个用位图替代标题的按钮. 你可以提供一个 image list, 里边包含按钮在正常状态, 失效, 推入和鼠标落在按钮 上的图表. CHyperLink 让你建立一个 static 控件, 它代表一个 hyperlink, 这样当 用户点击它时, 默认的 web 浏览器打开该链接. CWaitCursor 这不过是在它的构造函数中把鼠标图标改成等待状态, 而 在析构函数中还原. CCheckListViewCtrl 在每一项边上都有一个 check box 的 list box. CMultiPaneStatusBarCtrl 具有多个 pane 的状态条
88 WTL 源码剖析 --- ATLAPP.H ATLAPP.H 包含了消息循环类 接口类 和产生应用程序所必需的一些基础类定义 类定义如下 : CmessageFilter 类 --- 用于消息过滤的 CidleHandler 类 --- 用于空闲消息处理的 CmessageLoop 类 --- 用于消息循环的 CappModule 类 --- 应用程序基础类 CserverAppModule 类 --- 用于 Com 服务构架的应用程序类另外还有 3 个全局函数 : AtlGetDefaultGuiFont() 获得默认的显示字体 AtlCreateBoldFont() 产生一个粗体字体 AtlInitCommonControls() 初始化一些控件所需共同的 DLL WTL 程序的结构一个窗口程序的创建到销毁过程主要经过如下几个阶段 1. 注册窗口类 2. 创建窗口 3. 进入消息循环如果用 C 写过 Win32 窗口程序的人一定会记得如下的结构 : // 窗口过程处理函数 LRESULT CALLBACK WndProc(HWND hwnd,uint Message,WPARAM wparam,lparam l Param); int WINAPI WinMain(HINSTANCE hinstance,hinstance hprevinstance,lpstr szc mdline,int icmdshow) HWND hwnd = NULL; MSG msg; WNDCLASS wndclass; wndclass.style = CS_HREDRAW CS_VREDRAW; wndclass.lpfnwndproc = WndProc; // 注册窗口 if(!registerclass(&wndclass)) MessageBox(NULL,TEXT("Porgram requires Windows NT!"),szAppName,MB_ICO NERROR); return 0; // 创建窗口 hwnd = CreateWindow(szAppName,TEXT("My Application"), WS_OVERLAPPEDWINDOW WS_VSCROLL WS_HSCROLL, CW_USEDEFAULT,CW_USEDEFAULT,
89 CW_USEDEFAULT,CW_USEDEFAULT, NULL,NULL,hInstance,NULL); ShowWindow(hwnd,iCmdShow); UpdateWindow(hwnd); // 进入消息循环 while(getmessage(&msg,null,0,0)) TranslateMessage(&msg); DispatchMessage(&msg); return msg.wparam; 那么你可能会问 WTL 的 WinMain 函数再哪里? 如果你通过 WTL/ATL 导向生成一个应用程序, 那么你会在跟工程名字同名的.cpp 文件中发现如下的代码 : int WINAPI _twinmain(hinstance hinstance, HINSTANCE /*hprevinstance*/, LPTSTR lpstrc mdline, int ncmdshow) HRESULT hres = ::CoInitialize(NULL); // If you are running on NT 4.0 or higher you can use the following call instea d 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)); (MSLU) is used // this resolves ATL window thunking problem when Microsoft Layer for Unicode ::DefWindowProc(NULL, 0, 0, 0L); AtlInitCommonControls(ICC_COOL_CLASSES ICC_BAR_CLASSES); // add flags to s upport other controls D(hRes)); hres = _Module.Init(NULL, hinstance); // 等下分析它的实现 ATLASSERT(SUCCEEDE int nret = Run(lpstrCmdLine, ncmdshow);// 程序的关键分 _Module.Term(); ::CoUninitialize(); return nret;
90 从这个 _twinmain 函数的定义, 你可以发现程序的关键部分是我紫色标记出来的 Run() 函数 这个函数是一个自定义的函数, 不过如果通过 ATL/WTL 导向程序, 那么会自动生成这样一个 Run() 函数的, 下面我们先分析一下这个自动生成的 Run 函数 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; 通过这个 Run 函数我们可以看到在函数中完成了如下几个过程 : 1. 生成一个消息循环对象 (theloop) 2. 在全局的 _Module 中加入这个消息循环 3. 生成一个应用程序框架对象 4. 显示应用程序框架 5. 开始消息循环 6. 结束消息循环 7. 返回 WinMain 函数, 结束程序实现分析在这篇文章我不想过多的分析应用程序框架和窗口的细节, 这些内容将放在以后的几篇文章中详细分析, 本文主要对 ATLAPP.H 头文件中实现的一些过程进行详细分析 首先从全局变量 _Module 开始 _Module 维持着生成应用程序的主线程, 控制着程序的消息循环队列, 是一个 CAppModule 的对象 该 CAppModule 从 ATL::CcomModule 继承 在 WTL::CappModule 中定义了 8 个公有成员函数, 分别为 : AddMessageLoop() 添加一个消息循环, 进入消息循环队列里
91 RemoveMessageLoop() 移除消息循环队列 GetMessageLoop() 获得消息循环 InitSettingChangeNotify() 初始化环境 AddSettingChangeNotify() 添加一个窗口句柄 RemoveSettingChangeNotify() 清理环境除了 8 个公有成员函数外, 该类还定义了 3 个公有成员变量 m_dwmainthreadid 负责保存该应用程序的主线程 ID m_pmsgloopmap 负责存储消息循环 m_psettingchangenotify 负责存放窗口句柄下面分别来分析几个主要成员函数的实现 : BOOL AddMessageLoop(CMessageLoop* pmsgloop) CStaticDataInitCriticalSectionLock lock; // 锁住关键片断, 由于进程同步的关系!!! if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in CApp Module::AddMessageLoop.\n")); ATLASSERT(FALSE); return FALSE; n map yet ATLASSERT(pMsgLoop!= NULL); ATLASSERT(m_pMsgLoopMap->Lookup(::GetCurrentThreadId()) == NULL); // not i BOOL bret = m_pmsgloopmap->add(::getcurrentthreadid(), pmsgloop); lock.unlock(); return bret; 关键部分我用红色的字体标记出来了, 意思是什么? 通过当前线程的 Id 来标示一个消息循环, 存储在 m_pmsgloopmap 中 BOOL RemoveMessageLoop() CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::RemoveMessageLoop.\n")); ATLASSERT(FALSE); return FALSE;
92 BOOL bret = m_pmsgloopmap->remove(::getcurrentthreadid()); lock.unlock(); return bret; 关键部分同样通过红色字体标记出来, 嗯, 没错正如 AddMessageLoop 函数一样, 该函数也是通过线程 Id 来寻找消息循环移除对象的 CMessageLoop* GetMessageLoop(DWORD dwthreadid = ::GetCurrentThreadId()) const CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::GetMessageLoop.\n")); ATLASSERT(FALSE); return NULL; CMessageLoop* ploop = m_pmsgloopmap->lookup(dwthreadid); lock.unlock(); return ploop; 该函数通过线程 Id 在 m_pmsgloopmap 消息队列中寻找对应的消息循环, 找到后返回 BOOL InitSettingChangeNotify(DLGPROC pfndlgproc = _SettingChangeDlgProc) CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::InitSettingChangeNotify.\n")); ATLASSERT(FALSE); return FALSE; if(m_psettingchangenotify == NULL) typedef ATL::CSimpleArray<HWND> _notifyclass;
93 ATLTRY(m_pSettingChangeNotify = new _notifyclass); ATLASSERT(m_pSettingChangeNotify!= NULL); BOOL bret = (m_psettingchangenotify!= NULL); if(bret && m_psettingchangenotify->getsize() == 0) // init everything _ATL_EMPTY_DLGTEMPLATE templ; // 增加一个无模式对话框 HWND hntfwnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, N ULL, pfndlgproc); ATLASSERT(::IsWindow(hNtfWnd)); if(::iswindow(hntfwnd)) // need conditional code because types don't match in winuser.h #ifdef _WIN64 ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this); #else ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this)); #endif // 加入该窗口句柄 bret = m_psettingchangenotify->add(hntfwnd); else bret = FALSE; lock.unlock(); return bret; 该函数用来初始化一个存放窗口句柄的对象 BOOL InitSettingChangeNotify(DLGPROC pfndlgproc = _SettingChangeDlgProc) CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::InitSettingChangeNotify.\n")); ATLASSERT(FALSE);
94 return FALSE; if(m_psettingchangenotify == NULL) typedef ATL::CSimpleArray<HWND> _notifyclass; ATLTRY(m_pSettingChangeNotify = new _notifyclass); ATLASSERT(m_pSettingChangeNotify!= NULL); BOOL bret = (m_psettingchangenotify!= NULL); if(bret && m_psettingchangenotify->getsize() == 0) // init everything //?? 空的 ATL Dialog Template 吗? _ATL_EMPTY_DLGTEMPLATE templ; // 增加一个无模式对话框 HWND hntfwnd = ::CreateDialogIndirect(GetModuleInstance(), &templ, N ULL, pfndlgproc); ATLASSERT(::IsWindow(hNtfWnd)); if(::iswindow(hntfwnd)) // need conditional code because types don't match in winuser.h #ifdef _WIN64 ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, (LONG_PTR)this); #else ::SetWindowLongPtr(hNtfWnd, GWLP_USERDATA, PtrToLong(this)); #endif bret = m_psettingchangenotify->add(hntfwnd); else bret = FALSE; lock.unlock(); return bret; // 清理消息 void TermSettingChangeNotify()
95 CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::TermSettingChangeNotify.\n")); ATLASSERT(FALSE); return; 0) if(m_psettingchangenotify!= NULL && m_psettingchangenotify->getsize() > // 销毁窗口 ::DestroyWindow((*m_pSettingChangeNotify)[0]); delete m_psettingchangenotify; m_psettingchangenotify = NULL; lock.unlock(); BOOL AddSettingChangeNotify(HWND hwnd) CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock())) ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::AddSettingChangeNotify.\n")); ATLASSERT(FALSE); return FALSE; ATLASSERT(::IsWindow(hWnd)); BOOL bret = FALSE; if(initsettingchangenotify()!= FALSE) bret = m_psettingchangenotify->add(hwnd); lock.unlock(); return bret; BOOL RemoveSettingChangeNotify(HWND hwnd) CStaticDataInitCriticalSectionLock lock; if(failed(lock.lock()))
96 ATLTRACE2(atlTraceUI, 0, _T("ERROR : Unable to lock critical section in C AppModule::RemoveSettingChangeNotify.\n")); ATLASSERT(FALSE); return FALSE; BOOL bret = FALSE; if(m_psettingchangenotify!= NULL) bret = m_psettingchangenotify->remove(hwnd); lock.unlock(); return bret; 现在回到刚才提到的 Run() 函数, 里面最开始就定义了一个 CmessageLoop 循环对象, 然后通过 _Module 对象的 AddMessageLoop 成员函数加入到循环队列里面, 直到 _Module 调用了 RemoveMe ssageloop 移除循环队列, 程序才结束循环, 返回到 WinMain 函数 在这里还有一个比较重要的类, 那就是 CMessageLoop, 是他维持了系统的消息, 维持了程序的生命周期 那么下面我们来看看这个类的定义和具体的实现方法 CmessageLoop 包含了如下一些成员函数和成员变量成员变量 // 处理消息 ATL::CSimpleArray<CMessageFilter*> m_amsgfilter; // 处理空闲句柄 ATL::CSimpleArray<CIdleHandler*> m_aidlehandler; //Win32API 消息结构 MSG m_msg; 成员函数 ( 用红色标记的函数是虚函数 ) AddMessageFilter 加入一条消息过滤 RemoveMessageFilter 移除一条消息过滤 AddIdleHandler 加入一个空闲句柄 RemoveIdleHandler 移出一个空闲句柄 AddUpdateUI 为了兼容老的 ATL 而设计的 RemoveUpdateUI 为了兼容老的 ATL 而设计的 IsIdleMessage 过滤一些比如 WM_MOUSEMOVE 之类的消息 Run 消息循环 关键部分!!! PreTranslateMessage 消息过滤 OnIdle 空闲处理 再这里我不准备对每个函数都进行详细的分析, 主要分析核心的函数 Run,CmessageLoop 由它来 维持着系统的消息循环
97 函数如下 : int Run() // 空闲? BOOL bdoidle = TRUE; // 空闲计数器 int nidlecount = 0; // 返回标志 BOOL bret; // 开始消息循环了哦!!! for(;;) // 当 bdoidle 为 TRUE, 并且不能从消息队列里面取出消息了, 那么开始空闲操作了! //PM_NOREMOVE: 再 PeekMessage 函数处理后不将消息从队列里移除 while(bdoidle &&!::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE)) if(!onidle(nidlecount++)) bdoidle = FALSE; // 从当前线程获取一个消息 // 返回 -1 表示出现一个错误 // 返回 0 表示提交了一个 WM_QUIT, 程序将要退出 // 成功获得一个消息, 返回不等于 0 的值 bret = ::GetMessage(&m_msg, NULL, 0, 0); if(bret == -1) ATLTRACE2(atlTraceUI, 0, _T("::GetMessage returned -1 (error)\n")); continue; // error, don't process else if(!bret) ATLTRACE2(atlTraceUI, 0, _T("CMessageLoop::Run - exiting\n")); break; // WM_QUIT, exit message loop // 如果熟悉使用 c 语言来写 Win32 的程序员会发现, 原来 WinMain 中的哪个处理消息循环的语句放到这里来了!!! if(!pretranslatemessage(&m_msg)) //translates virtual-key messages into character messages.
98 ::TranslateMessage(&m_msg); //dispatches a message to a window procedure ::DispatchMessage(&m_msg); // 判断是否为空闲消息? // 排除 WM_MOUSEMOVE WM_NCMOUSEMOVE WM_SYSTIMER 消息 if(isidlemessage(&m_msg)) bdoidle = TRUE; nidlecount = 0; return (int)m_msg.wparam; 以上就是对 ATLAPP.H 中的几个比较重要的类的分析, 还有其他几个类的分析我将放在以后的文章中 ( 待续 )
概述
OPC Version 1.6 build 0910 KOSRDK Knight OPC Server Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOS_Init...5 2.2.2 KOS_InitB...5 2.2.3
INTRODUCTION TO COM.DOC
How About COM & ActiveX Control With Visual C++ 6.0 Author: Curtis CHOU [email protected] This document can be freely release and distribute without modify. ACTIVEX CONTROLS... 3 ACTIVEX... 3 MFC ACTIVEX
BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2
PowerBuilder 9 PowerBuilder Native Interface(PBNI) PowerBuilder 9 PowerBuilder C++ Java PowerBuilder 9 PBNI PowerBuilder Java C++ PowerBuilder NVO / PowerBuilder C/C++ PowerBuilder 9.0 PowerBuilder Native
int *p int a 0x00C7 0x00C7 0x00C int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++;
Memory & Pointer [email protected] 2.1 2.1.1 1 int *p int a 0x00C7 0x00C7 0x00C7 2.1.2 2 int I[2], *pi = &I[0]; pi++; char C[2], *pc = &C[0]; pc++; float F[2], *pf = &F[0]; pf++; 2.1.3 1. 2. 3. 3 int A,
FY.DOC
高 职 高 专 21 世 纪 规 划 教 材 C++ 程 序 设 计 邓 振 杰 主 编 贾 振 华 孟 庆 敏 副 主 编 人 民 邮 电 出 版 社 内 容 提 要 本 书 系 统 地 介 绍 C++ 语 言 的 基 本 概 念 基 本 语 法 和 编 程 方 法, 深 入 浅 出 地 讲 述 C++ 语 言 面 向 对 象 的 重 要 特 征 : 类 和 对 象 抽 象 封 装 继 承 等 主
untitled
MFC WTL : Orbit (www.winmsg.com) Windows MFC MFC MFC (MFC 4.21 1998 Windows 95 windows NT4) 200K 3%-4% MFC CodeProject Class MFC minigui WTL COM MFC ATL ATL ATL ATL WTL WTL WTL WTL MSDN 0 WTL MFC WTL MFC
CC213
: (Ken-Yi Lee), E-mail: [email protected] 49 [P.51] C/C++ [P.52] [P.53] [P.55] (int) [P.57] (float/double) [P.58] printf scanf [P.59] [P.61] ( / ) [P.62] (char) [P.65] : +-*/% [P.67] : = [P.68] : ,
untitled
1 Outline 數 料 數 數 列 亂數 練 數 數 數 來 數 數 來 數 料 利 料 來 數 A-Z a-z _ () 不 數 0-9 數 不 數 SCHOOL School school 數 讀 school_name schoolname 易 不 C# my name 7_eleven B&Q new C# (1) public protected private params override
Microsoft Word - 11.doc
除 錯 技 巧 您 將 於 本 章 學 到 以 下 各 項 : 如 何 在 Visual C++ 2010 的 除 錯 工 具 控 制 下 執 行 程 式? 如 何 逐 步 地 執 行 程 式 的 敘 述? 如 何 監 看 或 改 變 程 式 中 的 變 數 值? 如 何 監 看 程 式 中 計 算 式 的 值? 何 謂 Call Stack? 何 謂 診 斷 器 (assertion)? 如 何
无类继承.key
无类继承 JavaScript 面向对象的根基 周爱 民 / aimingoo [email protected] https://aimingoo.github.io https://github.com/aimingoo rand = new Person("Rand McKinnon",... https://docs.oracle.com/cd/e19957-01/816-6408-10/object.htm#1193255
概述
OPC Version 1.8 build 0925 KOCRDK Knight OPC Client Rapid Development Toolkits Knight Workgroup, eehoo Technology 2002-9 OPC 1...4 2 API...5 2.1...5 2.2...5 2.2.1 KOC_Init...5 2.2.2 KOC_Uninit...5 2.3...5
新版 明解C++入門編
511!... 43, 85!=... 42 "... 118 " "... 337 " "... 8, 290 #... 71 #... 413 #define... 128, 236, 413 #endif... 412 #ifndef... 412 #if... 412 #include... 6, 337 #undef... 413 %... 23, 27 %=... 97 &... 243,
Guava学习之Resources
Resources 提供提供操作 classpath 路径下所有资源的方法 除非另有说明, 否则类中所有方法的参数都不能为 null 虽然有些方法的参数是 URL 类型的, 但是这些方法实现通常不是以 HTTP 完成的 ; 同时这些资源也非 classpath 路径下的 下面两个函数都是根据资源的名称得到其绝对路径, 从函数里面可以看出,Resources 类中的 getresource 函数都是基于
SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基
开放数据处理服务 ODPS SDK SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基础功能的主体接口, 搜索关键词 "odpssdk-core" 一些
Microsoft Word - 01.DOC
第 1 章 JavaScript 简 介 JavaScript 是 NetScape 公 司 为 Navigator 浏 览 器 开 发 的, 是 写 在 HTML 文 件 中 的 一 种 脚 本 语 言, 能 实 现 网 页 内 容 的 交 互 显 示 当 用 户 在 客 户 端 显 示 该 网 页 时, 浏 览 器 就 会 执 行 JavaScript 程 序, 用 户 通 过 交 互 式 的
提问袁小兵:
C++ 面 试 试 题 汇 总 柯 贤 富 管 理 软 件 需 求 分 析 篇 1. STL 类 模 板 标 准 库 中 容 器 和 算 法 这 部 分 一 般 称 为 标 准 模 板 库 2. 为 什 么 定 义 虚 的 析 构 函 数? 避 免 内 存 问 题, 当 你 可 能 通 过 基 类 指 针 删 除 派 生 类 对 象 时 必 须 保 证 基 类 析 构 函 数 为 虚 函 数 3.
epub83-1
C++Builder 1 C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r C + + B u i l d e r 1.1 1.1.1 1-1 1. 1-1 1 2. 1-1 2 A c c e s s P a r a d o x Visual FoxPro 3. / C / S 2 C + + B u i l d e r / C
ebook
3 3 3.1 3.1.1 ( ) 90 3 1966 B e r n s t e i n P ( i ) R ( i ) W ( i P ( i P ( j ) 1) R( i) W( j)=φ 2) W( i) R( j)=φ 3) W( i) W( j)=φ 3.1.2 ( p r o c e s s ) 91 Wi n d o w s Process Control Bl o c k P C
Bus Hound 5
Bus Hound 5.0 ( 1.0) 21IC 2007 7 BusHound perisoft PC hound Bus Hound 6.0 5.0 5.0 Bus Hound, IDE SCSI USB 1394 DVD Windows9X,WindowsMe,NT4.0,2000,2003,XP XP IRP Html ZIP SCSI sense USB Bus Hound 1 Bus
Microsoft PowerPoint - gp2.ppt
Windows 視窗程式設計 (1) 靜宜大學資訊管理學系蔡奇偉副教授 大綱 Windows 視窗系統的特性 Windows API MSDN 線上說明文件 匈牙利 (Hungarian) 命名法 一個最少行的 Windows 視窗程式 Windows 程式的事件處理模型 視窗程式的骨架 1 Windows 視窗系統的特性 圖形化的人機介面 圖形顯示器 視窗 滑鼠 + 鍵盤 Multiprocessing
C PICC C++ C++ C C #include<pic.h> C static volatile unsigned char 0x01; static volatile unsigned char 0x02; static volatile unsigned cha
CYPOK CYPOK 1 UltraEdit Project-->Install Language Tool: Language Suite----->hi-tech picc Tool Name ---->PICC Compiler Executable ---->c:hi-picinpicc.exe ( Command-line Project-->New Project-->File Name--->myc
Microsoft Word - CIN-DLL.doc
6.3. 调 用 动 态 链 接 库 (DLL) 相 对 于 CIN 来 讲,NI 更 推 荐 用 户 使 用 DLL 来 共 享 基 于 文 本 编 程 语 言 开 发 的 代 码 除 了 共 享 或 重 复 利 用 代 码, 开 发 人 员 还 能 利 用 DLL 封 装 软 件 的 功 能 模 块, 以 便 这 些 模 块 能 被 不 同 开 发 工 具 利 用 在 LabVIEW 中 使 用
帝国CMS下在PHP文件中调用数据库类执行SQL语句实例
帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例 这篇文章主要介绍了帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例, 本文还详细介绍了帝国 CMS 数据库类中的一些常用方法, 需要的朋友可以参考下 例 1: 连接 MYSQL 数据库例子 (a.php)
<4D F736F F D E4345C6BDCCA84323B1E0B3CCD2AAB5E3D6AED2BB2E646F63>
基于 WINCE 平台 C# 编程要点之一 本文主要介绍在基于 Windows CE 平台的英创嵌入式主板下进行 C#(Microsoft Visual Stdio.Net 2005) 应用程序开发时会常常用到的一些功能函数以及开发方法, 这些方法适用于英创采用 WinCE 平台的所有型号嵌入式主板, 包括 EM9000 EM9260 EM9160 等 本文要点包括 : 文件的删除和复制 如何获取存取设备的空间大小
1 Project New Project 1 2 Windows 1 3 N C test Windows uv2 KEIL uvision2 1 2 New Project Ateml AT89C AT89C51 3 KEIL Demo C C File
51 C 51 51 C C C C C C * 2003-3-30 [email protected] C C C C KEIL uvision2 MCS51 PLM C VC++ 51 KEIL51 KEIL51 KEIL51 KEIL 2K DEMO C KEIL KEIL51 P 1 1 1 1-1 - 1 Project New Project 1 2 Windows 1 3 N C test
OOP with Java 通知 Project 4: 4 月 19 日晚 9 点
OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 19 日晚 9 点 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double x) { d
基于UML建模的管理管理信息系统项目案例导航——VB篇
PowerBuilder 8.0 PowerBuilder 8.0 12 PowerBuilder 8.0 PowerScript PowerBuilder CIP PowerBuilder 8.0 /. 2004 21 ISBN 7-03-014600-X.P.. -,PowerBuilder 8.0 - -.TP311.56 CIP 2004 117494 / / 16 100717 http://www.sciencep.com
ebook50-11
11 Wi n d o w s C A D 53 M F C 54 55 56 57 58 M F C 11.1 53 11-1 11-1 MFC M F C C D C Wi n d o w s Wi n d o w s 4 11 199 1. 1) W M _ PA I N T p W n d C W n d C D C * p D C = p W n d GetDC( ); 2) p W n
Microsoft Word - PHP7Ch01.docx
PHP 01 1-6 PHP PHP HTML HTML PHP CSSJavaScript PHP PHP 1-6-1 PHP HTML PHP HTML 1. Notepad++ \ch01\hello.php 01: 02: 03: 04: 05: PHP 06:
ebook140-8
8 Microsoft VPN Windows NT 4 V P N Windows 98 Client 7 Vintage Air V P N 7 Wi n d o w s NT V P N 7 VPN ( ) 7 Novell NetWare VPN 8.1 PPTP NT4 VPN Q 154091 M i c r o s o f t Windows NT RAS [ ] Windows NT4
A Preliminary Implementation of Linux Kernel Virus and Process Hiding
邵 俊 儒 翁 健 吉 妍 年 月 日 学 号 学 号 学 号 摘 要 结 合 课 堂 知 识 我 们 设 计 了 一 个 内 核 病 毒 该 病 毒 同 时 具 有 木 马 的 自 动 性 的 隐 蔽 性 和 蠕 虫 的 感 染 能 力 该 病 毒 获 得 权 限 后 会 自 动 将 自 身 加 入 内 核 模 块 中 劫 持 的 系 统 调 用 并 通 过 简 单 的 方 法 实 现 自 身 的
全国计算机技术与软件专业技术资格(水平)考试
全 国 计 算 机 技 术 与 软 件 专 业 技 术 资 格 ( 水 平 ) 考 试 2008 年 上 半 年 程 序 员 下 午 试 卷 ( 考 试 时 间 14:00~16:30 共 150 分 钟 ) 试 题 一 ( 共 15 分 ) 阅 读 以 下 说 明 和 流 程 图, 填 补 流 程 图 中 的 空 缺 (1)~(9), 将 解 答 填 入 答 题 纸 的 对 应 栏 内 [ 说 明
新・明解C言語入門編『索引』
!... 75!=... 48 "... 234 " "... 9, 84, 240 #define... 118, 213 #include... 148 %... 23 %... 23, 24 %%... 23 %d... 4 %f... 29 %ld... 177 %lf... 31 %lu... 177 %o... 196 %p... 262 %s... 242, 244 %u... 177
res/layout 目录下的 main.xml 源码 : <?xml version="1.0" encoding="utf 8"?> <TabHost android:layout_height="fill_parent" xml
拓展训练 1- 界面布局 1. 界面布局的重要性做应用程序, 界面是最基本的 Andorid 的界面, 需要写在 res/layout 的 xml 里面, 一般情况下一个 xml 对应一个界面 Android 界面布局有点像写 html( 连注释代码的方式都一样 ), 要先给 Android 定框架, 然后再在框架里面放控件,Android 提供了几种框架,AbsoluteLayout,LinearLayout,
Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]
Arrays and Strings 存储同类型的多个元素 Store multi elements of the same type 数组 (array) 存储固定数目的同类型元素 如整型数组存储的是一组整数, 字符数组存储的是一组字符 数组的大小称为数组的尺度 (dimension). 定义格式 : type arrayname[dimension]; 如声明 4 个元素的整型数组 :intarr[4];
EJB-Programming-4-cn.doc
EJB (4) : (Entity Bean Value Object ) JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Session Bean J2EE Session Façade Design Pattern Session Bean Session
RunPC2_.doc
PowerBuilder 8 (5) PowerBuilder Client/Server Jaguar Server Jaguar Server Connection Cache Thin Client Internet Connection Pooling EAServer Connection Cache Connection Cache Connection Cache Connection
Fun Time (1) What happens in memory? 1 i n t i ; 2 s h o r t j ; 3 double k ; 4 char c = a ; 5 i = 3; j = 2; 6 k = i j ; H.-T. Lin (NTU CSIE) Referenc
References (Section 5.2) Hsuan-Tien Lin Deptartment of CSIE, NTU OOP Class, March 15-16, 2010 H.-T. Lin (NTU CSIE) References OOP 03/15-16/2010 0 / 22 Fun Time (1) What happens in memory? 1 i n t i ; 2
C语言的应用.PDF
AVR C 9 1 AVR C IAR C, *.HEX, C,,! C, > 9.1 AVR C MCU,, AVR?! IAR AVR / IAR 32 ALU 1KBytes - 8MBytes (SPM ) 16 MBytes C C *var1, *var2; *var1++ = *--var2; AVR C 9 2 LD R16,-X ST Z+,R16 Auto (local
TwinCAT 1. TwinCAT TwinCAT PLC PLC IEC TwinCAT TwinCAT Masc
TwinCAT 2001.12.11 TwinCAT 1. TwinCAT... 3 2.... 4... 4...11 3. TwinCAT PLC... 13... 13 PLC IEC 61131-3... 14 4. TwinCAT... 17... 17 5. TwinCAT... 18... 18 6.... 19 Maschine.pro... 19... 27 7.... 31...
untitled
MODBUS 1 MODBUS...1 1...4 1.1...4 1.2...4 1.3...4 1.4... 2...5 2.1...5 2.2...5 3...6 3.1 OPENSERIAL...6 3.2 CLOSESERIAL...8 3.3 RDMULTIBIT...8 3.4 RDMULTIWORD...9 3.5 WRTONEBIT...11 3.6 WRTONEWORD...12
// HDevelopTemplateWPF projects located under %HALCONEXAMPLES%\c# using System; using HalconDotNet; public partial class HDevelopExport public HTuple
halcon 与 C# 混合编程之 Halcon 代码调用 写在前面 完成 halcon 与 C# 混合编程的环境配置后, 进行界面布局设计构思每一个按钮所需要实现 的功能, 将 Halcon 导出的代码复制至相应的 C# 模块下即可 halcon 源程序 : dev_open_window(0, 0, 512, 512, 'black', WindowHandle) read_image (Image,
RUN_PC連載_12_.doc
PowerBuilder 8 (12) PowerBuilder 8.0 PowerBuilder PowerBuilder 8 PowerBuilder 8 / IDE PowerBuilder PowerBuilder 8.0 PowerBuilder PowerBuilder PowerBuilder PowerBuilder 8.0 PowerBuilder 6 PowerBuilder 7
Simulator By SunLingxi 2003
Simulator By SunLingxi [email protected] 2003 windows 2000 Tornado ping ping 1. Tornado Full Simulator...3 2....3 3. ping...6 4. Tornado Simulator BSP...6 5. VxWorks simpc...7 6. simulator...7 7. simulator
SDS 1.3
Applied Biosystems 7300 Real-Time PCR System (With RQ Study) SDS 1.3 I. ~ I. 1. : Dell GX280 2.8GHz with Dell 17 Flat monitor 256 MB RAM 40 GB hard drive DVD-RW drive Microsoft Windows XP Operating System
WinMDI 28
WinMDI WinMDI 2 Region Gate Marker Quadrant Excel FACScan IBM-PC MO WinMDI WinMDI IBM-PC Dr. Joseph Trotter the Scripps Research Institute WinMDI HP PC WinMDI WinMDI PC MS WORD, PowerPoint, Excel, LOTUS
OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课
OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课 复习 Java 包 创建包 : package 语句, 包结构与目录结构一致 使用包 : import restaurant/ - people/ - Cook.class - Waiter.class - tools/ - Fork.class
6 C51 ANSI C Turbo C C51 Turbo C C51 C51 C51 C51 C51 C51 C51 C51 C C C51 C51 ANSI C MCS-51 C51 ANSI C C C51 bit Byte bit sbit
6 C51 ANSI C Turbo C C51 Turbo C C51 C51 C51 C51 C51 C51 C51 C51 C51 6.1 C51 6.1.1 C51 C51 ANSI C MCS-51 C51 ANSI C C51 6.1 6.1 C51 bit Byte bit sbit 1 0 1 unsigned char 8 1 0 255 Signed char 8 11 128
chap07.key
#include void two(); void three(); int main() printf("i'm in main.\n"); two(); return 0; void two() printf("i'm in two.\n"); three(); void three() printf("i'm in three.\n"); void, int 标识符逗号分隔,
MFC 2/e PDF GBK mirror - anyway solution MFC 1/e MFC 2/e
2/e 1998/04 MFC 1/e Windows MFC MFC 2/e 1998/05 1998 UNALIS 3/e 2/e 2/e 3/e 3/e MFC 2/e MFC 3/e MFC MFC 2/e VC5+MFC42 VC6+MFC421 MFC 2/e 1 MFC 2/e PDF http://www.jjhou.com http://expert.csdn.net/jjhou
Microsoft PowerPoint - 8. 运算符重载 Operator Overloading.pptx
运算符重载 Operator Overloading class Point { public: ; double x_, y_; Why Operator Overloading? Point (double x =0, double y = 0):x_(x),y_(y) { int main(){ Point a(1., 2), b(3,4); Point c = a + b; return 0;
ebook51-14
14 Wi n d o w s M F C 53 54 55 56 ( ) ( Wo r k e r T h r e a d ) 57 ( ) ( U s e r Interface Thread) 58 59 14.1 53 1. 2. C l a s s Wi z a r d O n I d l e () 3. Class Wi z a r d O n I d l e () O n I d l
untitled
A, 3+A printf( ABCDEF ) 3+ printf( ABCDEF ) 2.1 C++ main main main) * ( ) ( ) [ ].* ->* ()[] [][] ** *& char (f)(int); ( ) (f) (f) f (int) f int char f char f(int) (f) char (*f)(int); (*f) (int) (
ebook 86-15
15 G t k + d e l e t e _ e v e n t G n o m e G n o m e 15.1 GnomeDialog G t k + G n o m e D i a l o g 15.1.1 G n o m e D i a l o g g n o m e _ d i a l o g _ n e w ( ) G N O M E _ D I A L O G ( d i a l
bingdian001.com
1. DLL(Dynamic Linkable Library) DLL ± lib EXE DLL DLL EXE EXE ± EXE DLL 1 DLL DLL DLL Windows DLL Windows API Visual Basic Visual C++ Delphi 2 Windows system32 kernel32.dll user32.dll gdi32.dll windows
1.ai
HDMI camera ARTRAY CO,. LTD Introduction Thank you for purchasing the ARTCAM HDMI camera series. This manual shows the direction how to use the viewer software. Please refer other instructions or contact
NOWOER.OM m/n m/=n m/n m%=n m%n m%=n m%n m/=n 4. enum string x1, x2, x3=10, x4, x5, x; 函数外部问 x 等于什么? 随机值 5. unsigned char *p1; unsigned long *p
NOWOER.OM /++ 程师能 评估. 单项选择题 1. 下 描述正确的是 int *p1 = new int[10]; int *p2 = new int[10](); p1 和 p2 申请的空间 的值都是随机值 p1 和 p2 申请的空间 的值都已经初始化 p1 申请的空间 的值是随机值,p2 申请的空间 的值已经初始化 p1 申请的空间 的值已经初始化,p2 申请的空间 的值是随机值 2.
OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料
OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 Email: [email protected] 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料 OOP with Java Java 类型 引用 不可变类型 对象存储位置 作用域 OOP
_汪_文前新ok[3.1].doc
普 通 高 校 本 科 计 算 机 专 业 特 色 教 材 精 选 四 川 大 学 计 算 机 学 院 国 家 示 范 性 软 件 学 院 精 品 课 程 基 金 青 年 基 金 资 助 项 目 C 语 言 程 序 设 计 (C99 版 ) 陈 良 银 游 洪 跃 李 旭 伟 主 编 李 志 蜀 唐 宁 九 李 涛 主 审 清 华 大 学 出 版 社 北 京 i 内 容 简 介 本 教 材 面 向
mvc
Build an application Tutor : Michael Pan Application Source codes - - Frameworks Xib files - - Resources - ( ) info.plist - UIKit Framework UIApplication Event status bar, icon... delegation [UIApplication
软件工程文档编制
实训抽象类 一 实训目标 掌握抽象类的定义 使用 掌握运行时多态 二 知识点 抽象类的语法格式如下 : public abstract class ClassName abstract void 方法名称 ( 参数 ); // 非抽象方法的实现代码 在使用抽象类时需要注意如下几点 : 1 抽象类不能被实例化, 实例化的工作应该交由它的子类来完成 2 抽象方法必须由子类来进行重写 3 只要包含一个抽象方法的抽象类,
51 C 51 isp 10 C PCB C C C C KEIL
http://wwwispdowncom 51 C " + + " 51 AT89S51 In-System-Programming ISP 10 io 244 CPLD ATMEL PIC CPLD/FPGA ARM9 ISP http://wwwispdowncom/showoneproductasp?productid=15 51 C C C C C ispdown http://wwwispdowncom
Microsoft Word - 3D手册2.doc
第 一 章 BLOCK 前 处 理 本 章 纲 要 : 1. BLOCK 前 处 理 1.1. 创 建 新 作 业 1.2. 设 定 模 拟 控 制 参 数 1.3. 输 入 对 象 数 据 1.4. 视 图 操 作 1.5. 选 择 点 1.6. 其 他 显 示 窗 口 图 标 钮 1.7. 保 存 作 业 1.8. 退 出 DEFORMTM3D 1 1. BLOCK 前 处 理 1.1. 创 建
ebook140-9
9 VPN VPN Novell BorderManager Windows NT PPTP V P N L A V P N V N P I n t e r n e t V P N 9.1 V P N Windows 98 Windows PPTP VPN Novell BorderManager T M I P s e c Wi n d o w s I n t e r n e t I S P I
, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1
21 , 7, Windows,,,, : 010-62782989 13501256678 13801310933,,,, ;,, ( CIP) /,,. : ;, 2005. 11 ( 21 ) ISBN 7-81082 - 634-4... - : -. TP316-44 CIP ( 2005) 123583 : : : : 100084 : 010-62776969 : 100044 : 010-51686414
06 01 action JavaScript action jquery jquery AJAX CSS jquery CSS jquery HTML CSS jquery.css() getter setter.css('backgroundcolor') jquery CSS b
06 01 action JavaScript action jquery jquery AJAX 04 4-1 CSS jquery CSS jquery HTML CSS jquery.css() getter setter.css('backgroundcolor') jquery CSS background-color camel-cased DOM backgroundcolor.css()
1 4 1.1 4 1.2..4 2..4 2.1..4 3.4 3.1 Java.5 3.1.1..5 3.1.2 5 3.1.3 6 4.6 4.1 6 4.2.6 5 7 5.1..8 5.1.1 8 5.1.2..8 5.1.3..8 5.1.4..9 5.2..9 6.10 6.1.10
Java V1.0.1 2007 4 10 1 4 1.1 4 1.2..4 2..4 2.1..4 3.4 3.1 Java.5 3.1.1..5 3.1.2 5 3.1.3 6 4.6 4.1 6 4.2.6 5 7 5.1..8 5.1.1 8 5.1.2..8 5.1.3..8 5.1.4..9 5.2..9 6.10 6.1.10 6.2.10 6.3..10 6.4 11 7.12 7.1
epub 61-2
2 Web Dreamweaver UltraDev Dreamweaver 3 We b We b We Dreamweaver UltraDev We b Dreamweaver UltraDev We b We b 2.1 Web We b We b D r e a m w e a v e r J a v a S c r i p t We b We b 2.1.1 Web We b C C +
ebook14-4
4 TINY LL(1) First F o l l o w t o p - d o w n 3 3. 3 backtracking parser predictive parser recursive-descent parsing L L ( 1 ) LL(1) parsing L L ( 1 ) L L ( 1 ) 1 L 2 L 1 L L ( k ) k L L ( 1 ) F i r s
目 錄 壹 青 輔 會 結 案 附 件 貳 活 動 計 劃 書 參 執 行 內 容 一 教 學 內 容 二 與 當 地 教 師 教 學 交 流 三 服 務 執 行 進 度 肆 執 行 成 效 一 教 學 課 程 二 與 當 地 教 師 教 學 交 流 三 服 務 滿 意 度 調 查 伍 服 務 檢
2 0 1 0 年 靜 宜 青 年 國 際 志 工 泰 北 服 務 成 果 報 告 指 導 單 位 : 行 政 院 青 年 輔 導 委 員 會 僑 務 委 員 會 主 辦 單 位 : 靜 宜 大 學 服 務 學 習 發 展 中 心 協 力 單 位 : 靜 宜 大 學 師 資 培 育 中 心 財 團 法 人 台 灣 明 愛 文 教 基 金 會 中 華 民 國 九 十 九 年 九 月 二 十 四 日 目
Microsoft Word - template.doc
HGC efax Service User Guide I. Getting Started Page 1 II. Fax Forward Page 2 4 III. Web Viewing Page 5 7 IV. General Management Page 8 12 V. Help Desk Page 13 VI. Logout Page 13 Page 0 I. Getting Started
C 1
C homepage: xpzhangme 2018 5 30 C 1 C min(x, y) double C // min c # include # include double min ( double x, double y); int main ( int argc, char * argv []) { double x, y; if( argc!=
Microsoft Word - ch04三校.doc
4-1 4-1-1 (Object) (State) (Behavior) ( ) ( ) ( method) ( properties) ( functions) 4-2 4-1-2 (Message) ( ) ( ) ( ) A B A ( ) ( ) ( YourCar) ( changegear) ( lowergear) 4-1-3 (Class) (Blueprint) 4-3 changegear
Strings
Inheritance Cheng-Chin Chiang Relationships among Classes A 類 別 使 用 B 類 別 學 生 使 用 手 機 傳 遞 訊 息 公 司 使 用 金 庫 儲 存 重 要 文 件 人 類 使 用 交 通 工 具 旅 行 A 類 別 中 有 B 類 別 汽 車 有 輪 子 三 角 形 有 三 個 頂 點 電 腦 內 有 中 央 處 理 單 元 A
Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO
Car DVD New GUI IR Flow User Manual V0.1 Jan 25, 2008 19, Innovation First Road Science Park Hsin-Chu Taiwan 300 R.O.C. Tel: 886-3-578-6005 Fax: 886-3-578-4418 Web: www.sunplus.com Important Notice SUNPLUS
四川省普通高等学校
四 川 省 普 通 高 等 学 校 计 算 机 应 用 知 识 和 能 力 等 级 考 试 考 试 大 纲 (2013 年 试 行 版 ) 四 川 省 教 育 厅 计 算 机 等 级 考 试 中 心 2013 年 1 月 目 录 一 级 考 试 大 纲 1 二 级 考 试 大 纲 6 程 序 设 计 公 共 基 础 知 识 6 BASIC 语 言 程 序 设 计 (Visual Basic) 9
3.1 num = 3 ch = 'C' 2
Java 1 3.1 num = 3 ch = 'C' 2 final 3.1 final : final final double PI=3.1415926; 3 3.2 4 int 3.2 (long int) (int) (short int) (byte) short sum; // sum 5 3.2 Java int long num=32967359818l; C:\java\app3_2.java:6:
Microsoft PowerPoint - directx01.ppt
電腦遊戲程式設計 DirectX 簡介 靜宜大學資訊管理學系蔡奇偉副教授 大綱 何謂 DirectX? DirecX 的模組 HAL 和 HEL COM 檢查安裝的 DirectX 版本 遊戲程式的骨架 DirectX 繪圖程式的骨架 版權所有 : 靜宜大學資管系蔡奇偉副教授 1 何謂 DirectX? Windows API 的架構無法滿足電腦遊戲與多媒體軟體的即時性需求, 因而微軟公司規劃出 DirectX
Microsoft PowerPoint - string_kruse [兼容模式]
Strings Strings in C not encapsulated Every C-string has type char *. Hence, a C-string references an address in memory, the first of a contiguous set of bytes that store the characters making up the string.
untitled
8086/8088 CIP /. 2004.8 ISBN 7-03-014239-X.... TP313 CIP 2004 086019 16 100717 http://www.sciencep.com * 2004 8 2004 8 1 5 500 787 1092 1/16 16 1/2 391 000 1 2 ii 1 2 CAI CAI 3 To the teacher To the student
untitled
1 Outline 料 類 說 Tang, Shih-Hsuan 2006/07/26 ~ 2006/09/02 六 PM 7:00 ~ 9:30 聯 [email protected] www.csie.ntu.edu.tw/~r93057/aspnet134 度 C# 力 度 C# Web SQL 料 DataGrid DataList 參 ASP.NET 1.0 C# 例 ASP.NET 立
