窗口类Win32 application (2) 看局域网聊天开源软件IPMsg的Win32部分
废话就不多说了,开始。。。
简单分析:
IP Messenger : http://ipmsg.org (p.s.:该网站的右上角有英文版网页链接)
岛国H.Shirouzu写的跨平台局域网通信开源软件,基于TCP/IP,不需要服务器。
海内大家用的Feiq(飞秋)就是作者基于IPMsg写的,现在更新到r3.42。
初学tcp/ip的盆友不妨把source code抓下来读一读,ipmsg自己定义了一套应用层协议,消息的收发基于udp协议,文件的收发基于tcp。
我记得学校里最初学tcp/ip时,教的是c/s模型的socket编程,先写个server在那儿始终while(1),这边再启动几个client,如此,一个简单的局域网通信软件就写好了。
ipmsg source code里面有对ipmsg protocol作说明,基于日文有其它程序员翻译成了英文的prot-eng.txt。 如,启动或退出时通过向(255.255.255.255)广播的方法把消息发出去。
话说此文不会针对IPMsg协议来写,主要还是Win32流程的货色。上一稿《Win32 application (1) Begin》,讲到vs创建win32 app后,就没有下文了,今天接着写我的。关于Win32应用的流程分析蜘蛛网上有很多高质量的网文,请大家自行使用搜索引擎。
关于ipmsg的source code的话,有几点需要说明:
1.作者貌似是用vs2005写的,我把代码资源些都放到vs2012面,直接是编不过的,至于怎么处理,我也不知道,有谁知道的话不妨留言分享一下,thanks。
2.Source code里面的很多注释都是日文写的,我用VS2012没有乱码涌现,如果你用Source Insight或其它,可能需要再配置一下字体什么的,让它支撑日文表现。
3.external:
使用了libpng & zlib.
4. src:
install是面的source code及resource file都是用于安装ipmsg时的GUI表现及逻辑处置;
uninst则反之;
TLib则是ipmsg软件的主要基类:
用VS查看类图:
1)TApp
2) TWin
按照实现Win32 applicaiton的流程来看ipmsg中Win32部份的流程:
1. WinMain入口
2. Registers the window class. --> 注册需要挂一个callback function(To processes messages for the main window.)
3. Application initialization: saves instance handle and creates main window
4. Main message loop
ipmsg.cpp最后定义了WinMain:
int WINAPI WinMain(HINSTANCE hI, HINSTANCE, LPSTR cmdLine, int nCmdShow) { if (IsWin95()) { MessageBox(0, "Please use old version (v2.06 or earlier)", "Win95/98/Me is not supported", MB_OK); ::ExitProcess(0xffffffff); return 0; } TMsgApp app(hI, cmdLine, nCmdShow); return app.Run(); }
定义了一个TMsgApp类的对象,从前面的类图知道TMsgApp是从TApp继承过去的。TMsgApp没有重新定义Run(),所以多态地直接调用父类的Run():
int TApp::Run(void) { MSG msg; InitApp(); InitWindow(); while (::GetMessage(&msg, NULL, 0, 0)) { if (PreProcMsg(&msg)) continue; ::TranslateMessage(&msg); ::DispatchMessage(&msg); } return (int)msg.wParam; }
当初可以看出,Run()和我们用VS直接主动生成的WinMain函数:
1) InitApp注册窗口类;
2) InitWindow实例化窗口;
3) 最后消息处置循环。
1) InitApp注册窗口类,并挂载WinProc处置Window Message
BOOL TApp::InitApp(void) // reference kwc { WNDCLASSW wc; memset(&wc, 0, sizeof(wc)); wc.style = (CS_BYTEALIGNCLIENT | CS_BYTEALIGNWINDOW | CS_DBLCLKS); wc.lpfnWndProc = WinProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hI; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = NULL; wc.lpszMenuName = NULL; wc.lpszClassName = (LPCWSTR)defaultClassV; if (::FindWindowV(defaultClassV, NULL) == NULL) { if (::RegisterClassV(&wc) == 0) return FALSE; } return TRUE; }
TApp类实现的WinProc():
LRESULT CALLBACK TApp::WinProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { TApp *app = TApp::GetApp(); TWin *win = app->SearchWnd(hWnd); if (win) return win->WinProc(uMsg, wParam, lParam); if ((win = app->preWnd)) { app->preWnd = NULL; app->AddWinByWnd(win, hWnd); return win->WinProc(uMsg, wParam, lParam); } return ::DefWindowProc(hWnd, uMsg, wParam, lParam); }
可以看出,TApp作为父类,通过传递进来的窗口HANDLE找到对应的Window,并调用Window的消息处置函数。
这里会调用到TWin类的WinProc():
LRESULT TWin::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { BOOL done = FALSE; LRESULT result = 0; switch(uMsg) { case WM_CREATE: GetWindowRect(&orgRect); done = EvCreate(lParam); break; case WM_CLOSE: done = EvClose(); break; case WM_COMMAND: done = EvCommand(HIWORD(wParam), LOWORD(wParam), lParam); break; case WM_SYSCOMMAND: done = EvSysCommand(wParam, MAKEPOINTS(lParam)); break; case WM_TIMER: done = EvTimer(wParam, (TIMERPROC)lParam); break; case WM_DESTROY: done = EvDestroy(); break; /* other case */ }
TWin作为主窗口类,ipmsg从TWin继承了很多的子类,TMainWin是其中一个。
2) InitWindow实例化窗口TApp将InitWindow()声明为virtual,需要子类来实现,下面是
TMsgApp类实现的InitWindow():
void TMsgApp::InitWindow(void) { HWND hWnd; char class_name[MAX_PATH_U8] = IPMSG_CLASS, *tok, *msg, *p; char *class_ptr = NULL; ULONG nicAddr = 0; int port_no = atoi(cmdLine); BOOL show_history = FALSE; enum Stat { ST_NORMAL, ST_TASKBARUI_MSG, ST_EXIT, ST_ERR } status = ST_NORMAL; int taskbar_msg = 0; int taskbar_cmd = 0; /* ... ... something ... ... */ HANDLE hMutex = ::CreateMutex(NULL, FALSE, class_name); ::WaitForSingleObject(hMutex, INFINITE); if ((hWnd = FindWindowU8(class_name)) || !TRegisterClassU8(class_name, CS_DBLCLKS, ::LoadIcon(hI, (LPCSTR) IPMSG_ICON), ::LoadCursor(NULL, IDC_ARROW))) { if (hWnd) ::SetForegroundWindow(hWnd); ::ExitProcess(0xffffffff); return; } mainWnd = new TMainWin(nicAddr, port_no); mainWnd->Create(class_name); ::ReleaseMutex(hMutex); ::CloseHandle(hMutex); if (show_history) mainWnd->SendMessage(WM_COMMAND, MENU_HELP_HISTORY, 0); }
从前面可以看到mainWnd被实例化为一个TMainWin类的对象,并调用TMainWin类的create函数来创建main window;继续跟下去会发明它最终调用到了TWin的CreateV():
BOOL TWin::CreateV(const void *className, const void *title, DWORD style, DWORD exStyle, HMENU hMenu) { if (className == NULL) { className = TApp::GetApp()->GetDefaultClassV(); } TApp::GetApp()->AddWin(this); if ((hWnd = ::CreateWindowExV(exStyle, className, title, style, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, parent ? parent->hWnd : NULL, hMenu, TApp::GetInstance(), NULL)) == NULL) return TApp::GetApp()->DelWin(this), FALSE; else return TRUE; }
reateWindowExV()被不是Windows SDK提供的API,而只是TLib里定义的一个函数指针:
HWND (WINAPI *CreateWindowExV)(DWORD exStyle, const void *className, const void *title, DWORD style, int x, int y, int nw, int nh, HWND hParent, HMENU hMenu, HINSTANCE hI, void *param);
如果是在Windows系统中跑,它最终会指向CreateWindowExW这个Windows API。
至此,实例化窗口顺序调用就完成了,不过并没有看到主窗口是怎么设计的,它只不过创建了一个窗口,仅此而已,并且,没有看到Show&Update。当然这是在WinProc里收到WM_CREATE里面来做的,主窗口的EvCreate:
LRESULT TWin::WinProc(UINT uMsg, WPARAM wParam, LPARAM lParam) { BOOL done = FALSE; LRESULT result = 0; switch(uMsg) { case WM_CREATE: GetWindowRect(&orgRect); done = EvCreate(lParam); break; /* other case */ } }
从TWin类继承来的TMainWin重新定义了WinProc函数:
BOOL TMainWin::EvCreate(LPARAM lParam) { hMainWnd = hWnd; mainWin = this; if (IsWinVista() && TIsUserAnAdmin() && TIsEnableUAC()) { TChangeWindowMessageFilter(WM_DROPFILES, 1); TChangeWindowMessageFilter(WM_COPYDATA, 1); TChangeWindowMessageFilter(WM_COPYGLOBALDATA, 1); TChangeWindowMessageFilter(WM_CLOSE, 1); } if (!msgMng->GetStatus()) return TRUE; if (cfg->TaskbarUI) { Show(SW_MINIMIZE); } else { Show(SW_HIDE); } while (!TaskTray(NIM_ADD, hMainIcon, IP_MSG)) { Sleep(1000); // for logon script } TaskBarCreateMsg = ::RegisterWindowMessage("TaskbarCreated"); TaskBarButtonMsg = ::RegisterWindowMessage("TaskbarButtonCreated"); TaskBarNotifyMsg = ::RegisterWindowMessage(IP_MSG); SetIcon(cfg->AbsenceCheck ? hRevIcon : hMainIcon); SetCaption(); if (!SetupCryptAPI(cfg, msgMng)) MessageBoxU8("CryptoAPI can't be used. Setup New version IE"); msgMng->AsyncSelectRegister(hWnd); SetHotKey(cfg); if (msgMng->GetStatus()) { EntryHost(); } if (IsWin7()) { // for TaskbarUI ::CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); if (cfg->TaskbarUI) { CreateJumpList(className); } else { // DeleteJumpList(); } } ::SetTimer(hWnd, IPMSG_CLEANUP_TIMER, 60000, NULL); // 1min return TRUE; }
Show Window: Show(SW_HIDE); 调用Windows API。IPMsg启动后会主动最小化,所以你不会看到有一个主窗口界面涌现,不过它的确已创建了一个main window,当然实验点击右下角的图标来打开IPMsg时,会触发BUTTON事件,WinProc会去处置WM_LBUTTONUP消息, 用来Send Msg的窗口就会打开。
void TWin::Show(int mode) { ::ShowWindow(hWnd, mode); ::UpdateWindow(hWnd); }
3) 最后消息处置循环
while (::GetMessage(&msg, NULL, 0, 0)) { if (PreProcMsg(&msg)) continue; ::TranslateMessage(&msg); ::DispatchMessage(&msg); }
其中,PreProcMsg()是为了将msg传递给对应的窗口去处置,自己的娃自己管好,你不管,就要交给父辈来管。最后最后,就是编写各种消息处置函数了,当然Win32主流程当中也有不少细节在这里没有说,大家不妨自己抓份source code来读一读。上一个post中提到Win32 application开发,各种蛋疼,其中一个原因就是Win32没有像WinForm或WPF那样可以直接拖动控件来布局应用的图形界面,其实Win32是有的,Windows SDK已提供了一些基本的控件给Win32开发人员。
用这些控件前需要调用 InitCommonControls()或者InitCommonControlsEx()来初始化一下,这个函数在Commctrl.h里声明的。
这是IPMsg用来发消息的Dialog窗口,当然啰,要做出Tencent QQ那样的漂亮界面,还需要自己去定制一些界面库来实现。
文章结束给大家分享下程序员的一些笑话语录:
一程序员告老还乡,想安度晚年,于是决定在书法上有所造诣。省略数字……,准备好文房4宝,挥起毛笔在白纸上郑重的写下:Hello World
---------------------------------
原创文章 By
窗口和类
---------------------------------