posts - 256, comments - 1319, trackbacks - 41, articles - 8
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

一发不可收拾的学习

Posted on 2004-09-09 22:12 FantasySoft 阅读(647) 评论(4)  编辑 收藏 所属分类: All About SoftOnly Windows

        昨天还在感叹自己面对Windows开发就像一个傻瓜,今天随便找了个突破口,期待能够将自己掌握的知识联系起来,结果就是一发不可收拾,越看越迷糊,越想越凌乱。不管怎么样,先将今天看到的新面孔记个流水帐吧。
        首先,突破口就是WinMain函数的修饰符WINAPI。从WINDEF.H这个头文件中,我得知WINAPI实质上就是__stdcall
。那么什么是__stdcall呢?
        __stdcall属于Microsoft定义的函数修饰符,除了她之外,还有__fastcall、__cdecl和thiscall定义了函数被调用的方式,其中,thiscall不是关键字。以前关注的都是函数如何去定义,而函数调用都是很平常的事情,从来都没有去思考在函数调用的过程中,发生了什么事情。以下文字摘自MSDN,对于函数调用的过程中发生的事情做了描述:   
        All arguments are widened to 32 bits when they are passed. Return values are also widened to 32 bits and returned in the EAX register, except for 8-byte structures, which are returned in the EDX:EAX register pair. Larger structures are returned in the EAX register as pointers to hidden return structures. Parameters are pushed onto the stack from right to left.
        The compiler generates prolog and epilog code to save and restore the ESI, EDI, EBX, and EBP registers, if they are used in the function.
        译文:所有的实参在传递的时候都会被扩展为32位,返回值也是如此,并且会存储至EAX寄存器中(天啊,寄存器都来了)。如果返回的是8字节的结构体,那么她就会被存在EDX:EAX寄存器对中。如果是体积更大的结构体返回的话,那么EAX存放的将是一个指向结构体所在地址的指针。型参按照从右至左的顺序被推入栈中。
        在ESI、EDI、EBX和EBP寄存器当中,如果她们在程序中被使用到的话,编译器会生成序言(prolog)和结语(epilog)代码来存储和恢复这些被使用的寄存器的内容。

        再次回到刚刚提到的几种定义函数调用的修饰符上来。大家先看如下的函数定义和调用(以下代码和图片均来自MSDN):

void    calltype MyFunc( char c, short s, int i, double f );

void    MyFunc( char c, short s, int i, double
 f )
{
    
}


MyFunc (
'x'1281922.7183);

        对于以上的函数定义,calltype用__fastcall、__cdecl等替代。那么,在函数调用的过程中,不同的修饰符下的内存结构如下图:
       
         calltype = __cdecl

       
        calltype = __stdcall 和 thiscall
        
        
        calltype = __fastcall

        最后,由prolog和epilog又引出了Naked Function Calls的概念。所谓Nake Function Calls就是由自己来写prolog和epilog,而不是由编译器来生成。而这里面还有很多的学问,譬如Nake Function Calls的限制,prolog和epilog应该如何写等等。以下就先给出一个Naked Function的例子吧(来自MSDN):

__declspec(naked) int __fastcall  power(int i, int j) 
{
    
/* calculates i^j, assumes that j >= 0 */

    
/* prolog */
    __asm 
{
        push   ebp
        mov      ebp, esp
        sub      esp, __LOCAL_SIZE
       
// store ECX and EDX into stack locations allocated for i and j
        mov   i, ecx
        mov   j, edx
    }

      
    
{
        
int k=1// return value
        while (j-- > 0) k *= i;
        __asm 
{ mov eax, k };
    }


    
/* epilog */
    __asm  
    
{
        mov      esp, ebp
        pop      ebp
        ret
    }

}


int main()
{
}

        噢,我已经有点眩晕了。一天吃不成胖子,脑袋也装不下那么多的东西,就到这里吧。还是不要冷落WinMain,毕竟这才是主体,__stdcall只是一个修饰符而已啊。Oh, My God.

Feedback

#1楼    回复  引用    

2004-09-09 22:42 by 问题男 [未注册用户]
还遗漏了两个知识点:

Whether the caller function or called function removes the arguments from the stack at the end of the call. 

The name-decorating convention that the compiler uses to identify individual functions. 

前者是关于由谁(调用者或被调函数)释放参数的栈空间,后者是编译器“眼中的”函数符号名该如何被修饰

__stdcall、__fastcall都是被调函数清栈,__cdecl是被调着清栈;命名请查阅msdn,不再赘述

由于带有...参数,函数无法确定参数个数,自然无法清栈,所以wsprintf的修饰符是__cdecl,除此之外所有的winapi都是__stdcall

题外话:这些c/c++的规范的“二进制不确定性”也是组件技术(com etc)的产生原因之一(参见《essential com》——don box)

#2楼    回复  引用  查看    

2004-09-09 23:09 by FantasySoft      
刚想着把到底由被调函数还是主调函数来清除栈这个知识点加上呢。谢谢楼上的老大了。

至于name-decoration的问题,我也注意到了,但是总觉得这个在编程过程中意义不大,除非需要自己实现编译器。因此也没有将这个知识点加上。

再次感谢老大,让我受益非浅,谢谢!

#3楼    回复  引用    

2004-09-09 23:42 by 问题男 [未注册用户]
岂敢岂敢?先生后生尔

#4楼    回复  引用  查看    

2004-09-10 00:08 by FantasySoft      
还请老大以后多多指教,可惜您只留下了大名,却没有留下您的踪迹。还想着看看您的blog呢,呵呵~~

标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2004-10-14 17:40 编辑过
 
另存  打印