win32 DLL 学习总结

来源:互联网 发布:那个淘宝网站的商品是正品 编辑:程序博客网 时间:2024/06/06 06:21

DLL的开发与调用(一)——创建导出函数的Win32 DLL

http://www.cnblogs.com/Pickuper/articles/2053745.html

Visual C++6.0 中可实现的DLL

         Visual C++6.0 支持自动生成Win32 DLL和MFC AppWizard DLL两种,其中Win32 DLL不使用MFC类库,其导出的函数是标准的C接口,能够被非MFC和MFC的应用程序调用,应用范围更广泛。所以下面就介绍Win32 DLL的开发。

创建导出函数的Win32 DLL
1、启动Visual C++6.0,利用AppWizard创建一个“Win32 Dynamic-Link Library”类型的工程,工程名为SayHello。采用默认设置,即创建一个Win32 DLL的空项目。
2、为DLL工程添加头文件SayHello.h和源文件SayHello.cpp。在头文件SayHello.h中,声明DLL的导出函数Say和Sum,分别用来显示"Hello,World!"和求和。声明代码如下:

//SayHello.h
//
/*
extern "C"修饰词的作用是使C++编译器以C语言的方式对这个函数进行处理,以便供其他语言所用。
*/
extern "C" void _declspec(dllexport)Say(char* szWords,int nLen);                           //声明Say导出函数
extern "C" float _declspec(dllexport)Sum(float fNum1,float fNum2);   //声明Sum导出函数
在源文件SayHello.cpp中添加函数Say和Sum的实现代码,代码如下:

//SayHello.cpp
//
#include <string.h>
#include "SayHello.h"

void Say(char* szWords,int nLen)
{
    strcpy(szWords,"Hello,World!");
    strcat(szWords,"\0");
}

float Sum(float fNum1,float fNum2)
{
    return fNum1+fNum2;
}
3、【F7】键编译生成DLL。此时在工程的Debug文件夹下生成实际代码文件SayHello.dll和导入库文件SayHello.lib。
4、从DLL中导出函数有两种方法,一种是使用_declspec(dllexport)关键字,如SayHello.h中所示;一种是添加.def文件(值得注意的是,添加的文件类型是文本文件,且名称应输入SayHello.def),代码如下:

;SayHello.def
;

LIBRARY "SayHello"
DESCRIPTION "导出DLL中的函数"
EXPORTS
    Say  @1
    Sum  @2
5、加载DLL分为静态加载和动态加载。动态加载(运行时动态链接,也叫显示链接)DLL是通过LoadLibrary、GetProcAddress和FreeLibrary这3个API函数进行的。调用如下:

typedef void(*SAY)(char*,int);
SAY Say;
typedef float(*SUM)(float,float);
SUM Sum;
HINSTANCE hdll;
hdll=LoadLibrary("..\\..\\SayHello\\Debug\\SayHello.dll");
if(hdll!=NULL)
{
    //GetProcAddress函数获得获得获得DLL导出函数地址
    Say=(SAY)GetProcAddress(hdll,"Say");
    Sum=(SUM)GetProcAddress(hdll,"Sum");
}
else
{
    AfxMessageBox("无法加载DLL!");
    return;
}
UpdateData(TRUE);
const int Len=20;
char p[Len];
Say(p,Len);
m_strDispHello.Format("%s",p);
m_fResult=Sum(m_fNum1,m_fNum2);
UpdateData(FALSE);
FreeLibrary(hdll);
静态加载(加载时动态链接,也叫隐式链接)DLL是由编译系统完成对DLL的加载和应用程序结束时对DLL的卸载,需要将DLL的引用库文件(.lib)与应用程序进行静态链接。调用如下:
#pragma comment(lib,"SayHello.lib")
extern "C" _declspec(dllimport) void Say(char* szWords,int nLen); 
extern "C" _declspec(dllimport) float Sum(float fNum1,float fNum2);
此时就使用Say和Sum函数了。
========

Win32 动态链接(dll)简单示例

http://blog.csdn.net/weiwenhp/article/details/8710811

dll(dynamic link library)动态链接库相当于是把一些函数或者类啊编译成源码.不过它不可执行.只是当被其他exe或dll调用到时才被加载到内存中.像windows那些API都是放到一些dll文件中.比如kernel32.dll,它包含管理内存,进程,线程的一些函数.User32.dll包含用于执行用户界面任务的函数.

而当我们写代码要用到dll中的函数时,在编译阶段一般只要个lib文件,里面有dll中的函数和类的描述信息,但没有实现代码信息.

DLL的创建
下面来看一个创建dll的简单示例
创建 Win32 Project-->application type选DLL.
project名字就取DllTest.创建好项目后我们会看到自动生成了.dllmain.cpp和DllTest.cpp,前一个文件不用去动它.
我们就在DllTest.cpp文件中添加如下内容

_declspec(dllexport)int multiply(int one , int two)  //返回两数相乘的积{return one*two;}

编译下这个项目.你会在目录下面看到DllTest.dll 和 DllTest.lib 这两文件.等会其他项目中要用它俩.

DLL的使用
新建一个简单的Win32 console application 项目.把上面的DllTest.dll和DllTest.lib两文件拷到项目目录下.再添加如下代码

#include <iostream>using namespace std;#pragma comment(lib, "./DllTest.lib")int multiply( int one , int two) ; //函数声明,函数定义最终是去调用DllTest.dll中的代码了.                                          //另外最好是写成这样_declspec(dllimport)  int multiply( int one , int two)int main(){  int ret = multiply( 4,5);  cout<<ret;  //20 return 0;}

当然了,如果你嫌#pragma comment(lib, "./DllTest.lib")这样写麻烦,也不不写,而是在项目的property page -->Linker -->Input -->Additional Dependencies里面敲入DllTest.lib

Dll创建示例2(带类的dll)
上面是比较简单的再来看个复杂点的.

跟前面一样还是一样先创建一个win32 dll项目名为DllTest.然后添加class Arwen.

/////Arwen.h中内容/////////////////
#pragma once
#include <iostream>
#define DLL_API _declspec(dllimport)
class DLL_API Arwen{
public:
int age;
void Fun();
};
 
//////////Arwen.cpp中内容/////////////////
#include "StdAfx.h"
#include "Arwen.h"
#define DLL_API _declspec(dllexport)
void Arwen::Fun()
{
std::cout<<"my age is "<<age;


使用DLL

新建一个win32 console application ,把DllTest.dll和Dll.lib拷贝过去.另外把头文件Arwen.h也拷过去.

#include "Arwen.h"
#pragma comment(lib, "./DllTest.lib")
int main()
{
Arwen an;
an.age = 25;
an.Fun();
 return 0;
}

 动态加载DLL
前面讲的是静态加载DLL,现在瞧下怎么动态加载.

#include <windows.h>

typedef int( *pFun) (int a, int b);  //定义一个函数指针类型

void main()

{

  HINSTANCE hInt = LoalLibrary( _T( "../debug/DllTest.dll") );  //动态加载

  pFun mulitplyFun = (pFun) GetProcAddress( hInt , (LPCSTR) MAKEINTRESOURCE(2)); //函数序列号是通过工具dumpbin查到的



工具dumpbin的使用.

1.先找到vsvar32.bat文件,目录是在: 安装目录\VC\bin\vcvars32.bat.然后在cmd里面执行它

2.先切换到dll文件所在目录,假如这里是DllTest.dll,然后执行命令dumpbin - exports DllTest.dll

3.得到所以导出函数信息,其中ordianl那一列指函数序列号 , name那一列则是编译之后函数的名字,比之前的函数名多了些前缀后缀.
========

win32 dll简单例子


http://blog.csdn.net/rem2002/article/details/1744978
一。显示链接dll

编写dll

FILE->Visal C++项目: Win32项目->应用程序设置: 选择 DLL(D) 选项 并勾选 导出符号,将 h,cpp文件修改如下:

MyDll.h

   //Mydll.h
   #ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

extern "C" MYDLL_API int fun(int mode); //自己写的 extern "C" 不可少
extern "C" MYDLL_API int fun2(int a,int b); 

MyDll.cpp

#include "stdafx.h"
#include "MyDll.h"
BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

MYDLL_API int fun(int mode) //自己写的
{
    
    return mode*mode;
}

MYDLL_API int fun2(int a,int b) //自己写的
{
    int d = (a>b?(a-b):(b-a));
    return d;
}
编写测试程序:testDll

采用win32控制台生成的执行程序进行测试 (注: 属性->C/C++:预处理器->预处理器定义  加宏:MYDLL_EXPORTS)
因为MyDll.h中定义了宏 #define MYDLL_API __declspec(dllexport)

#include <iostream>
#include <Windows.h>

typedef int (*PFNMYDLL)(int);//声明函数原型
typedef int (*HHH)(int,int); 

using namespace std;

void main()
{
    HMODULE hModule = ::LoadLibrary("MyDll.dll");//加载DLL库

    PFNMYDLL newfun = (PFNMYDLL)::GetProcAddress(hModule,"fun");//取得fun函数的地址

    int i = newfun(4);
    printf("The result is %d ",i);

    HHH newfun2 = (HHH)::GetProcAddress(hModule,"fun2");//取得fun函数的地址

    int d = newfun2(6,4);
    printf("the 6,4 is: %d ",d);


    int c = newfun2(7,19);
    printf("the 7,19 is:%d ",c);


    ::FreeLibrary(hModule);
}
 
二.隐式链接


[cpp] view plain copy
#ifdef MYDLL_EXPORTS  
#define MYDLL_API __declspec(dllexport)  
#else  
#define MYDLL_API __declspec(dllimport)  
#endif  
  
class MYDLL_API MyDll  
{  
public:  
    MyDll(void);  
    ~MyDll(void);  
    void setValue(int value);  
    int getValue();  
  
private:  
    int m_nValue;  
};  
 
 使用 dll 代码


[cpp] view plain copy
#include <stdlib.h>  
#include <stdio.h>  
#include <windows.h>  
#include "MyDll.h"  
#pragma comment(lib,"MyDll.lib")  
  
void main()  
{  
  
 MyDll myDll;  
 myDll.setValue(20);  
 int i = myDll.getValue();  
  
 printf("%d",i);  
}  
 
 以下为转贴


三。导出并显式链接一组C++成员函数


这里有两个问题。第一是C++成员函数名是经过修饰的(即使指定extern "C"标记也是这样);第二是C++不允许将指向成员函数的指针转换成其它类型。这两个问题限制了C++类的显式链接。下面介绍两种方法来解决这个问题:①用虚函数表的方法,这也是COM使用的方法;②用GetProcAddress直接调用。


1.虚函数表方法:


使用到的 dll 头文件 MyDll.h


[cpp] view plain copy
#ifdef MYDLL_EXPORTS  
#define MYDLL_API __declspec(dllexport)  
#else  
#define MYDLL_API __declspec(dllimport)  
#endif  
  
class MYDLL_API MyDll  
{  
public:  
    MyDll(void);  
    MyDll(int i);  
    virtual ~MyDll(void);  
    virtual void setValue(int value);  
    virtual int getValue();  
  
private:  
    int m_nValue;  
};  
 
使用 dll 的代码


[cpp] view plain copy
#include <stdlib.h>  
#include <stdio.h>  
#include <string>  
#include <windows.h>  
  
#include "MyDll.h"  
  
typedef MyDll* (*pCreateA)();  
typedef MyDll* (*pCreateA1)(int);  
  
void main()  
{  
    HMODULE hModule;  
      
    hModule = ::LoadLibrary("MyDll");//加载DLL库  
  
    pCreateA pCreate = (pCreateA)GetProcAddress(hModule, TEXT("CreateMyDll"));  
  
    MyDll* a = (pCreate)();  
    a->setValue(20);  
    printf("one:%d/n",a->getValue());    
  
    pCreateA1 pCreate1 = (pCreateA1)GetProcAddress(hModule, TEXT("CreateMyDll1"));  
  
    MyDll* b = (pCreate1)(50);  
    printf("two:%d/n",b->getValue());  
  
    ::FreeLibrary(hModule);  
  
    getchar();  
    return;  
}   


dll 项目


MyDll.h 即使用到的 dll 头文件


MyDll.cpp


[cpp] view plain copy
#include "MyDll.h"  
  
MyDll::MyDll(void)  
:m_nValue(0)  
{  
}  
  
MyDll::MyDll(int i)  
{  
    m_nValue = i;  
}  
  
MyDll::~MyDll(void)  
{  
    m_nValue = 0;  
}  
  
void MyDll::setValue(int value)  
{  
    m_nValue = value;  
}  
  
int MyDll::getValue()  
{  
    return m_nValue;  
}  
Inst.cpp


[c-sharp] view plain copy
#include "MyDll.h"  
  
extern "C" __declspec(dllexport) MyDll* CreateMyDll()  
{  
    return new MyDll();  
}  
extern "C" __declspec(dllexport) MyDll* CreateMyDll1(int i)  
{  
    return new MyDll(i);  
}  
 
这个方法的使用得用户可以很容易地为你的程序制作插件。它的缺点是创建对象的内存必须在dll中分配.


直接使用GetProcAddress进行显式链接


这个方法的关键在于将GetProcAddress函数返回的FARPROC类型转化为C++中指向成员函数的指针。幸运的是,通过C++的unio和模板机制,这个目标可以很容易地实现。我们要做的只是定义如下的函数:


template<class Src , class Dest>


Dest force_cast(Src src){


 union{


  Dest d;


  Src s;


 } convertor;


convertor.s = Src;


 return convertor.d;


}


上面的函数允许我们在任何类型间进行转换,比reinterpret_cast更加有效。例如,我们定义一种指针类型:


typedef void (A::*PSetNum)(int);


我们可以将FARPROC类型的指针fp转化成PSetNum:


PSetNum psn = force_cast<PSetNum>(fp);


找到了将FARPROC转化成成员函数指针的方法以后,我们要考虑如何将C++成员函数以更加友好的名字导出。这可以通过一个.def文件来实现。


第一步是找到待导出函数经过修饰的函数名,这可以通过查看map file或者汇编代码来实现。然后在.def文件中指定导出函数的新的函数名:


EXPORTS


 ConstructorOfA1 = ??0A@@QAE@XZ        PRIVATE


 ConstructorOfA2 = ??0A@@QAE@H@Z       PRIVATE


 SetValueOfA       = ?SetNum@A@@UAEXH@Z  PRIVATE


 GetValueOfA       = ?GetNum@A@@UAEHXZ   PRIVATE  


下面是调用这些成员函数的方法:


typedef void (A::*PfnConstructorOfA1)();


typedef void (A::*PfnConstructorOfA2)(int);


typedef void (A::*PfnDestructorOfA)();


typedef void (A::*PfnSetNumOfA)(int);


typedef int  (A::*PfnGetNumOfA)();


A* a1 = (A*)_alloca(sizeof(A));


PfnConstructorOfA1 pfnConsA =


     force_cast<PfnConstructorOfA1>(GetProcAddress(hMod, TEXT("ConstructorOfA1")));


(a1->*pfnConsA)();


PfnSetNumOfA pfnSetNumA =


          force_cast<PfnSetNumOfA>(GetProcAddress(hMod, TEXT("SetNumOfA")));


(a1->*pfnSetNumA)(1);     


PfnGetNumOfA pfnGetNumA =


          force_cast<PfnGetNumOfA>(GetProcAddress(hMod, TEXT("GetNumOfA")));


_tprintf(TEXT("Value of m_nNum in a is %d/n"),(a1->*pfnGetNumA)());


 注意这里使用了alloca从栈中分配内存,你也可以使用malloc从堆中分配内存。但是不能使用C++的new操作符,因为能过new来分配内存编译器会自动插入对constructor的调用。但我们要的是显式链接,所以必须避免这种情况。随之产生的结果是我们只能显式地去调用构造函数和析构函数。
========

动态链接库-Win32 DLL的创建和使用

http://www.cnblogs.com/because/archive/2012/02/18/2357109.html
摘要
       利用Visual C++6.0创建和使用DLL(Dynamic-Link Library).


概述
   在实际编程时,我们可以把完成某种功能的函数放在一个动态链接库中,然后给其他程序调用。


   WinAPI中所有的函数都包含在3个最重要的DLL中。


Kernel32.dll
        它包含那些用于管理内存、进程和线程的函数,例如CreateThread函数;


User32.dll
        它包含那些用于执行用户界面任务的函数,例如CreateWindow函数;


GDI32.dll   
        它包含那些用于画图和显示文本的函数。


用法 
 新建一个Win32 Console Application工程:


以MathLib为工程名称新建Win32 Dynamic-Link Library的空工程,


添加C++ Source File 源文件到工程中,命名为MathLib.c


添加以下代码:


复制代码
 1 #define MATH_API _declspec(dllexport)
 2 #include "MathLib.h"
 3 int add(int a,int b)
 4 {
 5     return a+b;
 6 }
 7 int subtract(int a,int b)
 8 {
 9     return a-b;
10 }
复制代码
添加C/C++ Header File 头文件到工程中,命名为MathLib.h


复制代码
1 #ifdef MATH_API
2 #else
3     #define   MATH_API _declspec(dllimport)
4 #endif
5 MATH_API int add(int a,int b);
6 MATH_API int subtract(int a,int b);
复制代码
编译后生成MathLib.dll和MathLib.lib两个动态链接库文件。


测试
隐式调用
 
新建MFC AppWizard[exe]可执行工程DllTest,用于测试刚才新建动态链接库MathLib的功能。


复制MathLib.dll,MathLib.lib,MathLib.h到当前工程,


在DllTestDlg.cpp中添加头文件引用:


#include "MathLib.h"
 
添加MathLib.h头文件至工程,


在Project->Setting->Link->object/library modules:添加MathLib.lib


添加一个按钮Add到Dialogue中,在Add按钮的响应函数中添加以下代码:


1 void CDllTestDlg::OnBtnMath() 
2 {
3     // TODO: Add your control notification handler code here
4     CString res;
5     res.Format("10+2=%d",add(10,2));
6     MessageBox(res);
7 }
复制代码
编译运行程序,


成功运行MathLib中的加法功能。


工程文件:
========

windows程序设计之调用动态链接库DLL DLL的调用约定

http://www.cnblogs.com/llz5023/archive/2012/12/30/2839682.html
1、动态链接库英文为DLL,是Dynamic Link Library 的缩写形式,DLL是一个包含可由多个程序同时使用的代码和数据的库,DLL不是可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个DLL 副本的内容。DLL 是一个包含可由多个程序同时使用的代码和数据的库。


2、操作实例,C语言咧调用系统的kernel32.dll中的GlobalMemoryStatusEx函数


           typedef   void(WINAPI*   FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS);//声明函数指针模型
            HMODULE   hModule;//Dll句柄
            FunctionGlobalMemoryStatusEx   GlobalMemoryStatusEx;//函数指针模型声明函数变量
            MEMORYSTATUS status;
            status.dwLength = sizeof(status);
            //GlobalMemoryStatus(&status);
            hModule   =   LoadLibrary("kernel32.dll");//调试时hModule为0x10000000,载入动态链接库dll,返回它的句柄
            if(NULL==hModule)//判断载入是否成功
            {
                //error.
                MessageBox(hwndDlg,TEXT("载入指定的动态链接库dll失败"),TEXT("error"),MB_OK);
                return 0;
            }
            //调用GetProcAddress API根据dll句柄,和dll的声明的函数名获取函数指针
            GlobalMemoryStatusEx   =(FunctionGlobalMemoryStatusEx)GetProcAddress(hModule,"GlobalMemoryStatusEx");
            if(NULL==GlobalMemoryStatusEx)//判断获取是否成功
            {
                //error
                MessageBox(hwndDlg,TEXT("error2"),TEXT("error2"),MB_OK);
                return 0;
            }
            //获取成功,然后可以直接用函数指针来调用函数,函数名就是函数指针,C语言应该都懂
            GlobalMemoryStatusEx(&status);//调用函数
            FreeLibrary(hModule);//用完了要释放dll
3、第二步已经说名了怎么动态调用DLL,我们还要注意一点,DLL的调用约定
dll有__cdecl __stdcall WINAPI 等不同的调用约定,也就是参数的压栈顺序等,暂时不用关心,只要保证调用的时候和dll中的调用约定一样就可以。
//否则会报错:The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention. 
如上面的列子,如果我把typedef   void(WINAPI*   FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS);//声明函数指针模型,改成:
typedef   void(__cdecl*   FunctionGlobalMemoryStatusEx)(LPMEMORYSTATUS););//声明函数指针模型
运行时会报错误:
image 
由此,在声明函数原型指针时要注意写对调用约定,如果不知道,那么换着调试看那个对。
4、说明一下调用约定(Calling Convention)相关的(其他地方拷贝来的)


调用约定用来处理决定函数参数传送时入栈和出栈的顺序(由调用者还是被调用者把参数弹出栈),以及编译器用来识别函数名称的名称修饰约定等问题。在Microsoft VC++ 6.0中定义了下面几种调用约定,我们将结合汇编语言来一一分析它们:


4.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 ;由函数调用清栈


4.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 ;清栈


4.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。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。
========

DLL中导出函数(函数名及其调用约定)

http://www.cnblogs.com/leijiangtao/p/4797585.html
最近简单研究了一下dll的导出函数,整理了一下


1.导出函数名的问题


dll导出函数最简单的语法是


void__declspec(dllexport) fun();


由于它默认的是c++的调用约定cdecl,因此导出的函数就变成了


?fun@@YAXXZ


如果直接取函数名fun,就会找不到函数,有两种方法可以解决这个问题:用C的编译方式和def文件


①      用C的编译方式


在导出函数前声明extern “C”,即:


extern “C” void__declspec(dllexport) fun();


加入extern “C”是告诉编译器,用C的编译方式生成文件,不需要加入参数作为修饰


②      Def文件


在project中建立一个def文件,写入


LIBRARY   "testDLL"// testDLL是project的名字


EXPORTS            //输出


  fun              //函数名(也可以带序号的输出函数名fun@1)


extern “C” void__declspec(dllexport) 和在def文件中导出函数的作用是一样的,因此没必要都写在工程中。


Ps,如果导出的函数名带一些修饰,如:?fun@@YAXXZ,用GetProcAddress()函数直接调用“?fun@@YAXXZ”也是可以找到函数的。


2. 修饰函数的关键字


stdcall cdecl fastcall thiscall naked call


这些调用约定决定了:


?         参数传递次序


?         调用堆栈由谁(调用函数或被调用函数)清理


?         导出函数名


导出函数的调用约定和使用这个函数时声明的调用约定必须一致,否则程序会崩溃。


在C和C++中默认的调用约定是__cdecl,上面函数完整的修饰就是:


void__declspec(dllexport) __cdeclfun();


但是windows系统用的回调函数一般都是_stdcall。


下面是各个调用约定详细的解释:


_stdcall


是Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式, 自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。


int f(void *p) -->> _f@4(在外部汇编语言里可以用这个名字引用这个函数) 


__cdecl


C调用约定(即用__cdecl关键字说明)(The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。


另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。


_fastcall


调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),


在函数名修饰约定方面,它和前两者均不同。__fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。


thiscall


仅仅应用于“C++”成员函数。this指针存放于CX/ECX寄存器中,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。


naked call


当采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。


(这些代码称作 prolog and epilog code,一般,ebp,esp的保存是必须的). 


但是naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。 


另外,关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。
========
0 0