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分钟追求真理,明白人给个回复!

解决方案 »

  1.   

    vs2003的_MSC_VER是1310
    #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
      

  2.   

    早先的Windows(16位)回调函数的确是定义为pascal(那个时候没有stdcall方式),32位Windows才改为stdcall,stdcall是综合了pascal和cdecl调用的优点而形成的调用方式(从右向左入栈(cdecl),由被调用者清栈(pascal))。
      

  3.   

    cdecl:C/C++缺省调用方式,从右向左入栈,由调用者清栈;
    pascal:pascal缺省调用方式,从左向右入栈,由被调用者清栈;
    stdcall:从右向左入栈,由被调用者清栈。
      

  4.   

    #define PASCAL      __stdcall
      

  5.   

    "pascal,从左到右参数传递,被调者函数清除参数"这是没错的,maozefa 已经作了解释:
      "早先的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的情况存在,
    这段历史因果我就不得而知了。那必然是微软作了调整产生这样得概念混淆。
      

  6.   

    时间长了,都有些记不清了,好像早先的Windows API是纯C调用方式,即cdecl,而“为了加快回调函数的执行的效率”,“窗口回调函数是使用pascal调用”,后来可能为了统一包括回调函数在内的API的调用方式,而全部改用了stdcall。
      

  7.   

    蒲石和mozefa二位的回答给我启发. 查了一下资料,总结如下:
    =======================================================
    最早先,只有_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截然相反。
      

  8.   

    问题在于,老李写这么书的时候应该在2001年了吧. windows早已成为stdcall的天下了,所以不应该出错的. 书中的这个问题对有汇编基础的人会造成很大困惑.