DLL的开发与调用(一)——创建导出函数的Win32 DLL
http://www.cnblogs.com/Pickuper/articles/2053745.htmlVisual 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/8710811dll(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.html1、动态链接库英文为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。
========