窗口过程函数原型如下:
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
关键字CALLBACK定义的函数为回调函数,它实际上是一个宏:#define CALLBACK __stdcall
而__stdcall是一种调用约定。
1,回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。
在新建一个窗口程序时,必须要经过一下四个步骤:设计一个窗口类、注册窗口类、创建窗口、显示及更新窗口
设计窗口类的API原型为:
typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HINSTANCE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS, *PWNDCLASS;
注册窗口类的时候需要传入一个窗口过程的指针,WNDPROC A 32-bit pointer to a window procedure,所以WindowProc就是一个回调函数。
程序在调用一个函数(function)时(通常指api).相当于程序(program)呼叫(Call)了一个函数(function)关系表示如下:
call(调用)
program --------------------→ dll
程序在调用一个函数时,将自己的函数的地址作为参数传递给程序调用的函数时(那么这个自己的函数称回调函数).需要回调函数的 DLL 函数往往是一些必须重复执行某些操作的函数.关系表示如下:
call(调用)
program --------------------→ dll
↑ ¦
¦_______________________________¦
callback(回调)
2,调用约定
函数的调用约定主要决定函数参数的入栈顺序和谁来清除栈中的参数问题。
当高级语言函数被编译成机器码时,有一个问题就必须解决:因为CPU没有办法知道一个函数调用需要多少个、什么样的参数。即计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。在参数传递中,有两个很重要的问题必须得到明确说明:
函数调用后,由谁来把堆栈恢复原状?
函数的返回值放在什么地方?
一般情况下,VC编译器中project->setting->c/c++->code generation->call convention下面可以修改调用约定
回调函数一般是__stdcall,它是标准c调用约定,标准调用约定的函数在它们返回到调用者之前,都会从堆栈中移除掉参数,这也是Pascal的标准约定。名字修饰是前面加下划线,后面加@+参数大小,如_fun@number
__cdecl是c/c++调用约定,是调用者负责清理堆栈,而不是被调用函数。它是调用者来清除堆栈,所以对被调用函数的参数个数没有明确的限制,支持可变参数。但由于由被调函数清除堆栈,多以程序较大。它的名字修饰约定是在前面加下划线,如_fun。在这里我们应该要考虑类似于像scanf和printf这样的函数,这里我们应该明白这两个函数的参数都是可变的,如果参数不固定的话,在被调用函数内就无法知道参数究竟使用了多少个字节,所以为了实现可变参数,我们必须要在被调函数执行之后我们才知道参数究竟用了多少字节,所以我们在调用者来进行堆栈平衡操作。
不同环境下可以强制使用。