深入剖析WTL.doc

Size: px
Start display at page:

Download "深入剖析WTL.doc"

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(&#38;m_msg, NULL, 0, 0, PM_NOREMOVE) &#38;&#38; bdoidle) if(!onidle(nidlecount++)) bdoidle = FALSE; bret = ::GetMessage(&#38;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(&#38;m_msg)) ::TranslateMessage(&#38;m_msg); ::DispatchMessage(&#38;m_msg); if(isidlemessage(&#38;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) \

概述

概述 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

More information

INTRODUCTION TO COM.DOC

INTRODUCTION TO COM.DOC How About COM & ActiveX Control With Visual C++ 6.0 Author: Curtis CHOU mahler@ms16.hinet.net This document can be freely release and distribute without modify. ACTIVEX CONTROLS... 3 ACTIVEX... 3 MFC ACTIVEX

More information

BOOL EnumWindows(WNDENUMPROC lparam); lpenumfunc, LPARAM (Native Interface) PowerBuilder PowerBuilder PBNI 2

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

More information

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++;

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 trio@seu.edu.cn 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,

More information

FY.DOC

FY.DOC 高 职 高 专 21 世 纪 规 划 教 材 C++ 程 序 设 计 邓 振 杰 主 编 贾 振 华 孟 庆 敏 副 主 编 人 民 邮 电 出 版 社 内 容 提 要 本 书 系 统 地 介 绍 C++ 语 言 的 基 本 概 念 基 本 语 法 和 编 程 方 法, 深 入 浅 出 地 讲 述 C++ 语 言 面 向 对 象 的 重 要 特 征 : 类 和 对 象 抽 象 封 装 继 承 等 主

More information

untitled

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

More information

d2.doc

d2.doc 2 Windows Windows Windows Windows Windows Windows Windows Windows Windows Windows DOS Windows Windows Windows 1.0 Microsoft 2 Windows Windows 1.0 DOS Windows 1.0 80286 8086 Microsoft Windows 2.0 Windows

More information

Microsoft PowerPoint - gp3.ppt

Microsoft PowerPoint - gp3.ppt Windows 視窗程式設計 (2) 靜宜大學資訊管理學系蔡奇偉副教授 大綱 視窗的結構 Painting and Repainting GDI Device Context 視窗版的 Hello, world! 程式 取得裝置的功能資訊 版權所有 : 靜宜大學資訊管理學系蔡奇偉副教授 1 視窗的結構 標題列 (title) 工具列 (tools) 功能表 (menu) 工作區 (client) 狀態列

More information

CC213

CC213 : (Ken-Yi Lee), E-mail: feis.tw@gmail.com 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] : ,

More information

mfc.doc

mfc.doc SDK 编程讲座 ( 一 ) 摘自 SDK 路报 no.1 ( 电子版 ) Wndows 编程两种方式 : 1.SDK 编程 : 用 C 语言直接调用 Windows API 函数. 这类 API 函数有上千个 ; 2.MFC 编程 : 用类将上述 API 封装起来, 用 C++ 来调用. 一般只需 20 多个 windows 类和另外 20 多个通用的非 windows 类就可 " 干活 " 了.

More information

1. 注册自己的控件类 我把控件类名称定义为 "HyperLinkCtrl", 还要为窗口额外分配空间, 这样才能迚行更多的控制 // 注册控件类 ATOM WINAPI RegisterHyperLinkCtrl(HINSTANCE hins) WNDCLASSEX wndclass; ZeroM

1. 注册自己的控件类 我把控件类名称定义为 HyperLinkCtrl, 还要为窗口额外分配空间, 这样才能迚行更多的控制 // 注册控件类 ATOM WINAPI RegisterHyperLinkCtrl(HINSTANCE hins) WNDCLASSEX wndclass; ZeroM Win32 编程迚阶 : 打造自己的标准控件作者 :cntrump 前言 Windows 给我们提供了很多的标准控件, 基本上够用的 但是有时候我们会对标准控件丌满意, 这时候就可以考虑自己编写控件 本教程的目的是编写一个出一个简单的标准控件, 作用类似于网页上的超链接, 除了可以接受 Windows 常规消息还可以处理控件自定义的消息 程序运行的效果如下 : 鼠标点击之后就会打开在程序中所指定的链接

More information

untitled

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

More information

Microsoft Word - 11.doc

Microsoft Word - 11.doc 除 錯 技 巧 您 將 於 本 章 學 到 以 下 各 項 : 如 何 在 Visual C++ 2010 的 除 錯 工 具 控 制 下 執 行 程 式? 如 何 逐 步 地 執 行 程 式 的 敘 述? 如 何 監 看 或 改 變 程 式 中 的 變 數 值? 如 何 監 看 程 式 中 計 算 式 的 值? 何 謂 Call Stack? 何 謂 診 斷 器 (assertion)? 如 何

More information

无类继承.key

无类继承.key 无类继承 JavaScript 面向对象的根基 周爱 民 / aimingoo aiming@gmail.com 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

More information

概述

概述 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

More information

新版 明解C++入門編

新版 明解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,

More information

Guava学习之Resources

Guava学习之Resources Resources 提供提供操作 classpath 路径下所有资源的方法 除非另有说明, 否则类中所有方法的参数都不能为 null 虽然有些方法的参数是 URL 类型的, 但是这些方法实现通常不是以 HTTP 完成的 ; 同时这些资源也非 classpath 路径下的 下面两个函数都是根据资源的名称得到其绝对路径, 从函数里面可以看出,Resources 类中的 getresource 函数都是基于

More information

SDK 概要 使用 Maven 的用户可以从 Maven 库中搜索 "odps-sdk" 获取不同版本的 Java SDK: 包名 odps-sdk-core odps-sdk-commons odps-sdk-udf odps-sdk-mapred odps-sdk-graph 描述 ODPS 基

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" 一些

More information

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数

OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 4 月 18 日晚 9 点 关于抄袭 没有分数 复习 类的复用 组合 (composition): has-a 关系 class MyType { public int i; public double d; public char c; public void set(double

More information

Microsoft Word - 01.DOC

Microsoft Word - 01.DOC 第 1 章 JavaScript 简 介 JavaScript 是 NetScape 公 司 为 Navigator 浏 览 器 开 发 的, 是 写 在 HTML 文 件 中 的 一 种 脚 本 语 言, 能 实 现 网 页 内 容 的 交 互 显 示 当 用 户 在 客 户 端 显 示 该 网 页 时, 浏 览 器 就 会 执 行 JavaScript 程 序, 用 户 通 过 交 互 式 的

More information

提问袁小兵:

提问袁小兵: C++ 面 试 试 题 汇 总 柯 贤 富 管 理 软 件 需 求 分 析 篇 1. STL 类 模 板 标 准 库 中 容 器 和 算 法 这 部 分 一 般 称 为 标 准 模 板 库 2. 为 什 么 定 义 虚 的 析 构 函 数? 避 免 内 存 问 题, 当 你 可 能 通 过 基 类 指 针 删 除 派 生 类 对 象 时 必 须 保 证 基 类 析 构 函 数 为 虚 函 数 3.

More information

epub83-1

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

More information

Microsoft PowerPoint - Introduction to Windows Programming and MFC

Microsoft PowerPoint - Introduction to Windows Programming and MFC Introduction to Windows Programming and MFC 2006-10 几个重要的概念 Windows 编程基础与消息机制 MFC 框架 重要概念 API SDK DLL and Lib MFC API Application Programming Interface. 其实就是操作系统留给应用程序的一个调用接口, 应用程序通过调用操作系统的 API 而使操作系统去执行应用程序的命令

More information

ebook

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

More information

untitled

untitled 3 C++ 3.1 3.2 3.3 3.4 new delete 3.5 this 3.6 3.7 3.1 3.1 class struct union struct union C class C++ C++ 3.1 3.1 #include struct STRING { typedef char *CHARPTR; // CHARPTR s; // int strlen(

More information

Bus Hound 5

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

More information

Microsoft PowerPoint - gp2.ppt

Microsoft PowerPoint - gp2.ppt Windows 視窗程式設計 (1) 靜宜大學資訊管理學系蔡奇偉副教授 大綱 Windows 視窗系統的特性 Windows API MSDN 線上說明文件 匈牙利 (Hungarian) 命名法 一個最少行的 Windows 視窗程式 Windows 程式的事件處理模型 視窗程式的骨架 1 Windows 視窗系統的特性 圖形化的人機介面 圖形顯示器 視窗 滑鼠 + 鍵盤 Multiprocessing

More information

提纲 1 2 OS Examples for 3

提纲 1 2 OS Examples for 3 第 4 章 Threads2( 线程 2) 中国科学技术大学计算机学院 October 28, 2009 提纲 1 2 OS Examples for 3 Outline 1 2 OS Examples for 3 Windows XP Threads I An Windows XP application runs as a seperate process, and each process may

More information

C PICC C++ C++ C C #include<pic.h> C static volatile unsigned char 0x01; static volatile unsigned char 0x02; static volatile unsigned cha

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

More information

Microsoft Word - CIN-DLL.doc

Microsoft Word - CIN-DLL.doc 6.3. 调 用 动 态 链 接 库 (DLL) 相 对 于 CIN 来 讲,NI 更 推 荐 用 户 使 用 DLL 来 共 享 基 于 文 本 编 程 语 言 开 发 的 代 码 除 了 共 享 或 重 复 利 用 代 码, 开 发 人 员 还 能 利 用 DLL 封 装 软 件 的 功 能 模 块, 以 便 这 些 模 块 能 被 不 同 开 发 工 具 利 用 在 LabVIEW 中 使 用

More information

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例

帝国CMS下在PHP文件中调用数据库类执行SQL语句实例 帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例 这篇文章主要介绍了帝国 CMS 下在 PHP 文件中调用数据库类执行 SQL 语句实例, 本文还详细介绍了帝国 CMS 数据库类中的一些常用方法, 需要的朋友可以参考下 例 1: 连接 MYSQL 数据库例子 (a.php)

More information

<4D F736F F D E4345C6BDCCA84323B1E0B3CCD2AAB5E3D6AED2BB2E646F63>

<4D F736F F D E4345C6BDCCA84323B1E0B3CCD2AAB5E3D6AED2BB2E646F63> 基于 WINCE 平台 C# 编程要点之一 本文主要介绍在基于 Windows CE 平台的英创嵌入式主板下进行 C#(Microsoft Visual Stdio.Net 2005) 应用程序开发时会常常用到的一些功能函数以及开发方法, 这些方法适用于英创采用 WinCE 平台的所有型号嵌入式主板, 包括 EM9000 EM9260 EM9160 等 本文要点包括 : 文件的删除和复制 如何获取存取设备的空间大小

More information

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

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 pnzwzw@163.com 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

More information

OOP with Java 通知 Project 4: 4 月 19 日晚 9 点

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

More information

基于UML建模的管理管理信息系统项目案例导航——VB篇

基于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

More information

EJB-Programming-3.PDF

EJB-Programming-3.PDF :, JBuilder EJB 2.x CMP EJB Relationships JBuilder EJB Test Client EJB EJB Seminar CMP Entity Beans Value Object Design Pattern J2EE Design Patterns Value Object Value Object Factory J2EE EJB Test Client

More information

ebook50-11

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

More information

Microsoft Word - PHP7Ch01.docx

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:

More information

ebook140-8

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

More information

A Preliminary Implementation of Linux Kernel Virus and Process Hiding

A Preliminary Implementation of Linux Kernel Virus and Process Hiding 邵 俊 儒 翁 健 吉 妍 年 月 日 学 号 学 号 学 号 摘 要 结 合 课 堂 知 识 我 们 设 计 了 一 个 内 核 病 毒 该 病 毒 同 时 具 有 木 马 的 自 动 性 的 隐 蔽 性 和 蠕 虫 的 感 染 能 力 该 病 毒 获 得 权 限 后 会 自 动 将 自 身 加 入 内 核 模 块 中 劫 持 的 系 统 调 用 并 通 过 简 单 的 方 法 实 现 自 身 的

More information

全国计算机技术与软件专业技术资格(水平)考试

全国计算机技术与软件专业技术资格(水平)考试 全 国 计 算 机 技 术 与 软 件 专 业 技 术 资 格 ( 水 平 ) 考 试 2008 年 上 半 年 程 序 员 下 午 试 卷 ( 考 试 时 间 14:00~16:30 共 150 分 钟 ) 试 题 一 ( 共 15 分 ) 阅 读 以 下 说 明 和 流 程 图, 填 补 流 程 图 中 的 空 缺 (1)~(9), 将 解 答 填 入 答 题 纸 的 对 应 栏 内 [ 说 明

More information

新・明解C言語入門編『索引』

新・明解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

More information

res/layout 目录下的 main.xml 源码 : <?xml version="1.0" encoding="utf 8"?> <TabHost android:layout_height="fill_parent" xml

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,

More information

Microsoft PowerPoint - 4. 数组和字符串Arrays and Strings.ppt [兼容模式]

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];

More information

EJB-Programming-4-cn.doc

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

More information

RunPC2_.doc

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

More information

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

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

More information

C语言的应用.PDF

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

More information

TwinCAT 1. TwinCAT TwinCAT PLC PLC IEC TwinCAT TwinCAT Masc

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...

More information

untitled

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

More information

// HDevelopTemplateWPF projects located under %HALCONEXAMPLES%\c# using System; using HalconDotNet; public partial class HDevelopExport public HTuple

// 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,

More information

RUN_PC連載_12_.doc

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

More information

Simulator By SunLingxi 2003

Simulator By SunLingxi 2003 Simulator By SunLingxi sunlingxi@sina.com 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

More information

untitled

untitled 1 行 行 行 行.NET 行 行 類 來 行 行 Thread 類 行 System.Threading 來 類 Thread 類 (1) public Thread(ThreadStart start ); Name 行 IsAlive 行 行狀 Start 行 行 Suspend 行 Resume 行 行 Thread 類 (2) Sleep 行 CurrentThread 行 ThreadStart

More information

SDS 1.3

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

More information

WinMDI 28

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

More information

OOP with Java 通知 Project 3: 3 月 29 日晚 9 点 4 月 1 日上课

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

More information

Microsoft Word - 第5章.doc

Microsoft Word - 第5章.doc MFC 是微软的基础类库, 其核心是以 C++ 类的形式封装了 Windows 的 API 函数 利用 Visual C++ 6.0 可以快速开发基于 MFC 的 Windows 应用程序 通过本章的学习, 读者可以了解 MFC 基础类库, 同时掌握 Windows 应用程序设计的特点和程序设计方法 5.1 什么是 Microsoft 类库 在 1.1.3 节中已经简单地介绍了 MFC 的基本概念,Microsoft

More information

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 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

More information

Chapter 9: Objects and Classes

Chapter 9: Objects and Classes Java application Java main applet Web applet Runnable Thread CPU Thread 1 Thread 2 Thread 3 CUP Thread 1 Thread 2 Thread 3 ,,. (new) Thread (runnable) start( ) CPU (running) run ( ) blocked CPU sleep(

More information

chap07.key

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 标识符逗号分隔,

More information

MFC 2/e PDF GBK mirror - anyway solution MFC 1/e MFC 2/e

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

More information

Microsoft PowerPoint - 8. 运算符重载 Operator Overloading.pptx

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;

More information

ebook51-14

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

More information

untitled

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) (

More information

ebook 86-15

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

More information

bingdian001.com

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

More information

1.ai

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

More information

华恒家庭网关方案

华恒家庭网关方案 LINUX V1.5 1 2 1 2 LINUX WINDOWS PC VC LINUX WINDOWS LINUX 90% GUI LINUX C 3 REDHAT 9 LINUX PC TFTP/NFS http://www.hhcn.com/chinese/embedlinux-res.html minicom NFS mount C HHARM9-EDU 1 LINUX HHARM9-EDU

More information

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 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.

More information

OOP with Java 通知 Project 2 提交时间 : 3 月 14 日晚 9 点 另一名助教 : 王桢 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料

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: 51141201063@ecnu.cn 学习使用文本编辑器 学习使用 cmd: Power shell 阅读参考资料 OOP with Java Java 类型 引用 不可变类型 对象存储位置 作用域 OOP

More information

PowerPoint Presentation

PowerPoint Presentation 列 Kernel Objects Windows Kernel Object 來 理 行 行 What is a Kernel Object? The data structure maintains information about the object Process Object: 錄了 PID, priority, exit code File Object: 錄了 byte offset,

More information

_汪_文前新ok[3.1].doc

_汪_文前新ok[3.1].doc 普 通 高 校 本 科 计 算 机 专 业 特 色 教 材 精 选 四 川 大 学 计 算 机 学 院 国 家 示 范 性 软 件 学 院 精 品 课 程 基 金 青 年 基 金 资 助 项 目 C 语 言 程 序 设 计 (C99 版 ) 陈 良 银 游 洪 跃 李 旭 伟 主 编 李 志 蜀 唐 宁 九 李 涛 主 审 清 华 大 学 出 版 社 北 京 i 内 容 简 介 本 教 材 面 向

More information

mvc

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

More information

软件工程文档编制

软件工程文档编制 实训抽象类 一 实训目标 掌握抽象类的定义 使用 掌握运行时多态 二 知识点 抽象类的语法格式如下 : public abstract class ClassName abstract void 方法名称 ( 参数 ); // 非抽象方法的实现代码 在使用抽象类时需要注意如下几点 : 1 抽象类不能被实例化, 实例化的工作应该交由它的子类来完成 2 抽象方法必须由子类来进行重写 3 只要包含一个抽象方法的抽象类,

More information

51 C 51 isp 10 C PCB C C C C KEIL

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

More information

Microsoft Word - 3D手册2.doc

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. 创 建

More information

ebook140-9

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

More information

, 7, Windows,,,, : ,,,, ;,, ( CIP) /,,. : ;, ( 21 ) ISBN : -. TP CIP ( 2005) 1

, 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

More information

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 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()

More information

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

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

More information

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc

Microsoft Word - 把时间当作朋友(2011第3版)3.0.b.06.doc 2 5 8 11 0 13 1. 13 2. 15 3. 18 1 23 1. 23 2. 26 3. 28 2 36 1. 36 2. 39 3. 42 4. 44 5. 49 6. 51 3 57 1. 57 2. 60 3. 64 4. 66 5. 70 6. 75 7. 83 8. 85 9. 88 10. 98 11. 103 12. 108 13. 112 4 115 1. 115 2.

More information

epub 61-2

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 +

More information

ebook14-4

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

More information

OOP with Java 通知 Project 4: 推迟至 4 月 25 日晚 9 点

OOP with Java 通知 Project 4: 推迟至 4 月 25 日晚 9 点 OOP with Java Yuanbin Wu cs@ecnu OOP with Java 通知 Project 4: 推迟至 4 月 25 日晚 9 点 复习 Protected 可以被子类 / 同一包中的类访问, 不能被其他类访问 弱化的 private 同时赋予 package access class MyType { public int i; public double d; public

More information

目 錄 壹 青 輔 會 結 案 附 件 貳 活 動 計 劃 書 參 執 行 內 容 一 教 學 內 容 二 與 當 地 教 師 教 學 交 流 三 服 務 執 行 進 度 肆 執 行 成 效 一 教 學 課 程 二 與 當 地 教 師 教 學 交 流 三 服 務 滿 意 度 調 查 伍 服 務 檢

目 錄 壹 青 輔 會 結 案 附 件 貳 活 動 計 劃 書 參 執 行 內 容 一 教 學 內 容 二 與 當 地 教 師 教 學 交 流 三 服 務 執 行 進 度 肆 執 行 成 效 一 教 學 課 程 二 與 當 地 教 師 教 學 交 流 三 服 務 滿 意 度 調 查 伍 服 務 檢 2 0 1 0 年 靜 宜 青 年 國 際 志 工 泰 北 服 務 成 果 報 告 指 導 單 位 : 行 政 院 青 年 輔 導 委 員 會 僑 務 委 員 會 主 辦 單 位 : 靜 宜 大 學 服 務 學 習 發 展 中 心 協 力 單 位 : 靜 宜 大 學 師 資 培 育 中 心 財 團 法 人 台 灣 明 愛 文 教 基 金 會 中 華 民 國 九 十 九 年 九 月 二 十 四 日 目

More information

Microsoft Word - template.doc

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

More information

C 1

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!=

More information

PowerPoint 演示文稿

PowerPoint 演示文稿 嵌入式系统 嵌入式 GUI 浙江大学计算机学院陈文智 chenwz@zju.edu.cn 提纲 嵌入式 GUI 概述 MiniGUI 概述 MiniGUI 架构 MiniGUI 使用 MiniGUI 编程 1 嵌入式 GUI 概述 GUI 的出现是 PC 应用的一个分水岭 嵌入式平台上的 GUI 具备轻小型 占用资源少 高性能 高可靠性和可配置性等嵌入式平台所特有的优点 一般针对特定的硬件设备或环境,

More information

Microsoft Word - ch04三校.doc

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

More information

Strings

Strings Inheritance Cheng-Chin Chiang Relationships among Classes A 類 別 使 用 B 類 別 學 生 使 用 手 機 傳 遞 訊 息 公 司 使 用 金 庫 儲 存 重 要 文 件 人 類 使 用 交 通 工 具 旅 行 A 類 別 中 有 B 類 別 汽 車 有 輪 子 三 角 形 有 三 個 頂 點 電 腦 內 有 中 央 處 理 單 元 A

More information

Important Notice SUNPLUS TECHNOLOGY CO. reserves the right to change this documentation without prior notice. Information provided by SUNPLUS TECHNOLO

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

More information

四川省普通高等学校

四川省普通高等学校 四 川 省 普 通 高 等 学 校 计 算 机 应 用 知 识 和 能 力 等 级 考 试 考 试 大 纲 (2013 年 试 行 版 ) 四 川 省 教 育 厅 计 算 机 等 级 考 试 中 心 2013 年 1 月 目 录 一 级 考 试 大 纲 1 二 级 考 试 大 纲 6 程 序 设 计 公 共 基 础 知 识 6 BASIC 语 言 程 序 设 计 (Visual Basic) 9

More information

SQL Server SQL Server SQL Mail Windows NT

SQL Server SQL Server SQL Mail Windows NT ... 3 11 SQL Server... 4 11.1... 7 11.2... 9 11.3... 11 11.4... 30 11.5 SQL Server... 30 11.6... 31 11.7... 32 12 SQL Mail... 33 12.1Windows NT... 33 12.2SQL Mail... 34 12.3SQL Mail... 34 12.4 Microsoft

More information

3.1 num = 3 ch = 'C' 2

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:

More information

Microsoft PowerPoint - directx01.ppt

Microsoft PowerPoint - directx01.ppt 電腦遊戲程式設計 DirectX 簡介 靜宜大學資訊管理學系蔡奇偉副教授 大綱 何謂 DirectX? DirecX 的模組 HAL 和 HEL COM 檢查安裝的 DirectX 版本 遊戲程式的骨架 DirectX 繪圖程式的骨架 版權所有 : 靜宜大學資管系蔡奇偉副教授 1 何謂 DirectX? Windows API 的架構無法滿足電腦遊戲與多媒體軟體的即時性需求, 因而微軟公司規劃出 DirectX

More information

Microsoft PowerPoint - string_kruse [兼容模式]

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.

More information

untitled

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

More information

untitled

untitled 1 Outline 料 類 說 Tang, Shih-Hsuan 2006/07/26 ~ 2006/09/02 六 PM 7:00 ~ 9:30 聯 ives.net@gmail.com www.csie.ntu.edu.tw/~r93057/aspnet134 度 C# 力 度 C# Web SQL 料 DataGrid DataList 參 ASP.NET 1.0 C# 例 ASP.NET 立

More information