2006年就买了老李的<VCL架构剖析>,一直尘封在书架上. 近一段时间, 突觉可惜. 拿出来翻。从大方面觉得还可以,但有一些细节有严重问题,很影响读者思考。
也许是我看走眼, 但似乎是书上的问题,实在不吐不快。 下面就一个我认为不可原谅的问题,拿出来讨论一下,看有无同道中人,请给于指点:
就是,关于windows回调函数CALLBACK的调用方式问题。 我一直从实践中认为,callback就是stdcall,参数是从右至左入栈的。而该书却一直强调是pascal调用方式,也就是说,是从左至右入栈。这怎么可能呢?? 李大师会犯这种低级错误吗? 但白纸黑字啊!! 同志们帮鉴定一下!!!!该书的P15页,原话摘录:“此外为了加快回调函数的执行的效率,Microsoft使用了CALLBACK修饰关键字来定义WNDPROC,而CALLBACK则是定义成FAR PASCAL,代表一个使用长指针,Pascal调用惯例的函数”——但是,我查了一下VC的windef.h文件中,是#define CALLBACK __stdcall而且,在该书P177页,又再次强调:“pascal,从左到右参数传递,被调者函数清除参数 ”、“窗口回调函数是使用pascal调用惯例,但是VCL组件的事件处理函数却是register.....” 俺实在是看不下去了啊! 难道是我一直有误解? 我想一定是书的印刷问题!呵呵我很少发贴,一年一贴,就冲我花着20分钟追求真理,明白人给个回复!
#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#else
#define CALLBACK
#define WINAPI
#define WINAPIV
#define APIENTRY WINAPI
#define APIPRIVATE
#define PASCAL pascal
#endif
pascal:pascal缺省调用方式,从左向右入栈,由被调用者清栈;
stdcall:从右向左入栈,由被调用者清栈。
"早先的Windows(16位)回调函数的确是定义为pascal(那个时候没有stdcall方式),
32位Windows才改为stdcall,stdcall是综合了pascal和cdecl调用的优点而形成的调
用方式(从右向左入栈(cdecl),由被调用者清栈(pascal))。"
另外,《加密与解密》第三版 第73页中也有这样的内容。PASCAL调用约定与stdcall调用约定是不一样的。
“窗口回调函数是使用pascal调用惯例,但是VCL组件的事件处理函数却是register.....”
这里的PASCAL应该是引用了windef.h中的宏#define PASCAL __stdcall
与前面的pascal调用约定是不一样的。这里应该算是一个错误。以下是winnt.h中关于PASCAL调用约定得宏定义:
#ifdef _MAC
#define CALLBACK PASCAL
#define WINAPI CDECL
#define WINAPIV CDECL
#define APIENTRY WINAPI
#define APIPRIVATE CDECL #ifdef _68K_
#define PASCAL __pascal
#else
#define PASCAL
#endif
#elif (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED)
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
#else
#define CALLBACK
#define WINAPI
#define WINAPIV
#define APIENTRY WINAPI
#define APIPRIVATE
#define PASCAL pascal
#endif既然微软定义了这样的宏,那么说明曾经有段时间或者再某种情况下确实有 PASCAL==__pascal的情况存在,
这段历史因果我就不得而知了。那必然是微软作了调整产生这样得概念混淆。
=======================================================
最早先,只有_cdecl约定。它是自右至左。由调用者平衡堆栈。这种约定,好处是,支持可变参数调用(参数个数不定)。缺点是程序变大。
后来,pascal语言兴起,引入了pascal约定。自左至右入栈,由被调用者平衡。
在此情况下,WIN16 的API及DLL,就采用了pascal调用。甚至出现
int PASCAL WinProc(...) 这种声明方式。 也就是说,各种API调用,或回调函数都是以此为准的。在一些较老的书籍中,也出现“所有Windows API函数都是根据Pascal规范进行调用的”这种会引起新人误解的话。
再后来,win32出现后,引入了一种stdcall约定,它是_cdecl与pascal的混合体。它是自右至左,又是被调用者平衡。为了考虑兼容性,在VC中,把所有PASCAL调用进行了强制声明。
#define PASCAL __stdcall在这种情况下,在VC中,pascal可以说与stdcall等价。
但在原生的pascal语言,如delphi中,pascal约定仍保持原先的含义,是自左至右的,与stdcall截然相反。