前几天看到的文章,贴出来供大家参考!

解决方案 »

  1.   

    回调函数是一个很有用,也很重要的概念。当发生某种事件时,系统或其他函数将会自动调用你定义的一段函数。回调函数在windows编程使用的场合很多,比如Hook回调函数:MouseProc,GetMsgProc以及EnumWindows,DrawState的回调函数等等,还有很多系统级的回调过程。本文不准备介绍这些函数和过程,而是谈谈实现自己的回调函数的一些经验。     之所以产生使用回调函数这个想法,是因为现在使用VC和Delphi混合编程,用VC写的一个DLL程序进行一些时间比较长的异步工作,工作完成之后,需要通知使用DLL的应用程序:某些事件已经完成,请处理事件的后续部分。开始想过使用同步对象,文件影射,消息等实现DLL函数到应用程序的通知,后来突然想到可不可以在应用程序端先写一个函数,等需要处理后续事宜的时候,在DLL里直接调用这个函数即可。         于是就动手,写了个回调函数的原形。在VC和 Delphi里都进行了测试一:声明回调函数类型。       vc版              typedef int (WINAPI *PFCALLBACK)(int Param1,int Param2) ;       Delph版              PFCALLBACK = function(Param1:integer;Param2:integer):integer;stdcall;       实际上是声明了一个返回值为int,传入参数为两个int的指向函数的指针。       由于C++和PASCAL编译器对参数入栈和函数返回的处理有可能不一致,把函数类型用WINAPI(WINAPI宏展开就是__stdcall)或stdcall统一修饰。二:声明回调函数原形       声明函数原形      vc版               int WINAPI CBFunc(int Param1,int Param2);        Delphi版          function CBFunc(Param1,Param2:integer):integer;stdcall;                         以上函数为全局函数,如果要使用一个类里的函数作为回调函数原形,把该类函数声明为静态函数即可。
      

  2.   

    三: 回调函数调用调用者         调用回调函数的函数我把它放到了DLL里,这是一个很简单的VC生成的WIN32 DLL.并使用DEF文件输出其函数名 TestCallBack。实现如下:              PFCALLBACK  gCallBack=0;            void WINAPI TestCallBack(PFCALLBACK Func)           {                  if(Func==NULL)return;                  gCallBack=Func;                  DWORD ThreadID=0;                  HANDLE hThread = CreateThread(                                                                    NULL,                                                                         NULL,                                Thread1,                             LPVOID(0),                                                                         &ThreadID                                                                          );                   return;             }      此函数的工作把传入的 PFCALLBACK Func参数保存起来等待使用,并且启动一个线程。声明了一个函数指针PFCALLBACK gCallBack保存传入的函数地址。四: 回调函数如何被使用:                     TestCallBack函数被调用后,启动了一个线程,作为演示,线程人为的进行了延时处理,并且把线程运行的过程打印在屏幕上.本段线程的代码也在DLL工程里实现      ULONG  WINAPI Thread1(LPVOID Param)     {             TCHAR Buffer[256];             HDC hDC = GetDC(HWND_DESKTOP);             int Step=1;             MSG Msg;              DWORD StartTick;        //一个延时循环             for(;Step<200;Step++)             {                        StartTick = GetTickCount();                  /*这一段为线程交出部分运行时间以让系统处理其他事务*/                       for(;GetTickCount()-StartTick<10;)                         {                                 if(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE) )                                 {                                   TranslateMessage(&Msg);                                   DispatchMessage(&Msg);                                   }                           }                                                      /*把运行情况打印到桌面,这是vcbear调试程序时最喜欢干的事情*/           sprintf(Buffer,"Running %04d",Step);                         if(hDC!=NULL)                                  TextOut(hDC,30,50,Buffer,strlen(Buffer));                   }                /*延时一段时间后调用回调函数*/                  (*gCallback)(Step,1);                 /*结束*/                   ::ReleaseDC (HWND_DESKTOP,hDC);                  return 0;      }
      

  3.   


    五:万事具备        使用vc和Delphi各建立了一个工程,编写回调函数的实现部分       VC版     int WINAPI CBFunc(int Param1,int Param2)       {               int res= Param1+Param2;             TCHAR Buffer[256]="";            sprintf(Buffer,"callback result = %d",res);            MessageBox(NULL,Buffer,"Testing",MB_OK);  //演示回调函数被调用             return res;                   }            Delphi版          function CBFunc(Param1,Param2:integer):integer;          begin                  result:= Param1+Param2;                  TForm1.Edit1.Text:=inttostr(result);    / /演示回调函数被调用           end;               使用静态连接的方法连接DLL里的出口函数 TestCallBack,在工程里添加 Button( 对于Delphi的工程,还需要在Form1上放一个Edit控件,默认名为Edit1)。        响应ButtonClick事件调用 TestCallBack              TestCallBack(CBFunc) //函数的参数CBFunc为回调函数的地址        函数调用创建线程后立刻返回,应用程序可以同时干别的事情去了。现在可以看到屏幕上不停的显示字符串,表示dll里创建的线程运行正常。一会之后,线程延时部分结束结束,vc的应用程序弹出MessageBox,表示回调函数被调用并显示根据Param1,Param2运算的结果,Delphi的程序edit控件里的文本则被改写成Param1,Param2 的运算结果。           可见使用回调函数的编程模式,可以根据不同的需求传递不同的回调函数地址,或者定义各种回调函数的原形(同时也需要改变使用回调函数的参数和返回值约定),实现多种回调事件处理,可以使程序的控制灵活多变,也是一种高效率的,清晰的程序模块之间的耦合方式。在一些异步或复杂的程序系统里尤其有用 -- 你可以在一个模块(如DLL)里专心实现模块核心的业务流程和技术功能,外围的扩展的功能只给出一个回调函数的接口,通过调用其他模块传递过来的回调函数地址的方式,将后续处理无缝地交给另一个模块,随它按自定义的方式处理。       本文的例子使用了在DLL里的多线程延时后调用回调函数的方式,只是为了突出一下回调函数的效果,其实只要是在本进程之内,都可以随你高兴可以把函数地址传递来传递去,当成回调函数使用。        这样的编程模式原理非常简单单一:就是把函数也看成一个指针一个地址来调用,没有什么别的复杂的东西,仅仅是编程里的一个小技巧。至于回调函数模式究竟能为你带来多少好处,就看你是否使用,如何使用这种编程模式了。
      

  4.   

    __cdecl、__fastcall、__stdcall、__thiscall,都是压栈动作的区别。
      

  5.   

    不过__fastcall没有压栈动作的。呵呵。刚忘了说了。
      

  6.   

    http://expert.csdn.net/Expert/topic/2171/2171115.xml?temp=2.062625E-02http://expert.csdn.net/Expert/topic/2200/2200337.xml?temp=.4463617
      

  7.   

    其实回调函数不过是函数指针的应用而已,至于callback(__stdcall)是为了通用,因为大部分语言都支持这种调用方式
      

  8.   

    贴一下:
    回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就
    是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可
    以在回调函数里完成你要做的事。capVideoStreamCallback 这个回调函数,我没有做过,看了一下Help,应该是通过发送消息
    WM_CAP_SET_CALLBACK_VIDEOSTREAM,来设置的,或者调用宏capSetCallbackOnVideoStream
    来完成的。这样设定之后,系统在进行图像捕捉的过程中,就会自动调用你写的回调函数。这个回调函数的函数体需要你自已来写,然后在另一函数中调用,即是说:
    LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr)
    {
     ........
    }
    //在另一函数中调用它(即以capVideoStreamCallback的地址作为一参数)
    Function(1,......,capVideoStreamCallback,.....);
    这就好像我们用定时器一样,在设置定时器时需要为定时器设置一回调函数:
    ::SetTimer(m_hWnd,1,1000,(TIMERPROC)TMProc);这里的TMProc就是回调函数
    模块A有一个函数foo,它向模块B传递foo的地址,然后在B里面发生某种事件(event)时,通过从A里面传递过来的foo的地址调用foo,通知A发生了什么事情,让A作出相应反应。
        那么我们就把foo称为回调函数。    “这个回调函数不是VFW.h中声明的么,“
        ----那是声明了回调函数原型,是告诉你传递进来的回调函数必须和它定义的原型保持一致。    ”为什么要自己写函数体呢?“
        ----比如在上面模块B里面,它只知道当event发生时,向模块A发出通知,具体怎么回应这个事件,不是B所关心的,也不是B所能预料到的。
        你站在A的角度上思考,当然要你自己作出对event的反应,也就是你要自己写函数体。    你如果明白了C++里面的函数指针,就很容易理解回调函数了。"不知道系统调用后有什么结果,或者我怎么利用这个结果啊"
    ---如果你向系统传递一个回调函数地址,那么你的程序就相当于上面我说的模块A,系统就相当于模块B,系统只是调用你的函数,它根本不可能知道会有什么结果。
       你怎么利用这个结果,看你是怎么定义这个回调函数的。      
    回调函数和回调机制是不同的概念,。,,函数是被调用的,但是回调机制在不同的语言中不都是以函数指针来实现的。比如c#...一般的在windows api 中,会调都是使用函数指针实现的函数调用方式
     
    我们知道在进行函数调用时,有几种调用方法,主要分为C式,Pascal式.在C和C++中
    C式调用是缺省的,类的成员函数缺省调用为_stdcall。二者是有区别的,下面我们用
    实例说明一下:1. __cdecl :C和C++缺省调用方式
    例子: 
    void Input( int &m,int &n);/*相当于void __cdecl Input(int &m,int &n);*/
    以下是相应的汇编代码:
    00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax
    0040106B push eax ;然后压栈
    0040106C lea ecx,[ebp-4] ;取[ebp-4]地址(ebp-4),存到ecx
    0040106F push ecx ;然后压栈
    00401070 call @ILT+5(Input) (0040100a);然后调用Input函数
    00401075 add esp,8 ;恢复栈
     
        从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
    后压栈ebp-4,然后调用函数Input,最后Input函数调用结束后,利用esp+8恢复栈。由
    此可见,在C语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复
    堆栈。
    下面看一下:地址ebp-8和ebp-4是什么?
        在VC的VIEW下选debug windows,然后选Registers,显示寄存器变量值,然后在
    选debug windows下面的Memory,输入ebp-8的值和ebp-4的值(或直接输入ebp-8和-4),
    看一下这两个地址实际存储的是什么值,实际上是变量 n 的地址(ebp-8),m的地址
    (ebp-4),由此可以看出:在主调用函数中进行实参的压栈并且顺序是从右到左。另外,
    由于实参是相应的变量的引用,也证明实际上引用传递的是变量的地址(类似指针)。
    总结:在C或C++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且
    恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用
    函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划
    线(_).2. WINAPI (实际上就是PASCAL,CALLBACK,_stdcall)
    例子: 
    void WINAPI Input( int &m,int &n);
    看一下相应调用的汇编代码:
    00401068 lea eax,[ebp-8]
    0040106B push eax
    0040106C lea ecx,[ebp-4]
    0040106F push ecx
    00401070 call @ILT+5(Input) (0040100a)
     
        从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
    后压栈ebp-4,然后调用函数Input,在调用函数Input之后,没有相应的堆栈恢复工作
    (为其它的函数调用,所以我没有列出)
    下面再列出Input函数本身的汇编代码:(实际此函数不大,但做汇编例子还是大了些,
    大家可以只看前和后,中间代码与此例子无关) 
    39: void WINAPI Input( int &m,int &n)
    40: {
    00401110 push ebp
    00401111 mov ebp,esp
    00401113 sub esp,48h
    00401116 push ebx
    00401117 push esi
    00401118 push edi
    00401119 lea edi,[ebp-48h]
    0040111C mov ecx,12h
    00401121 mov eax,0CCCCCCCCh
    00401126 rep stos dword ptr [edi]
    41: int s,i;
    42:
    43: while(1)
    00401128 mov eax,1
    0040112D test eax,eax
    0040112F je Input+0C1h (004011d1)
    44: {
    45: printf("\nPlease input the first number m:");
    00401135 push offset string "\nPlease input the first number m"... (004260b8)
    0040113A call printf (00401530)
    0040113F add esp,4
    46: scanf("%d",&m);
    00401142 mov ecx,dword ptr [ebp+8]
    00401145 push ecx
    00401146 push offset string "%d" (004260b4)
    0040114B call scanf (004015f0)
    00401150 add esp,8
    47:
    48: if ( m= s )
    004011B3 mov eax,dword ptr [ebp+8]
    004011B6 mov ecx,dword ptr [eax]
    004011B8 cmp ecx,dword ptr [ebp-4]
    004011BB jl Input+0AFh (004011bf)
    57: break;
    004011BD jmp Input+0C1h (004011d1)
    58: else
    59: printf(" m < n*(n+1)/2,Please input again!\n");
    004011BF push offset string " m < n*(n+1)/2,Please input agai"... (00426060)
    004011C4 call printf (00401530)
    004011C9 add esp,4
    60: }
    004011CC jmp Input+18h (00401128)
    61:
    62: }
    004011D1 pop edi
    004011D2 pop esi
    004011D3 pop ebx
    004011D4 add esp,48h
    004011D7 cmp ebp,esp
    004011D9 call __chkesp (004015b0)
    004011DE mov esp,ebp
    004011E0 pop ebp
    004011E1 ret 8
     
     
     
        最后,我们看到在函数末尾部分,有ret 8,明显是恢复堆栈,由于在32位C++中,
    变量地址为4个字节(int也为4个字节),所以弹栈两个地址即8个字节。
    由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能
    实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,
    因为参数数量由主调函数确定。
    下面再看一下,ebp-8和ebp-4这两个地址实际存储的是什么值,ebp-8地址存储的是n 
    的值,ebp -4存储的是m的值。说明也是从右到左压栈,进行参数传递。    总结:在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且
    负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法
    是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数
    所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8
    对于大多数api函数以及窗口消息处理函数皆用 CALLBACK ,所以调用前,主调函数会
    先压栈,然后api函数自己恢复堆栈。如: 
    push edx
    push edi
    push eax
    push ebx
    call getdlgitemtexta
      

  9.   

    Win32程序函数调用时堆栈变化情况分析
     
    2002-12-10 18:19:13   PCVC.NET   halk   阅读次数: 4804 
      在经典的汇编语言教程中,函数调用时堆栈的使用都是着重讲解的问题。如今随着高级语言的越来越完善,单纯使用汇编开发的程序已经不多了。但对函数调用时堆栈动向的了解仍有助于我们明晰程序的执行流程,从而在程序编写和调试的过程中有一个清晰的思路。一.调用约定
    在Win32中,有关函数的调用主要有两种约定。
    1._stdcall
            以__stdcall方式调用的函数有以下特征:
        &#8226;  参数由右至左压栈
        &#8226; 调用返回时,堆栈由被调函数调整
    2.__cdecl
    __cdecl约定是C/C++函数的默认调用约定。它有以下特征:
        &#8226; 参数由右至左压栈
        &#8226; 调用返回时,堆栈由调用者调整二.Win32函数调用过程1.    压入参数
    这里依据以上的调用方式将调用者给出的参数一一压入堆栈。2.    压入断点
    当程序执行到Call指令的时候,当前语句的地址作为断点地址压入堆栈。3.    跳转
    eip的值被重新设置为被调函数的起始地址。4.    mov ebp, esp
    这里ebp被用来在堆栈中寻找调用者压入的参数,同时作为调用者堆栈指针的一个备份。在此前还应该执行一条:
    push ebp
    把ebp中原来的数值保存。5.    sub esp,N
    这里N是函数内局部变量的总字节数加上一个整数,一般为40。此后esp即为被调函数的堆栈指针了。6.    初始化esp ~ esp-N之间的N字节空间
    这是对堆栈中已分配给局部变量使用的内存空间的初始化,一般全部设置为0xcc。7.    顺序执行函数内语句。
    此时函数的堆栈位于所有局部变量的内存空间之后,二者之间一般有40字节的隔离带。8.返回
    为保障调用的正常返回,函数内应当保证规范使用堆栈,使即将返回的时候esp的值恢复为执行第一条语句前的状态。说明白点,就是每一条push都要有相应的pop。
    调用返回的过程如下:
    mov esp, ebp
    执行后,esp恢复为调用者的堆栈指针,栈顶除断点地址外,还存有原ebp的值和调用时压入的参数。
    然后依次弹出ebp的值和断点地址。如果是__cdecl约定则直接返回调用者,调用者将负责调整堆栈,丢弃调先前压入的参数。如果是__stdcall则这个工作由被调函数来执行。程序样例如下:
    ……
    0040B8E8   push        1            ;压入参数
    0040B8EA   call        00401028        ;调用函数
    ……
    00401028   jmp         0040b7c0        ;跳转到函数入口
    ……
    0040B7C0   push        ebp            ;保存ebp
    0040B7C1   mov         ebp,esp        
    0040B7C3   sub         esp,44h        ;设置函数的堆栈指针,此函数中有4
    ;字节的局部变量
    0040B7C6   push        ebx
    0040B7C7   push        esi        
    0040B7C8   push        edi
    0040B7C9   lea         edi,[ebp-44h]    
    0040B7CC   mov         ecx,11h
    0040B7D1   mov         eax,0CCCCCCCCh
    0040B7D6   rep stos    dword ptr [edi]    ;初始化局部变量空间
    0040B7D8   mov         eax,dword ptr [ebp+8]
    0040B7DB   mov         dword ptr [ebp-4],eax
    ……
    0040B7DE   pop         edi            ;弹出曾压栈的数据
    0040B7DF   pop         esi
    0040B7E0   pop         ebx
    0040B7E1   mov         esp,ebp        ;恢复调用者的堆栈
    0040B7E3   pop         ebp            ;弹出原ebp值
    0040B7E4   ret         4            ;返回并将堆栈向上调整4字节。
    ;此处为__stdcall约定,所以由函数调
    ;整堆栈相应的C代码如下:void __stdcall fun(int);int main(void)
    {
        ……    
    fun(1);
    ……
        return 0;
    }void __stdcall fun(int para)
    {
        int localpara = para;
        ……
    }
     
    C 与C++的两种调用方法
    (1)_stdcall:用于Win32 API函数和COM+接口。它从右向左将参数推入堆栈,被调函数在返回之前从堆栈中弹出自己的参数。从堆栈中弹出自己参数的函数不支持参数数目的变化。
    (2)_cdecl:C和C++默认方式)。它也是从右向左传递参数。但是被调函数不负责从堆栈中弹出参数,调用函数将在函数调用返回后晴空堆栈。
    CALLBACK采用方式(1)。它称为回调函数,即供系统调用的函数。
    例如窗口函数、定时处理函数、线程处理函数等。CALLBACK 是老式叫法,现在微软都改为WINAPI.至于编译器的解释上。c与c++搞的很麻烦。我也记不住每次碰到都去查一下.我列个表:语言                  调用约定            输出方式c                     stdcall          _SomeFunctionname@Number
    c                     cdecl            _SomeFunctionname
      C++的烦的要死。
    C++编译时函数名修饰约定规则: __stdcall调用约定: 
    1、以“?”标识函数名的开始,后跟函数名; 
    2、函数名后面以“@@YG”标识参数表的开始,后跟参数表; 
    3、参数表以代号表示: 
    X--void , 
    D--char, 
    E--unsigned char, 
    F--short, 
    H--int, 
    I--unsigned int, 
    J--long, 
    K--unsigned long, 
    M--float, 
    N--double, 
    _N--bool, 
    .... 
    PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复; 
    4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前; 
    5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。 其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如 
    int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z” 
    void Test2() -----“?Test2@@YGXXZ” __cdecl调用约定: 
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。 __fastcall调用约定: 
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。 
          编写DLL的时候输出函数老是不认就是编译器解释的不同造成的。EXTERN "C"就是为了统一。全部用c方式解释。两种声明是不同的,vc默认的调用是__cdecl ,所以 extern "C" __declspec(dllimport) int mydllfunc(int);是extern "C" __declspec(dllimport) int __cdecl mydllfunc(int);
    因此和extern "C" __declspec(dllimport) int __stdcall mydllfunc(int);是有区别的
      

  10.   

    回调函数确实非常有用,
    不过,它毕竟是一个“函数”,
    在这个面向“对象”的编程世界里,
    多少显得有些太过于底层。以前使用回调函数的地方,
    在面向对象的解决方案里,可以用COM等来代替。
    这样,用一个“对象”指针,代替了“函数”指针.
      

  11.   


    调用约定调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:
    _cdecl按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。
    _stdcall按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。所有的Win32 API函数都遵循该约定。
    _fastcall头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。未来的编译器可能使用不同的寄存器来存放参数。
    thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。
    naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用,如下:__declspec( naked ) int func( formal_parameters ){// Function body}
    过时的调用约定原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:#define CALLBACK __stdcall#define WINAPI __stdcall#define WINAPIV __cdecl#define APIENTRY WINAPI#define APIPRIVATE __stdcall#define PASCAL __stdcall
    表7-1显示了一个函数在几种调用约定下的修饰名(表中的“C++”函数指的是“C++”全局函数,不是成员函数),函数原型是void CALLTYPE test(void),CALLTYPE可以是_cdecl、_fastcall、_stdcall。表7-1 不同调用约定下的修饰名
    调用约定
     extern “C”或.C文件
     .cpp, .cxx或/TP编译开关
     
    _cdecl
     _test
     ?test@@ZAXXZ
     
    _fastcall
     @test@0
     ?test@@YIXXZ
     
    _stdcall
     _test@0
     ?test@@YGXXZ
     函数调用的几个概念:_stdcall,_cdecl....
    (剑灵时空-www.SwordElf.com)
     
    原作者: csdn,sinman
    来自于: 不详
    共有213位读者阅读过此文 内容:
    左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。    _stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。    2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。    _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。    3、__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。    _fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。        4、thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。    5、naked call采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。    关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。    要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。2)名字修饰约定1、修饰名(Decoration name)“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同,下面分别说明。    a、C编译时函数名修饰约定规则:__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
       
    __fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。    它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。    b、C++编译时函数名修饰约定规则:__stdcall调用约定:
              1、以“?”标识函数名的开始,后跟函数名;
              2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;
              3、参数表以代号表示:
                 X--void ,
                 D--char,
                 E--unsigned char,
                 F--short,
                 H--int,
                 I--unsigned int,
                 J--long,
                 K--unsigned long,
                 M--float,
                 N--double,
                 _N--bool,
                 ....
                 PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;
              4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前; 
              5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。    其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如
              int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
              void Test2()                       -----“?Test2@@YGXXZ”__cdecl调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。__fastcall调用约定:
    规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。    VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用.CB在输出函数声明时使用4种修饰符号
    //__cdecl
    cb的默认值,它会在输出函数名前加_,并保留此函数名不变,参数按照从右到左的顺序依次传递给栈,也可以写成_cdecl和cdecl形式。
    //__fastcall
    她修饰的函数的参数将尽肯呢感地使用寄存器来处理,其函数名前加@,参数按照从左到右的顺序压栈;
    //__pascal
    它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈;
    //__stdcall
    使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈,也可以是_stdcall;