DLL中调用约定和名称修饰

写了一篇关于DLL中调用约定和名称修饰的文章,其中还有很多内容不确定~请大家指教~谢谢~文档中心中的链接:  
http://www.csdn.net/Develop/read_article.asp?id=25141  
http://www.csdn.net/develop/Read_Article.asp?Id=25142  
http://www.csdn.net/develop/Read_Article.asp?Id=25143  
=============================  
DLL中调用约定和名称修饰  
 
调用约定(Calling  Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。  
 
在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器。这项技术通常被称为名称改编(Name  Mangling)或者名称修饰(Name  Decoration)。许多C++编译器厂商选择了自己的名称修饰方案。  
 
因此,为了使其它语言编写的模块(如Visual  Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰。  
1.调用约定(Calling  Convention)  
调用约定用来处理决定函数参数传送时入栈和出栈的顺序(由调用者还是被调用者把参数弹出栈),以及编译器用来识别函数名称的名称修饰约定等问题。在Microsoft  VC++  6.0中定义了下面几种调用约定,我们将结合汇编语言来一一分析它们:  
1、__cdecl  
__cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。  
 
下面将通过一个具体实例来分析__cdecl约定:  
 
在VC++中新建一个Win32  Console工程,命名为cdecl。其代码如下:  
 
int  __cdecl  Add(int  a,  int  b);                        //函数声明  
 
void  main()  
{  
           Add(1,2);                                                            //函数调用  
}  
 
int  __cdecl  Add(int  a,  int  b)                        //函数实现  
{  
           return  (a  +  b);  
}  
 
函数调用处反汇编代码如下:  
 
;Add(1,2);  
push                                    2                                                                        ;参数从右到左入栈,先压入2  
push                1                                                                        ;压入1  
call                            @ILT+0(Add)  (00401005)            ;调用函数实现  
add                          esp,8                                                            ;由函数调用清栈  
2、__stdcall  
__stdcall调用约定用于调用Win32  API函数。采用__stdcal约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret  n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。  
 
还是那个例子,将__cdecl约定换成__stdcall:  
 
int  __stdcall  Add(int  a,  int  b)  
{  
return  (a  +  b);  
}  
 
函数调用处反汇编代码:  
             
           ;  Add(1,2);  
push                                    2                                                                                    ;参数从右到左入栈,先压入2  
push                1                                                                                    ;压入1  
call                            @ILT+10(Add)  (0040100f)                        ;调用函数实现  
 
函数实现部分的反汇编代码:  
 
;int  __stdcall  Add(int  a,  int  b)  
push                                    ebp  
mov                          ebp,esp  
sub                              esp,40h  
push                            ebx  
push                            esi  
push                            edi  
lea                              edi,[ebp-40h]  
mov                          ecx,10h  
mov                            eax,0CCCCCCCCh  
rep  stos                    dword  ptr  [edi]  
;return  (a  +  b);  
mov                          eax,dword  ptr  [ebp+8]  
add                              eax,dword  ptr  [ebp+0Ch]  
pop                          edi  
pop                            esi  
pop                          ebx  
mov                        esp,ebp  
pop                            ebp  
ret                              8                                  ;清栈  
3、__fastcall  
__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall。  
 
依旧是相类似的例子,此时函数调用约定为__fastcall,函数参数个数增加2个:  
 
int  __fastcall  Add(int  a,  double  b,  int  c,  int  d)  
{  
return  (a  +  b  +  c  +  d);  
}  
 
函数调用部分的汇编代码:  
 
;Add(1,  2,  3,  4);  
push                                    4                                                ;后两个参数从右到左入栈,先压入4  
mov                          edx,3                                    ;将int类型的3放入edx  
push                          40000000h                        ;压入double类型的2  
push                          0  
mov                          ecx,1                                    ;将int类型的1放入ecx  
call                            @ILT+0(Add)  (00401005)                            ;调用函数实现  
 
函数实现部分的反汇编代码:  
                         
;  int  __fastcall  Add(int  a,  double  b,  int  c,  int  d)  
push                                    ebp  
mov                            ebp,esp  
sub                            esp,48h  
push                            ebx  
push                            esi  
push                            edi  
push                            ecx  
lea                              edi,[ebp-48h]  
mov                          ecx,12h  
mov                      eax,0CCCCCCCCh  
rep  stos                    dword  ptr  [edi]  
pop                            ecx  
mov                            dword  ptr  [ebp-8],edx  
mov                            dword  ptr  [ebp-4],ecx  
;return  (a  +  b  +  c  +  d);  
fild                            dword  ptr  [ebp-4]  
fadd                          qword  ptr  [ebp+8]  
fiadd                          dword  ptr  [ebp-8]  
fiadd                          dword  ptr  [ebp+10h]  
call                            __ftol  (004011b8)  
pop                            edi  
pop                            esi  
pop                            ebx  
mov                          esp,ebp  
pop                            ebp  
ret                              0Ch                                                            ;清栈  
 
关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...->C/C++->Code  Generation项选择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd,即__cdecl。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。  
4、thiscall  
thiscall调用约定是C++中的非静态类成员函数的默认调用约定。thiscall只能被编译器使用,没有相应的关键字,因此不能被程序员指定。采用thiscall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,只是另外通过ECX寄存器传送一个额外的参数:this指针。  
 
这次的例子中将定义一个类,并在类中定义一个成员函数,代码如下:  
 
class  CSum  
                 {  
public:  
int  Add(int  a,  int  b)  
{  
return  (a  +  b);  
}  
};  
 
 
---------------------------------------------------------------  
 
2.名称修饰(Name  Decoration)  
C或C++函数在内部(编译和链接)通过修饰名(Decoration  Name)识别。函数的修饰名是编译器在编译函数定义或者原型时生成的字符串。编译器在创建.obj文件时对函数名称进行修饰。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出C++重载函数、构造函数、析构函数,又如在汇编代码里调用C或C++函数等。  
 
在VC++中,函数修饰名由编译类型(C或C++)、函数名、类名、调用约定、返回类型、参数等多种因素共同决定。下面分C编译、C++编译(非类成员函数)和C++类及其成员函数编译三种情况说明:  
1、C编译时函数名称修饰  
当函数使用__cdecl调用约定时,编译器仅在原函数名前加上一个下划线前缀,格式为_functionname。例如:函数int  __cdecl  Add(int  a,  int  b),输出后为:_Add。  
 
当函数使用__stdcall调用约定时,编译器在原函数名前加上一个下划线前缀,后面加上一个@符号和函数参数的字节数,格式为_functionname@number。例如:函数int  __stdcall  Add(int  a,  int  b),输出后为:_Add@8。  
 
当函数是用__fastcall调用约定时,编译器在原函数名前加上一个@符号,后面是加一个@符号和函数参数的字节数,格式为@functionname@number。例如:函数int  __fastcall  Add(int  a,  int  b),输出后为:@Add@8。  
 
       以上改变均不会改变原函数名中的字符大小写。  
2、C++编译时函数(非类成员函数)名称修饰  
当函数使用__cdecl调用约定时,编译器进行以下工作:  
 
1.以?标识函数名的开始,后跟函数名;  
2.函数名后面以@@YA标识开始,后跟返回值和参数表;  
3.当函数的返回值或者参数与C++类无关的时候,返回值和参数表以下列代号表示:  
           B:const  
D:char  
E:unsigned  char  
F:short  
G:unsigned  short  
H:int  
I:unsigned  int  
J:long  
K:unsigned  long  
M:float  
N:double  
_N:bool  
PA:指针(*,后面的代号表明指针类型,如果相同类型的指针连续出现,以0  
代替,一个0代表一次重复)  
           PB:const指针  
           AA:引用(&)  
           AB:const引用  
U:类或结构体  
V:Interface(接口)  
W4:enum  
X:void  
4、@@YA标识之后紧跟的是该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前。当函数的返回值或者参数与C++类无关的时候,其处理符合本条规则,否则按照5、6规则处理;  
5、当函数返回值为某个类或带有const性质的类的时候,返回值的命名为:?A/?B+V+类名+@@(不带加号)。当函数返回值为某个类的指针/引用或者带有const性质的类的指针/引用的时候,返回值的命名为:PA/AA或者PB/AB+V+类名+@@(不带加号);  
6、函数参数为某个类的时候,并且该参数所使用的类曾经出现过的话(也就是与函数返回值所使用的类相同或者与前一个参数使用的类相同),则该参数类型格式为:V+1+@(不带加号)。如果该参数所使用的类没有出现过的话,则该参数类型格式为:V+类名+@@(不带加号)。函数参数为某个类的指针/引用或者带有const性质指针/引用的时候,则该参数类型格式是在上述格式的基础上在V前面加上代表指针/引用类型或者带有const性质指针/引用类型的标识符(PA/AA或PB/AB);  
7、参数表后以@Z标识整个名字的结束,如果该函数无参数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值