一、抽象渗漏法则

根据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. );

声明,并且实现这个函数,让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. }
  10. }

如此简单,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;
  10. }

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);
  13. }

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

http://blog.csdn.net/seawave/article/details/1338879

从WinMain开始的更多相关文章

  1. Windows程序内部运行机制 转自http://www.cnblogs.com/zhili/p/WinMain.html

    一.引言 要想熟练掌握Windows应用程序的开发,首先需要理解Windows平台下程序运行的内部机制,然而在.NET平台下,创建一个Windows桌面程序,只需要简单地选择Windows窗体应用程序 ...

  2. WinMain与wWinMain,win32的字符集问题

    刚接触win32开发的会发现,用vs新建一个win32项目默认代码里面的入口函数并不是WinMain而是_tWinMain或者wWinMain,这些都有什么区别呢? WinMain对应的是多字节字符集 ...

  3. WinMain函数详解(转载)

    略加增添与修改! 工具:VC++6.0       系统:win7 64位 在Windows应用程序中,我们可以认为 WinMain() 函数是程序的入口,WinMain()的原型如下: int WI ...

  4. WinMain初始化详细过程以及消息循环

    主要内容:详细介绍WinMain函数的初始化过程以及消息循环 1.窗口类定义 通过给窗口类数据结构WNDCLASS赋值完成, 该数据结构中包含窗口类的各种属性 <1>LoadIcon 作用 ...

  5. <MFC_1>深入剖析MFC的WinMain和消息机制

    一.开篇引论 熟悉Win32开发的朋友,应该非常了解它的基本组成和流程 1. WinMain:书写窗口类(WNDCLASS) -> 注册窗口类 -> 创建窗口 -> 显示窗口和更新窗 ...

  6. 主函数 main WinMain _tmain _tWinMain 的区别

    main是C/C++的标准入口函数名 WinMain是windows API窗体程序的入口函数.(int WINAPI WinMain()) 中 WINAPI是__stdcall宏,在windef.h ...

  7. 1 对WinMain的理解

    就像C语言的main是它的程序路口一样,windows的程序入口是WinMain,WinMain的定义可以查看winbase.h文件. Hello Windows(c语言中的Hello world!) ...

  8. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

  9. 'WinMain' : function cannot be overloaded

    Create a MFC Application (UNICODE), paste following code in one of your cpp file. int APIENTRY WinMa ...

  10. MFC框架中消失的WinMain()

    学过一段时间的MFC之后,很多人大概都有一个疑问:在MFC中,WinMain()哪去了?因为任何一个使用过Win32 SDK编程的人都知道,WinMain()函数是Win32程序开始的入口点,可是在M ...

随机推荐

  1. android通过服务实现消息推送

    这里运用到的andorid知识模块主要有Notification和Service,和一个android-async-http-master开源框架 android项目中,有时会有这样一种需求:客户每隔 ...

  2. [置顶] JNI之java传递数据给c语言

    1.首先创建 DataProvider类: package com.pl.ndkpassdata; public class DataProvider { static{ System.loadLib ...

  3. QSettings保存程序设置

    今天看了一些QSettings的简单用法,可以用来保存程序的设置,使得程序每次启动都可以显示上次关闭时的状态.我这里实现了一个简单的文本编辑窗口,可以设置文本的字体,字体的颜色和背景色.每次关闭程序都 ...

  4. c++11 新特性之 autokeyword

    C++11是对眼下C++语言的扩展和修正.C++11包含大量的新特性:包含lambda表达式,类型推导keywordauto.decltype,和模板的大量改进. g++编译c++11命令加上 -st ...

  5. Flex中怎么给表格中的滚动栏定位

    1.问题背景 假设表格中的字段过多,会出现滚动栏,在将滚动栏滚到一定的位置时,又一次刷新表格.滚动栏会回到原处,原来查看的字段还得继续滚动,才干查看到. 2.实现实例 <? xml versio ...

  6. android代码控制seekbar的样式

    package com.zte; import android.app.Activity; import android.graphics.Color; import android.graphics ...

  7. QT显示机制(7篇相关文章)

    了解QT显示机制,最重要的就是要了解QT是如何管理窗体的显示区域的,这里有个重要的类:QRegion, 在QT中可以通过QRegion定义一个窗体的显示区域,也可以通过QRegion定义窗体的可修改区 ...

  8. premake Ubuntu下的安装

    premake是个跨平台的编译工具,先看看在Ubuntu下怎么安装. 首先下载,在/usr目录下: sudo wget -O premake-4.4-beta4-linux.tar.gz http:/ ...

  9. string的不可变性

    1.不可变性 代码如下: static void Main(string[] args){string str1 = "a";string str2 = str1;str2 = & ...

  10. JDK 环境变量配置(Mac)

    Mac JDK 安装过后 修改 ~/. bash_profile 配置环境变量 修改内容: JAVA_HOME=$(/usr/libexec/java_home) export JAVA_HOME P ...