您的位置:首页 > 编程语言 > C语言/C++

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: