实例分析一个简单的Win32程序

本文较为详细的分析了一个Win32程序的组成、结构、实现方法及运行原理,对于进行Windows程序设计有很好的借鉴参考价值。分享给大家供大家参考之用。具体分析如下:

一、Windows程序与普通C或C++程序的不同

学过C或C++等语言的人都知道,我们写的程序都一个入口,main函数,但是在Win32程序里,我们的入口函数又是什么呢?它是怎么样运行的,跟我们用C或C++写的控制台程序又有什么不同呢?

我们先说Win32程序跟我们控制台的程序的一个很重要的不同点,就是Win32程序是一个消息响应程序,例如点击了一个按钮,就会产生一个消息onButoon,然后会这个消息会进入我们程序所维护的一个消息队列,程序运行过程中不断地取出队列中的消息,并作出相应的处理。直到取出的是结束程序的消息。

二、了解MSG的结构和组成

首先,既然Windows的程序是基于消息触发的,那么Windows是如何定义一个消息的呢?下面是在MSDN上说明文档上的定义:

typedef struct tagMSG {   // msg
  HWND hwnd;
  UINT message;
  WPARAM wParam;
  LPARAM lParam;
  DWORD time;
  POINT pt;
} MSG;

下面我们来分析一下这个结构体:

HWND hwnd:hwnd是一个窗口的句柄,用来唯一标识一个窗口资源;至于什么是一个句柄,它有点类似对C或C++中的指针,句柄是资源的标识,根据资源的类型,又可将句柄细分成图标句柄(HICON),光标句柄(HCURSOR),窗口句柄(HWND),应用程序实例句柄(HINSTANCE)等等各种类型的句柄。操作系统给每一个窗口指定的一个唯一的标识号即窗口句柄。
 
UINT message:message是一个UINT(即C或C++中的unsign int)类型的变量,它用来标识一个具体的消息,如按键盘的消息。message用一个整数来表示,但是一个整数通常不好记忆,所以在VC++中就用微软给我们定义的一些宏来表示,如WM_KEYDOWN。

WPARAM wParam:整型参数,用来指示message的附加信息。

LPARAM lParam:跟wParam一样,是一个整型参数,用来指示message的附加信息。与wParam一样,多用来区分同一个消息的不同情况。

DWORD time:DWORD其实是C或C++中的unsigned long类型,time标识了一个消息产生时的时间。

POINT pt:POINT是一个结构体,表示现实世界里的一个点,里面有两个LONG类型的成员x和y,用来表示产生这个消息产时光标或鼠标的坐标。

由此可知一个MSG的变量所包含的信息是相当多和详细的。

三、了解WinMain函数

然后,像C或C++控制台程序的入口是main函数一样,Win32程序的入口也是main函数,不过它叫WinMain函数,它的定义如下:

int WINAPI WinMain(
 HINSTANCE hInstance,   // handle to current instance
 HINSTANCE hPrevInstance, // handle to previous instance
 LPSTR lpCmdLine,     // command line
 int nCmdShow       // show state
);

下面我们来分析一下这个函数:

HINSTANCE hInstance:hInstance是一个指向当前应用程序实例的一个句柄。实例就是一个运行中的程序。

HINSTANCE hPrevInstance:hPrevInstance是一个指向之前应用程序实例的一个句柄。

LPSTR lpCmdLine:lpCmdLine是一个指向字符串的指针,表示一个命令行参数,什么是命令行参数呢?就是我们C或C++中的main函数中的参数char *argv[]。

int nCmdShow:用来表示一个窗口的显示,表示它是要最大化显示,最小化显示,正常大小显示还是隐藏显示。

WinMain与main函数一样,是由操作系统进行调用的,所以这些参数也是由操作系统来赋值。

WINAPI是什么呢?其实它是一个宏,它代表的是__stdcall,表示的是参数传递的顺序,但是在VC中,参数的默认传递顺序为__cdecl。

四、创建一个窗口

那我们应该怎样设计一个窗口呢?要设计一个窗口,实际上是要设计一个窗口类,用来标记一个窗口的各种属性,在VC中已经有这样类(更正确地说是一个结构体)WNDCLASS。它的定义如下,后面的注释说明了它们的用处:

typedef struct _WNDCLASS {
 UINT style;       //用于指定类的类型,即窗口类的类型
 WNDPROC lpfnWndProc;  //指定一个窗口回调函数,是一个函数的指针
 int cbClsExtra;     //类的附加内存,通常数情况下为0
 int cbWndExtra;     //窗口附加内存,通常情况下为0
 HANDLE hInstance;  //当前实例句柄,用WinMain中的形参给它赋值
 HICON hIcon;     //图标句柄,用于指示应用程序所用的是什么图标,用函数LoadIcon进行赋值
 HCURSOR hCursor; //光标句柄,用于指示鼠标进入应用程序窗口区域时的显示,用函数LoadCursor进行赋值
 HBRUSH hbrBackground;   //用于指示程序的背景颜色,用函数(HBRUSH)GetStockObject赋值。
 LPCTSTR lpszMenuName;   //指定菜单的名字
 LPCTSTR lpszClassName;   //指定类的名字
 } WNDCLASS;

注:类型窗口的过程函数,也称回调函数,原理是,当应用程序收到给某一窗口的消息时,就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。对于一条消息,操作系统调用的是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。

在VC里或写Windows程序时,我们会经常用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。其实这些宏是一个UINT类型的一个数值,所以我们可以用|运算符来把多个特性结合在一起,用&~来去掉一个特性。

所以要创建一个窗口,首先我们在WinMain函数中创建一个WNDCLASS变量,并对WNDCLASS变量中的成员赋值之后,就可以注册这个窗口,可调用函数RegisterClass(&wndcls)来注册一个窗口,它需要一个WNDCLASS类型变量的地址。然后定义一个窗口的句柄HWND hwnd;然后调用函数CreateWindow,把返回值赋给hwnd。最后调用函数ShowWindow(hwnd,SW_SHOWNORMAL);UpdateWindow(hwnd);来显示窗口。

五、建立消息循环

现在窗口是创建出来了,但是之前我们就说过,Windows程序是基于消息触发和处理的程序,那么我们如何让程序让系统知道我们的操作呢?例如点击了一下鼠标,按了一下键盘,那就要建立我们的消息循环了,建立方法如下。
首先,我们定义一个MSG类型的变量,如MSG msg;
然后执行如下的循环:

while(GetMessage(&msg,NULL,0,0)) {
 TranslateMessage(&msg);
 DispatchMessage(&msg);
 }

解释:

GetMessage函数从我们的消息队列中取出消息,第一个参数为MSG变量,它出会自动帮我们填充msg中的成员变量;第二个参数是一个窗口句柄,NULL表示接受属于调用线程的所有消息;第三个参数指定消息的最小值;第四个参数指定消息的最大值。这两个参数若设为0,则获取所有在消息队列中的消息。它的返回值为BOOL型,只有在取出的消息为WM_QUIT时,返回FALSE;即除非关闭程序,否则将是一个死循环,一直对我们的操作进行处理。

TranslateMessage函数,用于翻译、处理和转换消息并把新消息投放到消息队列中,并且此过程不会影响原来的消息队列。

DispatechMessage函数,用于把收到的消息传到窗口回调函数进行分析和处理。即将消息传递给操作系统,让操作系统调用窗口回调函数,来对信息进行处理。

六、回调函数(窗口过程函数)

首先来看看它的定义:

LRESULT CALLBACK WinSunProc(
 HWND hwnd, // 窗口句柄
 UINT uMsg, // 消息标志符
 WPARAM wParam, // MSG第一附加参数
 LPARAM lParam // MSG第二附加参数
 );

CALLBACK是一个宏表示前面所说的_stdcall,LRESULT是一个long型参数。
调用时,把窗口类WNDCLASS的参数传递过来,里面有一个switch语句,用来判断要处理的消息类型,并作出相应的处理,注意switch语句里一定有一个default:return DefWindowProc(hwnd,uMsg,wParam,lParam);表示没有在case中出现的消息将按默认处理方式来处理,不然窗口运行会出错,连窗口都不能创建和显示出来。

通过上述分析还原了一个Win32应用程序的完整执行流程,相信本文所述对大家的Windows应用程序设计有一定的借鉴价值。

(0)

相关推荐

  • win32下进程间通信(共享内存)实例分析

    一.概述 很多情况下在Windows程序中,各个进程之间往往需要交换数据,进行数据通讯.WIN32 API提供了许多函数使我们能够方便高效的进行进程间的通讯,通过这些函数我们可以控制不同进程间的数据交换. 进程间通讯(即:同机通讯)和数据交换有多种方式:消息.共享内存.匿名(命名)管道.邮槽.Windows套接字等多种技术."共享内存"(shared memory)可以定义为对一个以上的进程是可见的内存或存在于多个进程的虚拟地址空间.例如:如果两个进程使用相同的DLL,只把DLL的代码

  • tc编译的dos程序和vc编译的win32控制台程序的异同

    它们有着本质的区别:1:一个是16的DOS程序,一个是标准的32的Windows应用程序.前者依然受着64KB内存分段约束,后者无忧无虑地使用着4GB内存地址空间.它们本来就是运行在不同的操作系统上的.别把他们混淆.DOS程序可以运行在windows上是因为有NTVDM.而你把VC生成的控制台程序放到DOS下只会得到!?This program cannot be run in DOS mode.2:如果你是用标准库编写的话,代码应该没多大差异的.但执行时调用库方式却差天共地.前者通过API模拟

  • 一个win32窗口创建示例

    复制代码 代码如下: /*一个简单的win32窗口调用*/#include<Windows.h>#include<tchar.h> //声明窗口函数LRESULT CALLBACK WindowProc(HWND hwnd,       UINT uMsg,       WPARAM wParam,       LPARAM lparam       );int WINAPI WinMain(     HINSTANCE hInstance,     HINSTANCE hPrev

  • WIN32程序获取父进程ID的方法

    学过windows程序设计的人都知道,windows中的进程是可以有父子关系的,拥有父子关系的进程,一旦父进程结束,子进程有会随之退出.但是如果进程之间没有父子关系,我们如何让子进程在父进程退出是也同时跟着退出呢?方法有很多,本文介绍其中的一种利用父进程ID的方案,实现的原理很简单:先获取父进程的ID,然后通过ID来获取父进程Handle,通过监视父进程的Handle来决定子进程是否退出.所以,这里的关键就是如何获取父进程的ID. 为了获取父进程ID,这里需要用到一个微软未公开的API: NTS

  • Win32 下病毒设计入门详细说明

    本文假定你对dos下的病毒和386PM有一定的了解. 1.感染任何一个病毒都需要有寄主,把病毒代码加入寄主程序中(伴侣病毒除外) 以下说明如何将病毒代码嵌入PE文件中,有关PE文件的结构请看以前的文章. PE文件的典型结构:MZ Header DOS STUB CODE PE HEADER OPTIONAL HEADER SECTION TABLE SECTION 1 SECTION 2 ... IMPORT TABLE EXPORT TABLE 和DOS的可执行文件类似,PE的代码映象分为几个

  • Win32应用程序(SDK)设计原理详解

    一般来说所谓的Win32应用程序开发,就是在C语言的层面上,直接使用Win32 API(Application Programming Interface:系统开放出来,给程序员使用的接口.)来开发Windows应用程序或者系统程序.虽然现在直接用Win32 API开发应用程序的人已经不多了,但是深入理解Windows系统程序设计原理,仍然是成为Windows开发高手的必经之路. 所谓的Win32,其实是一个API规范,与UNIX系统编程接口标准POSIX是相对应的.下面是进行直接的WIN32

  • win32使用openfilename浏览文件窗口示例

    代码如下: 复制代码 代码如下: OPENFILENAME ofn;WCHAR* szFile = new WCHAR[512];WCHAR* szFileTitle = new WCHAR[512];memset(&ofn, 0, sizeof(ofn));memset(szFile, 0, sizeof(WCHAR)*512);memset(szFileTitle, 0, sizeof(WCHAR)*512); ofn.lStructSize = sizeof(ofn);ofn.hwndOw

  • win32 api实现简单的消息窗口示例

    复制代码 代码如下: #include<windows.h> LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow){static TCHAR szAppName[]=TEXT("HelloWin");HWND hwnd;MSG msg;WNDCL

  • win32 api实现2048游戏示例

    自学的win32编程,写了一个win32 API版2048,自己摸索着写的,按上下左右箭头开始游戏 复制代码 代码如下: #include <windows.h>#include <stdlib.h>#include <stdio.h>#include <ctime>LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);//全局操作数组int arr[4][4] = { { 0, 0, 0, 0 }, {

  • 终于明白了tc编译的dos程序和vc编译的win32控制台程序的区别

    它们有着本质的区别:1:一个是16的DOS程序,一个是标准的32的Windows应用程序.前者依然受着64KB内存分段约束,后者无忧无虑地使用着4GB内存地址空间.它们本来就是运行在不同的操作系统上的.别把他们混淆.DOS程序可以运行在windows上是因为有NTVDM.而你把VC生成的控制台程序放到DOS下只会得到!?This program cannot be run in DOS mode.2:如果你是用标准库编写的话,代码应该没多大差异的.但执行时调用库方式却差天共地.前者通过API模拟

随机推荐