首页 > 代码库 > VC++编程之第一课笔记
VC++编程之第一课笔记
第一课 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++编程之第一课笔记