- 结构WNDCLASS包含一个窗口类的全部信息,也是Windows编程中使用的基本数据结构之一,应用程序通过定义一个窗口类确定窗口的属性
基本方法有:
typedef struct _WNDCLASS {UINT style;// 窗口类型WNDPROC lpfnWndProc;//窗口处理函数int cbClsExtra;//窗口扩展int cbWndExtra;//窗口实例扩展HINSTANCE hInstance;//实例句柄HICON hIcon;//窗口的最小化图标HCURSOR hCursor;//窗口鼠标光标HBRUSH hbrBackground;//窗口背景色LPCTSTR lpszMenuName;//窗口菜单LPCTSTR lpszClassName;// 窗口类名
} WNDCLASS, *LPWNDCLASS;
- int WINAPI WinMain:正如在C程序中的进入点是函数main一样,Windows程序的进入点是WinMain
int WINAPI WinMain(HINSTANCE hInstance,//当前实例句柄HINSTANCE hPrevInstance,//先前实例句柄LPSTR lpCmdLine,//命令行参数int nCmdShow//显示状态(最大化、最小化、隐藏)
);
- WNDCLASS中的回调函数是窗体的消息处理函数:LRESULT CALLBACK
LRESULT//表示会返回多种long型值
CALLBACK//只是为了识别这是一个回调函数的空宏,回调函数,这里也叫窗口函数,是给windows系统调用的
一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM) 组成
LRESULT CALLBACK WndProc(HWND hwnd, //窗口句柄,整型值UINT msg, //unsigned int,消息名称WPARAM wParam,//typedef UINT WPARAM;control identifierLPARAM lParam//typedef LONG LPARAM;notification messages
);
- cbClsExtra和cbWndExtra主要区别:
- 两者都是附加额外空间;
- cbClsExtra是对类的(这里所说的类不是C++中的类,而是WNDCLASS结构的变量用RegisterClass注册后,在系统中记录的一组信息,是窗口的一个模板),用该类生成的所有窗口共享该附加空间。类似于C++类中的static变量。
- cbWndExtra是对窗口的,每实例化一个窗口都有这么一个附加空间。类似于C++类中的成员变量
- LoadIcon:用于加载窗口图标;该函数从与 hInstance 模块相关联的可执行文件中装入lpIconName指定的图标资源,仅当图标资源还没有被装入时该函数才执行装入操作,否则只获取装入的资源句柄。
比如,在对窗口类初始化时,我们可以如下使用:
WNDCLASS wndclass;
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
- 上面代码中,我们将标题栏上的图标定义为 IDI_APPLICATION.
注意,上面函数中第一个参数为NULL,此时使用的是系统预定义的图标,其它预定义选项可参考 MSDN;也可以使用自定义的图标资源,即使用 MAKEINTRESOURCE 宏对一个十六位数的资源标识符(高 8 位为0,低 8位为图标资源ID)进行转换
LoadIcon(HINSTANCE hInstance,LPCSTR lpIconName);
HCURSOR LoadCursor(HINSTANCE hlnstance,//标识一个模块事例,它的可执行文件包含要载入的光标LPCTSTR lpCursorName//指向以NULL结束的字符串的指针,该字符串存有等待载入的光标资源名);
- 要使用Win32预定义的一个光标,应用程序必须把hlnstance参数设为NULL,并把IpCursorName设为如下值之一:
IDC_APPSTARTING 标准的箭头和小沙漏
IDC_ARROW 标准的箭头
IDC_CROSS 十字光标
IDC_HAND Windows 98/Me, Windows 2000/XP: Hand
IDC_HELP 标准的箭头和问号
IDC_IBEAM 工字光标
IDC_ICON Obsolete for applications marked version 4.0 or later.
IDC_NO 禁止圈
IDC_SIZE Obsolete for applications marked version 4.0 or later. Use IDC_SIZEALL.
IDC_SIZEALL 四向箭头指向东、西、南、北
IDC_SIZENESW 双箭头指向东北和西南
IDC_SIZENS 双箭头指向南北
IDC_SIZENWSE 双箭头指向西北和东南
IDC_SIZEWE 双箭头指向东西
IDC_UPARROW 垂直箭头
IDC_WAIT 沙漏,Windows7系统下会显示为选择的圆圈表示等待
- RegisterClass():注册窗口类;一个窗口类只有被注册了,才可以被CreateWindow等函数调用,wndclass是一个描述要注册的窗口类的信息的结构体;
if(0==RegisterClass(&wndClass))return 0;//注册窗口类并判断注册是否成功
- ShowWindow():该函数设置指定窗口的显示状态
BOOL ShowWindow(HWND hWnd, //窗口句柄int nCmdShow//指定窗口如何显示,该参数可以为值(0~11)之一);
- GetMessage():从调用线程的消息队列里取得一个消息并将其放于指定的结构,获取消息成功后,线程将从消息队列中删除该消息;在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下
GetMessage(LPMSG lpMsg,//指向MSG结构的指针,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中HWND hWnd,//取得其消息的窗口的句柄,指定接收属于哪一个窗口的消息;通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息UINT wMsgFilterMin,//指定被检索的最小消息值的整数UINT wMsgFilterMax//指定被检索的最大消息值的整数;如果wMsgFilterMin和wMsgFilter Max都设置为0,则接收所有消息);while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);//将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出DispatchMessage(&msg);//分派一个消息到窗口过程,由窗口过程函数对消息进行处理}
- GetMessage函数只有在接收到WM_QUIT消息时,才返回0。此时while语句判断的条件为假,循环退出,程序才有可能结束运行;在没有接收到WM_QUIT消息时,Windows应用程序就通过这个while循环来保证程序始终处于运行状态
- Windows应用程序的消息处理过程如下:
- 操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
- 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
- 应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。
- 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。
- 窗口消息:
WM_CREATE:是windows中一个窗口消息;当一个应用程序通过CreateWindowEx函数或者CreateWindow函数请求创建窗口时发送此消息,(此消息在函数返回之前发送)。
WM_PAINT:当窗口显示区域的一部分显示内容或者全部变为“无效”,以致于必须“更新画面”时,将由这个消息通知程序。
WM_CHAR:WM_CHAR消息包含被按下键盘按键的字符编码
WPARAM wParam //包含按的什么键;LPARAM lParam //一些附加信息,如在按键的同时是否按了Ctrl、Alt、Shift键
WM_CLOSE:窗口或应用程序应该关闭的时收到此消息;结合DestroyWindow函数使用,销毁指定窗口
WM_DESTROY:窗口销毁后,即调用DestroyWindow()后,消息队列得到的消息;结合PostQuitMessage函数使用,通知系统当前有一个线程发送了进程中止退出请求;PostQuitMessage函数投递一个WM_QUIT消息到线程消息队列并且立即返回,该函数简单的通知系统线程请求马上退出。当线程从它的消息队列收到WM_QUIT消息时,将退出自身消息循环并且交还控制给操作系统;其中0将作为WM_QUIT消息的wParam参数,作为程序退出码返回给系统处理
DefWindowProc():默认的窗口处理函数,我们可以把不关心的消息都丢给它来处理;例如,当这个函数在处理关闭窗口消息WM_CLOSE时,是调用DestroyWindow函数关闭窗口并且发WM_DESTROY消息给应用程序;而它对WM_DESTROY这个消息是不处理的,因为我们在应用程序中对这个消息的处理是发出WM_QUIT消息。因此WM_CLOSE、WM_DESTROY、WM_QUIT这三个消息是先后产生的。
- BeginPaint():函数为指定窗口进行绘图工作的准备,并将和绘图有关的信息填充到一个PAINTSTRUCT结构中
HDC BeginPaint(HWND hwnd, //[输入]被重绘的窗口句柄LPPAINTSTRUCT lpPaint //[输出]指向一个用来接收绘画信息的PAINTSTRUCT结构
);
- 如果函数成功,返回值是指定窗口的“显示设备描述表”句柄;hDC是设备场景句柄;hDC与绘图API(GDI函数)有关,是把窗口绘制在屏幕上用的,例:hDC=BeginPaint(hWnd,&paintStruct);
- TextOut():该函数用当前选择的字体、背景颜色和正文颜色将一个字符串写到指定位置;
BOOL TextOut(HDC hdc, // 设备描述表句柄int nXStart, // 字符串的开始位置 x坐标int nYStart, // 字符串的开始位置 y坐标LPCTSTR lpString, // 字符串int cbString // 字符串中字符的个数
);
- GetClientRect():该函数获取窗口客户区的大小;注意:窗口的客户区为窗口中除标题栏、菜单栏之外的地方
BOOL GetClientRect(HWND hWnd, // 窗口句柄LPRECT lpRect // 客户区坐标
);
- RECT:这个对象是用来存储成对出现的参数,比如,一个矩形框的左上角坐标、宽度和高度,RECT结构通常用于Windows编程。
- DrawText():该函数在指定的矩形里写入格式化的正文,根据指定的方法对正文格式化(扩展的制表符,字符对齐、折行等)
int DrawText(HDC hDC, // [输入]设备环境句柄LPCTSTR lpString, // [输入]指向将被写入的字符串的指针int nCount, // [输入]指向字符串中的字符数。如果nCount为-1,则lpString指向的字符串被认为是以\0结束的,DrawText会自动计算字符数。LPRECT lpRect, // [输入/输出]指向结构RECT的指针,其中包含文本将被置于其中的矩形的信息(按逻辑坐标)UINT uFormat // [输入]指定格式化文本的方法
);
- ZeroMemory():其作用是用0来填充一块内存区域
void ZeroMemory( PVOID Destination,//指向一块准备用0来填充的内存区域的开始地址SIZE_T Length//准备用0来填充的内存区域的大小,按字节来计算);
- SECURITY_ATTRIBUTES:安全属性结构体,包含一个对象的安全描述符,并指定检索到指定这个结构的句柄是否是可继承的;该结构的lpSecurityDescriptor成员用于设定管道的安全属性,如果传NULL,那么该管道将获得一个默认的安全属性
typedef struct _SECURITY_ATTRIBUTES {DWORD nLength; //结构体的大小,可用SIZEOF取得LPVOID lpSecurityDescriptor; //安全描述符BOOL bInheritHandle ;//安全描述的对象能否被新创建的进程继承
} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;
- CreatePipe():创建一个匿名管道,并从中得到读写管道的句柄
BOOL WINAPI CreatePipe( _Out_PHANDLE hReadPipe, //返回一个可用于读管道数据的文件句柄_Out_PHANDLE hWritePipe, //返回一个可用于写管道数据的文件句柄_In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,//传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进程继承。如果传NULL,则返回的句柄是不可继承的_In_DWORD nSize//管道的缓冲区大小。但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默认的缓冲区大小);
- STARTUPINFO:指定新进程的主窗口特性的一个结构
DWORD cb; //包含STARTUPINFO结构中的字节数,将cb初始化为sizeof(STARTUPINFO)
DWORD dwFlags; //使用标志及含义,STARTF_USESHOWWINDOW:使用wShowWindow成员;STARTF_USESTDHANDLES:使用hStdInput、hStdOutput和hStdError成员
wShowWindow的设置取决与同数据结构中的dwFlags, 如果dwFlags 被设置成STARTF_USESHOWWINDOW, 那么wShowWindow可以是除了SW_SHOWDEFAULT之外的任何其他值
StartupInfo.wShowWindow=SW_HIDE;//将STARTUPINFO结构中bai的窗口属性设置为duSW_HIDE(隐藏)
HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput用于标识键盘缓存,hStdOutput和hStdError用于标识控制台窗口的缓存
HANDLE hStdOutput;
HANDLE hStdError;
- CreateProcess():用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件;
BOOL CreateProcess
(LPCTSTR lpApplicationName,//指向一个NULL结尾的、用来指定可执行模块的字符串;当被设为NULL时,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。LPTSTR lpCommandLine,//指向一个以NULL结尾的字符串,该字符串指定要执行的命令行LPSECURITY_ATTRIBUTES lpProcessAttributes,LPSECURITY_ATTRIBUTES lpThreadAttributes,BOOL bInheritHandles,DWORD dwCreationFlags,LPVOID lpEnvironment,LPCTSTR lpCurrentDirectory,LPSTARTUPINFO lpStartupInfo,//指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体LPPROCESS_INFORMATIONlpProcessInformation//指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体
);
- PeekNamedPipe():预览一个管道中的数据,或取得与管道中的数据有关的信息;返回值BooL,非零表示成功,零表示失败
BOOL WINAPI PeekNamedPipe(__in HANDLE hNamedPipe, //管道句柄__out_opt LPVOID lpBuffer, //读取输出缓冲区,可选__in DWORD nBufferSize, //缓冲区大小__out_opt LPDWORD lpBytesRead, //接收从管道中读取数据的变量的指针,可选__out_opt LPDWORD lpTotalBytesAvail, //接收从管道读取的字节总数__out_opt LPDWORD lpBytesLeftThisMessage//保存这次读操作后仍然保留在消息中的字符数。只能为那些基于消息的命名管道设置
);
- ReadFile():从文件指针指向的位置开始将数据读出到一个文件中, 且支持同步和异步操作
BOOL ReadFile(HANDLE hFile,//文件的句柄LPVOID lpBuffer,//用于保存读入数据的一个缓冲区DWORD nNumberOfBytesToRead,//要读入的字节数LPDWORD lpNumberOfBytesRead,//指向实际读取字节数的指针LPOVERLAPPED lpOverlapped//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
- PeekMessage():该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构。
BOOL PeekMessage(LPMSG IpMsg,//接收消息信息的MSG结构指针HWND hWnd,//其消息被检查的窗口句柄UINT wMSGfilterMin,//指定被检查的消息范围里的第一个消息UINT wMsgFilterMax,//指定被检查的消息范围里的最后一个消息UINT wRemoveMsg//确定消息如何被处理,当参数取PM_NOREMOVE时,PeekMessage处理后,消息不从队列里除掉
);
- TCHAR:通过define定义的字符串宏
- 窗口句柄:系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二
使用管道实现重定向功能。在程序中启动新的进程,在新进程中执行ping.exe程序,使用管道技术将ping.exe的输出结果重定向到Windows的窗口中。在进程执行结束后,恢复系统默认的输入/输出。 代码如下:
#include<windows.h>
#define BUF_SIZE 1000
TCHAR PipeData[BUF_SIZE]="\0";
LRESULT CALLBACK myWndProc(HWND hWnd,UINT uMsgId,WPARAM wParam,LPARAM lParam);
void AppendText(HWND hwnd);
void OnPing(HWND hwnd);
void PeekAndPump();int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPreInst,LPSTR pszCmdLine,int nCmdShow)
{static char szAppName[]="输出重定向!";WNDCLASS wndClass;HWND hWnd;MSG msg;wndClass.style=CS_VREDRAW|CS_HREDRAW;//CS_HREDRAW:一旦移动或尺寸调整使客户区的宽度发生变化,就重新绘窗口;CS_VREDRAW:一旦移动或尺寸调整使客户区的高度发生变化,就重新绘制窗口wndClass.lpfnWndProc=myWndProc;//类的默认窗口实现程序是myWndProc这个函数实现的wndClass.cbClsExtra=0;//窗口类无扩展wndClass.cbWndExtra=0;//窗口实例无扩展wndClass.hInstance=hInst;//定义应用程序实例句柄wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);//把第一个参数设置为NULL,表示使用系统预定义图标(也可以使用自定义的图标);将标题栏上的图标定义为IDI_APPLICATIONwndClass.hCursor=LoadCursor(NULL,IDC_ARROW);//把第一个参数设置为NULL,表示使用系统预定义光标;IDC_ARROW 标准的箭头wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);//获得库存的白色画刷,赋给窗口背景色wndClass.lpszMenuName=NULL;//设置窗口类的菜单wndClass.lpszClassName=szAppName;//设置窗口类名称if(0==RegisterClass(&wndClass))//注册窗口类并判断注册是否成功return 0;hWnd=CreateWindow(szAppName,//窗口类结构体的字符串名称szAppName,//窗口的标题WS_OVERLAPPEDWINDOW,//窗口的风格CW_USEDEFAULT,//窗口左上角的x轴坐标CW_USEDEFAULT,//窗口左上角的y轴坐标CW_USEDEFAULT,//窗口的宽度CW_USEDEFAULT,//窗口的高度NULL,//本窗口的父窗口NULL,//本窗口的菜单hInst,//与当前窗口相连的实例句柄,NULL//CREATESTRUCT的附加参数);if(hWnd==0) return 0;ShowWindow(hWnd,nCmdShow);//显示窗口类UpdateWindow(hWnd);//更新消息while(GetMessage(&msg,NULL,0,0)){TranslateMessage(&msg);DispatchMessage(&msg);}return msg.wParam;
}LRESULT CALLBACK myWndProc(HWND hWnd,UINT uMsgId,WPARAM wParam,LPARAM lParam)
{switch(uMsgId){case WM_CREATE:return 0;case WM_PAINT:AppendText(hWnd);return 0;case WM_CHAR:if(wParam=='s'||wParam=='S')OnPing(hWnd);case WM_CLOSE:DestroyWindow(hWnd);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;default:return DefWindowProc(hWnd,uMsgId,wParam,lParam);}
}void AppendText(HWND hwnd)
{static TCHAR Data[10*BUF_SIZE]="\0";HDC hDC;PAINTSTRUCT paintStruct;RECT clientRect;strcat(Data,PipeData);PipeData[0]='\0';hDC=BeginPaint(hwnd,&paintStruct);if(hDC!=NULL){TextOut(hDC,0,0,"Press s or S to start ping",25);GetClientRect(hwnd,&clientRect);DrawText(hDC,Data,-1,&clientRect,DT_LEFT|DT_VCENTER);EndPaint(hwnd,&paintStruct);}
}void OnPing(HWND hwnd)
{LPCTSTR szCommand="ping.exe 127.0.0.1";//LPCTSTR就表示一个指向const对象的指针;指向以null结尾的命令字符串:"命令 设备参数"LPCTSTR szCurrentDirectory="c:\\windows\\system32\\";DWORD BytesLeftThisMessage=0;DWORD NumBytesRead;DWORD TotalBytesAvailable=0;HANDLE PipeReadHandle;//管道读句柄HANDLE PipeWriteHandle;//管道写句柄PROCESS_INFORMATION ProcessInfo;SECURITY_ATTRIBUTES SecurityAttributes;STARTUPINFO StartupInfo;BOOL Success;ZeroMemory(&StartupInfo,sizeof(StartupInfo));//用0来填充,即初始化结构体ZeroMemory(&ProcessInfo,sizeof(ProcessInfo));ZeroMemory(&SecurityAttributes,sizeof(SecurityAttributes));SecurityAttributes.nLength=sizeof(SECURITY_ATTRIBUTES);//设置安全属性结构体SecurityAttributes.bInheritHandle=TRUE;SecurityAttributes.lpSecurityDescriptor=NULL;Success=CreatePipe(&PipeReadHandle,&PipeWriteHandle,&SecurityAttributes,0);if(!Success) return;StartupInfo.cb=sizeof(STARTUPINFO);StartupInfo.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;StartupInfo.wShowWindow=SW_HIDE;StartupInfo.hStdOutput=PipeWriteHandle;StartupInfo.hStdError=PipeWriteHandle;Success=CreateProcess(NULL,(LPTSTR)szCommand,NULL,NULL,TRUE,0,NULL,szCurrentDirectory,&StartupInfo,&ProcessInfo);if(!Success) return;for(;;){NumBytesRead=0;Success=PeekNamedPipe(PipeReadHandle,PipeData,1,&NumBytesRead,&TotalBytesAvailable,&BytesLeftThisMessage);if(!Success) break;if(NumBytesRead){Success=ReadFile(PipeReadHandle,PipeData,BUF_SIZE-1,&NumBytesRead,NULL);if(!Success) break;PipeData[NumBytesRead]='\0';InvalidateRect(hwnd,NULL,TRUE);PeekAndPump();}else{if(WaitForSingleObject(ProcessInfo.hProcess,0)==WAIT_OBJECT_0) break;PeekAndPump();Sleep(100);}}CloseHandle(ProcessInfo.hThread);CloseHandle(ProcessInfo.hProcess);CloseHandle(PipeReadHandle);CloseHandle(PipeWriteHandle);
}void PeekAndPump()
{MSG Msg;while(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE))if(GetMessage(&Msg,NULL,0,0)){TranslateMessage(&Msg);DispatchMessage(&Msg);}
}
运行结果如下图: