如何快速入门Windows编程

来源:互联网 发布:传奇 知乎 编辑:程序博客网 时间:2024/05/01 09:55
摘要:不少朋友向我征询学习Windows界面编程的方法,每一次,我总毫不犹豫的回答:先去学Windows SDK吧。——是的,如果你要以最快的速度学习Windows编程,那么,你需要从Windows SDK开始。假如你选择MFC或者Delphi/C++ Builder等等,虽然看似你快人一步,但是你仍然困惑着,总有一天,你还是得回头一点一滴了解Windows SDK,而此时,你已经走了不少冤枉路了。。。这其中的道理是很深刻的,本文作者以Joel的《抽象渗漏法则》解释这种现象,深得我心。

点击这里获得更多Windows界面开发相关的资料。要了解开源的界面库WinxGui,请访问WinxGui的官方网站。

出处:从WinMain开始
作者:于无声处

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


目录

  1. 抽象渗漏法则(摘自Joel)
  2. 针对Windows GUI编程的封装
  3. 只用API函数创建GUI程序
    • WinMain函数
    • 登记自己的窗口类
    • 创建主窗口
    • 消息循环
  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中定义:

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语言的方式写出来:如此简单,WinMain这个函数只有这么短,分别调用三个自定义函数就OK了。

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

 

2、窗口消息回调函数

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

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字串来做类名。

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。 

int messageLoop() {
    MSG msg;
    
while (GetMessage(&msg, 000)) {
        TranslateMessage(
&msg);
        DispatchMessage(
&msg);
    }
    
return static_cast<int>(msg.wParam);
}

 

四、完整的示例代码
#include <sstream>
#include 
<windows.h>

//独一无二的类名,一般用GUID字串,以免与其他程序的类名重复
static const char * CLASS_NAME = "{198CEAB2-AD78-4ed3-B099-247639080CB0}";

/************************************************************************
    回调函数,当主窗口收到任何Windows消息时被调用
***********************************************************************
*/
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;
}

/************************************************************************
    登记自己的窗口类
***********************************************************************
*/
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);
}

/************************************************************************
    创建并显示主窗口
***********************************************************************
*/
bool createMyWindow(int cmdShow) {
    HWND mainWnd 
= CreateWindowEx(0, CLASS_NAME, "Demo", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        
00, GetModuleHandle(0), 0);
    
if (0!=mainWnd) {
        ShowWindow(mainWnd, cmdShow);
        UpdateWindow(mainWnd);
        
return true;
    } 
else {
        
return false;
    }
}

/************************************************************************
    消息循环
***********************************************************************
*/
int messageLoop() {
    MSG msg;
    
while (GetMessage(&msg, 000)) {
        TranslateMessage(
&msg);
        DispatchMessage(
&msg);
    }
    
return static_cast<int>(msg.wParam);
}

/************************************************************************
    WinMain,程序入口
***********************************************************************
*/
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;
    }
}



原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 皮鞋划了个口子怎么办 光面鞋划了一道怎么办 绿萝叶子太密怎么办 绿萝中间太密了怎么办 苹果mac密码忘了怎么办 mac锁屏密码忘了怎么办 剑三头发飞起来怎么办 苹果6p开不了机怎么办 苹果6p黑屏了怎么办 肚子胀反胃想吐怎么办 脸上干皮特别多怎么办 月光足还不了款怎么办 孩子不爱和小朋友玩怎么办 不知道怀孕抽烟了怎么办 校园欺凌来了该怎么办 拉的屎是绿色的怎么办 生完孩子痔疮痛怎么办 生过孩子脱肛该怎么办 生完宝宝有痔疮怎么办 毎次大便都脱肛怎么办 怀孕快生了便秘怎么办 35周孕晚期便秘怎么办 怀孕了老公出轨了怎么办 婆婆跟老公睡了怎么办 婆婆和老公互黏怎么办 15岁就掉头发怎么办 24岁经常掉头发怎么办 舍友打游戏太吵怎么办 家里的地砖想换怎么办 墙上的瓷砖掉了怎么办 瓷砖掉了一块瓷怎么办 白色裙子太透了怎么办 剑三石头插错了怎么办 四六级证书丢了怎么办 当月发票冲红了怎么办 当月发票红冲了怎么办 作废的发票扔了怎么办 发票公章盖错了怎么办 发票章盖的模糊怎么办 下的电影没字幕怎么办 荣耀8下载东西慢怎么办