VC++编程之第一课笔记――Windows程序内部运行原理
2014-11-20 21:50
549 查看
第一课 Windows程序内部运行原理
API
操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用。这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。如Create Window就是一个API函数,应用程序调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。
MSG(消息结构体)
结构体定义如下:typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; #ifdef _MAC DWORD lPrivate; #endif } MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;传递的消息,宏以"WM_"开头
hwnd,资源的标识
细分:图标句柄(HICON)光标句柄(HCURSOR)
窗口句柄(HWND)
应用程序实例句柄(HINSTANCE)
message
比如说,敲击'A'键,它只传递了WM_KEYDOWN的消息,具体参数在下面的参数内:wParam:包含了敲下的键的信息,WM_CHAR(ASCII值)lParam
time:消息传递过去的时间
pt:消息传递时鼠标所处坐标
WINMAIN函数
Windows程序的入口函数:int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
hPrevInstance
同一程序执行的实例,指向前一个实例的句柄,现已几乎不用szCmdLine
类似于C程序main函数的命令行参数argc与argv,sz表示以零结束的字符串nCmdShow
指定程序运行时的显示状态,是最大化还是最小化亦或是正常注释:此函数由操作系统调用,因此以上的参数都是由操作系统赋值窗口
窗口类的结构体:typedef struct tagWNDCLASSA { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCSTR lpszMenuName; LPCSTR lpszClassName; } WNDCLASSA, *PWNDCLASSA, NEAR *NPWNDCLASSA, FAR *LPWNDCLASSA;
style(窗口类的类型)
CLASS STYLE,宏以"CS_"开头。一般赋值"CS_HREDRAW | CS_VREDRAW",使窗口在水平或垂直坐标发生改变时重画。lpfnWndProc(窗口过程函数,回调函数)
此为函数指针。当应用程序收到某一窗口的消息时,就调用所指向的函数来处理这条消息。这一调用过程不用应用程序自己来实施,而有操作系统去调用这个函数。但是,回调函数本身的代码必须由我们自己写上去。cbClsExtra
为整个窗口类申请额外的内存空间,被属于这个窗口类的所有窗口共享。一般设为零。cbWndExtra
指定一定的内存分配给这个窗口,称为窗口附加内存。通常也设为零。hInstance
实例号,代表了当前应用程序的实例号。一般将WinMain函数中的hInstance赋给它。
hIcon(图标句柄)
使用LoadIcon函数:
WINUSERAPI HICON WINAPI LoadIconA( HINSTANCE hInstance, LPCSTR lpIconName );使用微软给我们提供的标准图标时,将hInstance设为NULL。微软给我们提供的图标名称是以"IDI_"开头的,比如说IDI_APPLICATION,IDI_QUESTION。
hCursor(光标句柄)
与图标类似,也使用LoadCursor函数获取光标,只是名称以"IDC_"开头。
hbrBackground(画刷的句柄)
使用GetStockObject函数,获得的句柄需要强制转换。其所用的宏一般以"_BRUSH"结尾,代表各种颜色的画刷。例子:
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
lpszMenuName(菜单的名字)
lpszClassName(窗口类的名字)
窗口创建步骤:
1. 设计一个窗口类
将上面的参数赋值2. 注册窗口类
使用RegisterClass函数对设计的窗口进行创建,一般都会创建成功。
WINUSERAPI ATOM WINAPI RegisterClassA( CONST WNDCLASSA *lpWndClass );例子:
RegisterClass(&wndclass);
3. 创建窗口
使用CreateWindow函数进行窗口的创建。创建成功后会返回一个HWND类型的句柄,用以显示及更新窗口。 函数定义如下:
WINUSERAPI HWND WINAPI CreateWindowExA( DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int X, int Y, int nWidth, int nHeight, HWND hWndParent , HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam ); #define CreateWindowA(lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam)\ CreateWindowExA(0L, lpClassName, lpWindowName, dwStyle, x, y,\ nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam) #ifdef UNICODE #define CreateWindow CreateWindowW #else #define CreateWindow CreateWindowA #endif // !UNICODE其中:lpClassName(窗口的名字)使用上面赋值好的即可lpWindowName(标题)即窗口左上角的标题名dwStyle(窗口的类型)要与上面的窗口类的类型区分开,这个所取的宏值是以"WS_"开头的。一般使用WS_OVERLAPPEDWINDOW。
#define WS_OVERLAPPEDWINDOW ( WS_OVERLAPPED | \ WS_CAPTION | \ WS_SYSMENU | \ WS_THICKFRAME | \ WS_MINIMIZEBOX | \ WS_MAXIMIZEBOX )上面的宏定义依次为:可层叠、具有标题栏、具有系统菜单、可调边框、具有最小化按钮、具有最大化按钮。x,y(窗口左上角的坐标)这两个值一般使用CW_DEFAULT宏,表示系统给它的缺省值。nWidth,nHeigth(窗口的宽度和高度)也可使用缺省的CW_DEFAULT值。hWndParent(父窗口句柄)hMenu(菜单句柄)hInstance(当前实例句柄)将
WinMain函数中的hInstance参数赋给它即可lpParam(附加参数)作为一个窗口创建时WM_CREATE消息中的lParam参数的指针。在创建多文档接口(MDI)客户窗口的时候,此指针必须指向CLIENTCREATESTRUCT结构体。用不上的时候设置为NULL即可。
4. 显示及更新窗口
使用ShowWindow函数显示窗口,
UpdateWindow函数刷新窗口。
ShowWindow函数定义如下:
WINUSERAPI BOOL WINAPI ShowWindow( HWND hWnd, int nCmdShow );其中:HWND(显示的窗口的句柄)赋值上一步骤中得到的句柄即可nCmdShow(窗口的显示状态)使用的宏定义是以"SW_"开头的,正常显示为SW_SHOWNORMAL。
消息循环
使用GetMessage函数从消息队列中取消息。函数定义如下:
WINUSERAPI BOOL WINAPI GetMessageA( LPMSG lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax );其中:
lpMsg
指向MSG结构体的指针。它有out的标识,说明在进行传参的时候不需要对这个结构体的内部成员进行初始化,只需要定义一个结构体变量并将其地址放在这里就可以了。通过函数调用,会自动填充结构体内部的成员变量。hWnd
表示获得哪个窗口的消息。设为NULL表示获得调用线程的属于任何窗口的消息。wMsgFilterMin
设定消息队列中获得的消息的最小值。可以使用WM_KEYFIRST指定第一个键盘的消息,WM_MOUSEFIRST指定第一个鼠标的消息。wMsgFilterMax
与上一参数相反。可以使用WM_KEYLAST指定最大的键盘的消息。由于函数返回BOOl值,当从消息队列中取得消息时返回的值为真,当满足为假的条件时,程序就会退出了。因此可以用如下代码得到消息循环:while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }其中:
TranslateMessage
函数
用户按下按键时会产生WM_KEYDOWN和WM_KEYUP消息,同时所按按键的字母会在附加参数wParam中。这个函数会将这两个消息转换为一个WM_CHAR消息,并投入到消息队列中。整个过程中不会影响原来的消息,只会产生一个新的消息。DispatchMessage
函数
它会将收到的消息传到窗口的回调函数中去处理。它先将其分发给操作系统,然后操作系统会利用回调函数来处理这个消息。消息处理函数(回调函数)
它返回的是long型。它的参数与消息结构提的前四个参数一样。函数定义:typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);其中:
HWND hWnd; UINT uMsg; WPARAM wParam; LPARAM lParam;
MessageBox
函数
这个比较熟悉,其返回值是点击的BUTTON的值,其他的就不多介绍了。函数原型:WINUSERAPI int WINAPI MessageBoxA( HWND hWnd , LPCSTR lpText, LPCSTR lpCaption, UINT uType );其中:
hWnd
所属窗口的句柄lpText
要显示的内容lpCation
消息框的标题uType
消息框包含的BUTTON的类型,其定义的宏是以"MB_"开头的,比如MB_OK、MB_OKCANCEL等窗口的重绘
窗口在改变后进行刷新重绘的时候会发送一个WM_PAINT消息,消息处理函数收到这个消息时会调用BeginPaint和
EndPaint函数对窗口进行重绘。且这两个函数只能在响应WM_PAINT消息时使用。
case WM_PAINT: hDC = BeginPaint(hWnd, &ps); ・・・ EndPaint(hWnd, &ps);DC(Device Context)设备描述表是一个用来确定任何设备的GDI输出的位置和形象的属性的集合。下面是
BeginPaint函数的原型:
WINUSERAPI HDC WINAPI BeginPaint( HWND hWnd, LPPAINTSTRUCT lpPaint );其中lpPaint是一个指向PAINTSTRUCT结构的指针,它也是out标识的,即此结构体系统会自动填充的,不用我们去关心。
在窗口中显示文字
1. 获得设备环境句柄(HDC)
先使用GetDC函数获得设备环境句柄(HDC)。
2. 再使用TextOut
函数在窗口输出文字
函数原型如下:WINGDIAPI BOOL WINAPI TextOutA( HDC hdc, int nXStart, int nYStart, LPCSTR lpString, int cbString );其中:
hdc
即第一步得到的设备环境句柄nXStart,nYStart
文本开始的坐标,此坐标相对于窗口左上角(原点)lpString
输出的字符串cbString
输出的字符串的位数3. 使用Release
函数释放HDC句柄
例子:HDC hDC = GetDC(hWnd); TextOut(hDC, 0, 50, TEXT("title"), TEXT("one words")); ReleaseDC(hWnd, hDC);
窗口的关闭
示例代码:case WM_CLOSE: if (IDYES == MessageBox(hWnd, TEXT("Quit this program?"), TEXT("QUESTION"), MB_YESNO)) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam);当窗口即将关闭,即你想要退出程序,点击右上角的"x"时,会发送WM_CLOSE消息,继而调用
DestroyWindow函数。
DestroyWindow函数会把窗口销毁,再发送WM_DESTROY、WM_NCDESTROY消息使窗口无效并移除其键盘焦点。这个函数还销毁窗口的菜单,清空线程的消息队列,销毁与窗口过程相关的定时器,解除窗口对剪贴板的拥有权,打断剪贴板器的查看链。而
PostQuitMessage函数功能:该函数向系统表明有个线程有终止请求,发送WM_QUIT消息到线程的消息队列中。其参数为退出代码,会作为WM\QUIT消息的附加参数放入wParam中。而上面的消息循环中的
GetMessage函数收到WM_QUIT消息会返回零值,从而终止消息循环。循环终止时,
WinMain函数就会退出了。在上面的额示例代码中我们还看到了
DefWindowProc函数,它是缺省的消息处理函数,也是必不可少的。注意:
DestroyWindow函数会将窗口销毁,但是还并没有退出程序。
两种调用约定
1. _stdll调用
_stdcall是Pascal程序的缺省调用方式,参数采用从右到左的压栈方式,被调函数自身在返回前清空堆栈。WIN32 API都采用_stdcall调用方式,这样的宏定义说明了问题:#define CALLBACK __stdcall #define WINAPI __stdcall #define WINAPIV __cdecl #define APIENTRY WINAPI #define APIPRIVATE __stdcall #define PASCAL __stdcall按C编译方式,_stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数。
2. _cdecl调用
_cdecl是C/C++的缺省调用方式,参数采用从右到左的压栈方式,传送参数的内存栈由调用者维护。_cedcl约定的函数只能被C/C++调用,每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。由于_cdecl调用方式的参数内存栈由调用者维护,所以变长参数的函数能(也只能)使用这种调用约定。由于Visual C++默认采用_cdecl调用方式,所以VC中中调用DLL时,用户应使用_stdcall调用约定。按C编译方式,_cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。3. _fastcall调用
_fastcall调用较快,它通过CPU内部寄存器传递参数。按C编译方式,_fastcall调用约定在输出函数名前面加“@”符号,后面加“@”符号和参数的字节数,形如@functionname@number。4. _stdcall与_cdecl的区别
WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除??如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用_stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用_stdcall(虽然有时是以WINAPI的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用_stdcall关键字。
本文出自 “zero4eva” 博客,请务必保留此出处http://zero4eva.blog.51cto.com/7558298/1580296
相关文章推荐
- 孙鑫VC学习笔记:第一讲 Windows程序内部运行原理
- 孙鑫vc++学习(vs2008)笔记之第一课Windows程序运行原理
- 孙鑫VC++讲座笔记-(1)Windows程序内部运行机制
- 孙鑫VC++讲座笔记-(1)Windows程序内部运行机制
- [转]孙鑫VC教程例子代码1---Windows程序内部运行原理
- Windows程序内部运行原理及SDK编程实现
- 《笔记》孙鑫老师MFC第一讲(windows程序内部运行原理)
- 孙鑫VC++讲座笔记-(1)Windows程序内部运行机制
- Lesson1 Windows程序内部运行原理 ---孙鑫VC++教程
- vc学习笔记之windows程序内部运行机制
- 孙鑫VC++讲座笔记-(1)Windows程序内部运行机制
- 孙鑫VC讲座笔记--WINDOWS程序内部运行原理
- MFC笔记 Windows程序内部运行原理
- 一. Windows程序内部运行机制--Windows编程课程学习笔记
- Lesson1 Windows程序内部运行原理 ---孙鑫VC++教程
- 孙鑫VC学习笔记:第一讲 Windows程序内部运行原理
- VC++讲座笔记-(1)Windows程序内部运行机制
- 孙鑫VC视频学习笔记之windows程序内部运行原理
- MFC视频教程(孙鑫)学习笔记1-Windows程序内部运行原理
- 孙鑫VC++讲座笔记-(1)Windows程序内部运行机制