从WinMain开始

本文应一个初学Windows程序设计的朋友而作。


目录

  1. 抽象渗漏法则(摘自Joel)
  2. 针对Windows GUI编程的封装
  3. 只用API函数创建GUI程序
  4. 完整的示例代码

一、抽象渗漏法则

根据Joel的抽象渗漏法则所有重大的抽象机制在某种程度上都是有漏洞的。Joel举过一个例子:

C++字符串类型应该能让你假装字符串是个基本类型,它们尝试“字串很难处理”这个事实抽象掉,让它使用上象整型一样容易,几乎所有C++字串类型都会重载加号运算符,才能把字串连接写成s + "bar"。不过你知道吗?不管怎么努力,世上还是没有C++字串类型能让你写成 "foo"+"bar",因为C++里的字串常数一定是char *,绝对不会变成字串。这个抽象机制呈现一个程序语言本身不给补的漏洞。

当我想训练某人成为C++程序员时,最好能完全不教char *和指针运算,直接去学STL字符串。问题是总有一天他们会写出 "foo" + "bar" 这样的代码,然后看到怪事出现,于是我就得停下来教他们有关char *的事情。他们也可能会试着调用某个需要OUT LPTSTR参数的Windows API,于是又得把char *、指针、Unicode、wchar_t以及tchar.h搞懂,才会知道如何调用。而这些全都是漏洞。

二、针对Windows GUI编程的封装
而针对Windows的GUI编程,有很多封装,如VCL、MFC、WTL等,凡此种种,都把WinMain、CreateWindow和 RegisterClassEx这些API与程序员隔离开来,对一个一开始就只接触这些类库的初学者来说,根本不知道原来一个Windows程序的入口点 其实是WinMain(事实上,一个Win32 EXE的入口点也并不是WinMain,而是编程语言的Runtime库,不过,这里把它抽象掉似乎更有益于理解)。

用API来搭建一个GUI程序是比较枯燥的,这种对于Windows GUI程序的枯燥搭建进行的抽象封装,它的所谓“某种程度上的漏洞”,也许就是使程序员根本不知道每个窗口都有一个窗口类(不是指OO语言里的Class),而每一个窗口类都有一个回调函数(Callback)来对不同的窗口消息进行不同的响应。

做为现在才接触Windows GUI编程的初学者,几乎都不了解一个Windows GUI程序是从WinMain开始的(前面说过,从WinMain开始也只是一个抽象而已,真实的情况并不是这样),那么如何仅仅使用Windows的API函数来创建一个GUI程序呢?

三、只用API搭建Windows GUI程序
1、WinMain()函数

首先,必须要声明一个WinMain()函数(为了简明起见,这里先不讨论_tWinMain这个宏,也不考虑Unicode的问题),它的原型在Windows.h中定义:

  1. int WINAPI WinMain( 
  2.     HINSTANCE hInstance,       //程序当前实例的句柄,以后随时可以用GetModuleHandle(0)来获得 
  3.     HINSTANCE hPrevInstance,   //这个参数在Win32环境下总是0,已经废弃不用了 
  4.     char * lpCmdLine,          //指向以/0结尾的命令行,不包括EXE本身的文件名, 
  5.                                //以后随时可以用GetCommandLine()来获取完整的命令行 
  6.     int nCmdShow               //指明应该以什么方式显示主窗口 
  7. ); 
int WINAPI WinMain(
    HINSTANCE hInstance,       //程序当前实例的句柄,以后随时可以用GetModuleHandle(0)来获得
    HINSTANCE hPrevInstance,   //这个参数在Win32环境下总是0,已经废弃不用了
    char * lpCmdLine,          //指向以/0结尾的命令行,不包括EXE本身的文件名,
                               //以后随时可以用GetCommandLine()来获取完整的命令行
    int nCmdShow               //指明应该以什么方式显示主窗口
);


声明,并且实现这个函数,让Linker程序可以找到它,让编程语言的运行时刻库在完成一些必要的初始化工作后,能够正确地调用它。所以,认为它就是程序的入口点,也是一种简单的“抽象法则”。

在这个入口点函数中,需要按顺序做下面几件事(如果是基于事先设计并存放在资源里的对话框的程序,稍有不同,以后再说):

    • 用RegisterClassEx函数登记一个独一无二的Class
    • 用CreateWindowEx函数创建一个主窗口
    • 进入一个”消息循环“,直到收到WM_QUIT消息
    • 从WinMain函数返回

      基本上所有的流程都如出一辙,所以完全可以设计出一个“Template模式”出来重用,让以后的程序直接从某个抽象基类继承,实现基类所需的虚方法就可以了,不过为了不偏离重心,还是用C语言的方式写出来:

      1. int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) { 
      2.     if (registerMyClass() && createMyWindow(cmdShow)) { 
      3.         return messageLoop(); 
      4.     } else
      5.         std::ostringstream msg; 
      6.         msg << "创建主窗口失败,错误代码:" << GetLastError(); 
      7.         MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP); 
      8.         return 0; 
      9.     } 
      int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) {
          if (registerMyClass() && createMyWindow(cmdShow)) {
              return messageLoop();
          } else {
              std::ostringstream msg;
              msg << "创建主窗口失败,错误代码:" << GetLastError();
              MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);
              return 0;
          }
      }

      如此简单,WinMain这个函数只有这么短,分别调用三个自定义函数就OK了。

             
      2、窗口消息回调函数

      简单地说,回调(Callback)函数就是一个按规定原型实现的一个函数,当别人来调用。比如说,每个窗口都有一个窗口类(用RegisterClassEx登记的Class,或者系统缺省已实现的Class),每个窗口类有一个回调函数,当窗口收到WIndows消息的时候,就会去调用这个回调函数,而这个回调函数的代码是程序员自己写的,用来根据实际情况处理不同的窗口消息。

      1. LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) { 
      2.     switch (msg) { 
      3.     case WM_DESTROY: 
      4.         PostQuitMessage(0); //如果是“窗口销毁”事件,则应该在消息队列中投递 
      5.         break;              //一个WM_QUIT消息,使GetMessage()返回FALSE 
      6.     default
      7.         return DefWindowProc(wnd, msg, wParam, lParam); 
      8.     } 
      9.     return 0; 
      LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
          switch (msg) {
          case WM_DESTROY:
              PostQuitMessage(0); //如果是“窗口销毁”事件,则应该在消息队列中投递
              break;              //一个WM_QUIT消息,使GetMessage()返回FALSE
          default:
              return DefWindowProc(wnd, msg, wParam, lParam);
          }
          return 0;
      }


             

      3、登记窗口类

      在创建主窗口之前,一定要先用RegisterClassEx这个API函数登记一个类,类名必须是独一无二的,所以一般都用GUID字串来做类名。

      1. bool registerMyClass() { 
      2.     WNDCLASSEX  wce = {0}; 
      3.     wce.cbSize          = sizeof(wce); 
      4.     wce.style           = CS_VREDRAW | CS_HREDRAW; 
      5.     wce.lpfnWndProc     = &onMainWndMessage;  //指明回调函数 
      6.     wce.hInstance       = GetModuleHandle(0); 
      7.     wce.hIcon           = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO)); 
      8.     wce.hCursor         = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW)); 
      9.     wce.hbrBackground   = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1); 
      10.     wce.lpszClassName   = CLASS_NAME; //独一无二的类名 
      11.     wce.hIconSm         = wce.hIcon; 
      12.     return 0!=RegisterClassEx(&wce); 
      bool registerMyClass() {
          WNDCLASSEX  wce = {0};
          wce.cbSize          = sizeof(wce);
          wce.style           = CS_VREDRAW | CS_HREDRAW;
          wce.lpfnWndProc     = &onMainWndMessage;  //指明回调函数
          wce.hInstance       = GetModuleHandle(0);
          wce.hIcon           = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));
          wce.hCursor         = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
          wce.hbrBackground   = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);
          wce.lpszClassName   = CLASS_NAME; //独一无二的类名
          wce.hIconSm         = wce.hIcon;
          return 0!=RegisterClassEx(&wce);
      }


          

      4、创建主窗口

      (略,直接看完整代码

      5、消息循环

      消息循环很简单,仅当GetMessage这个API函数返回FALSE时,才退出循环。而GetMessage()仅当处理到消息队列中的WM_QUIT消息时才会返回FALSE。

      1. int messageLoop() { 
      2.     MSG msg; 
      3.     while (GetMessage(&msg, 0, 0, 0)) { 
      4.         TranslateMessage(&msg); 
      5.         DispatchMessage(&msg); 
      6.     } 
      7.     return static_cast<int>(msg.wParam); 
      int messageLoop() {
          MSG msg;
          while (GetMessage(&msg, 0, 0, 0)) {
              TranslateMessage(&msg);
              DispatchMessage(&msg);
          }
          return static_cast<int>(msg.wParam);
      }


          

      四、完整的示例代码

      1. #include <sstream> 
      2. #include <Windows.h> 
      3.  
      4. //独一无二的类名,一般用GUID字串,以免与其他程序的类名重复 
      5. static const char * CLASS_NAME = "{198CEAB2-AD78-4ed3-B099-247639080CB0}"
      6.  
      7. /************************************************************************
      8.     回调函数,当主窗口收到任何Windows消息时被调用
      9. ************************************************************************/ 
      10. LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) { 
      11.     switch (msg) { 
      12.     case WM_DESTROY: 
      13.         PostQuitMessage(0); //如果是“窗口销毁”事件,则应该在消息队列中投递 
      14.         break;              //一个WM_QUIT消息,使GetMessage()返回FALSE 
      15.     default
      16.         return DefWindowProc(wnd, msg, wParam, lParam); 
      17.     } 
      18.     return 0; 
      19.  
      20. /************************************************************************
      21.     登记自己的窗口类
      22. ************************************************************************/ 
      23. bool registerMyClass() { 
      24.     WNDCLASSEX  wce = {0}; 
      25.     wce.cbSize          = sizeof(wce); 
      26.     wce.style           = CS_VREDRAW | CS_HREDRAW; 
      27.     wce.lpfnWndProc     = &onMainWndMessage;  //指明回调函数 
      28.     wce.hInstance       = GetModuleHandle(0); 
      29.     wce.hIcon           = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO)); 
      30.     wce.hCursor         = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW)); 
      31.     wce.hbrBackground   = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1); 
      32.     wce.lpszClassName   = CLASS_NAME; //独一无二的类名 
      33.     wce.hIconSm         = wce.hIcon; 
      34.     return 0!=RegisterClassEx(&wce); 
      35.  
      36. /************************************************************************
      37.     创建并显示主窗口
      38. ************************************************************************/ 
      39. bool createMyWindow(int cmdShow) { 
      40.     HWND mainWnd = CreateWindowEx(0, CLASS_NAME, "Demo", WS_OVERLAPPEDWINDOW, 
      41.         CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
      42.         0, 0, GetModuleHandle(0), 0); 
      43.     if (0!=mainWnd) { 
      44.         ShowWindow(mainWnd, cmdShow); 
      45.         UpdateWindow(mainWnd); 
      46.         return true
      47.     } else
      48.         return false
      49.     } 
      50.  
      51. /************************************************************************
      52.     消息循环
      53. ************************************************************************/ 
      54. int messageLoop() { 
      55.     MSG msg; 
      56.     while (GetMessage(&msg, 0, 0, 0)) { 
      57.         TranslateMessage(&msg); 
      58.         DispatchMessage(&msg); 
      59.     } 
      60.     return static_cast<int>(msg.wParam); 
      61.  
      62. /************************************************************************
      63.     WinMain,程序入口
      64. ************************************************************************/ 
      65. int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) { 
      66.     if (registerMyClass() && createMyWindow(cmdShow)) { 
      67.         return messageLoop(); 
      68.     } else
      69.         std::ostringstream msg; 
      70.         msg << "创建主窗口失败,错误代码:" << GetLastError(); 
      71.         MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP); 
      72.         return 0; 
      73.     } 
      74. }  
      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值