扩展DLL:扩展的DLL中所包含的MFC库函数,只能被MFC应用程序所调用
win32创建导出一个函数
一、创建动态链接库(win32下的DLL)
1、创建一个Win32 项目,输入文件名称,应用程序类型为DLL,【附加选择】选择空项目。
2、在文件中新建一个C++文件,声明导出函数 extern "C" _declspec(dllexport) int add(int a, int b);(win32创建导出一个函数);在源文件的前面不用加 _declspec(dllexport)。
3、使用Build生成动态链接库,Deubg中会出现dll 和lib文件;应该需要查看一下dll中是否有导出函数。
解决名字改编问题
C++编译器在生成DLL时,为了支持函数的重载等功能,会对导出的函数进行名字改编,如int add(int, int )会改写成_add_int_int,并且不同编译器改编的名字不同。如果利用不同的编译器分别建立DLL和调用DLL时,后者在调用时可能会发生问题。比如我们用C++创建一个DLL,用C语言编写的程序访问DLL中的函数就会出现问题,会找不到所需的DLL导出函数。我们希望动态链接库文件在编译时,导出函数的名字不要发生改编,这时需要在定义导出函数时添加限定符extern ”C”,一定要大写。
注:利用限定符extern "C"可以解决C++与C语言之间相互调用时函数命名的问题。但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。另外,如果函数的调用约定发生了变化,即使在声明这些导出函数时使用了extern "C"限定符,他们的名字仍然会发生改编。
我们可以通过一个称为模块定义文件(DEF)的方式来解决名字改编的问题。
VC++支持两种语言:即C/C++,这也是造成DLL导出函数差异的根源
我们用VS2008新建个DLL工程,工程名为"TestDLL"
把默认的源文件后缀 .CPP改为.C(C文件)
输入测试代码如下:
01 int _stdcall MyFunction(int iVariant)
02 {
03 return 0;
04 }
为了导出上面这个函数,我们有以下几个方法:
1、使用传统的模块定义文件(.def)
新建一个 后缀为.def的文本文件(这里建一个TestDll.Def),文件内容为:
LIBRARY TestDll
EXPORTS
MyFunction
在在本项目的 Link--输入--模块定义文件选项中指定输入依赖文件:/DEF:"TestDll.Def"
2. Visual C++ 提供的方便方法
在01行的int 前加入 __declspec(dllexport) 关键字
通过以上两种方法,我们就可以导出MyFunction函数。
我们用Dependency查看导出的函数:
第一种方法导出的函数为:
MyFunction
第二种方法导出的函数为:
_MyFunction@4
注:__stdcall会使导出函数名字前面加一个下划线,后面加一个@再加上参数的字节数,比如_MyFunction@4的参数(int iVariant)就是4个字节。__fastcall与 __stdcall类似,不过前面没有下划线,而是一个@,比如@MyFunction@4。__cdecl则是始函数名。
小结:如果要导出C文件中的函数,并且不让编译器改动函数名,用def文件导出函数。
下面我们来看一下C++文件
我们用VS2008新建个DLL工程,工程名为"TestDLL"
默认的源文件后缀为 .CPP (即C++文件)。
输入测试代码如下:
01 int _stdcall MyFunction(int iVariant)
02 {
03 return 0;
04 }
为了导出上面这个函数,我们有以下几个方法:
3. 使用传统的模块定义文件 (.def)
新建一个 后缀为.def的文本文件(这里建一个TestDll.Def),文件内容为:
LIBRARY TestDll
EXPORTS
MyFunction
在 Link 时指定输入依赖文件:/DEF:"TestDll.Def"
4. Visual C++ 提供的方便方法
在01行的int 前加入 __declspec(dllexport) 关键字
通过以上两种方法,我们就可以导出MyFunction函数。
我们用Dependency查看导出的函数:
第一种方法导出的函数为:
MyFunction
第二种方法导出的函数为:
?MyFunction@@YGHH@Z
可以看到 第二种方法得到的 导出函数名 并不是我们想要的,如果在exe中用显示方法(LoadLibrary、GetProcAddress)调用 MyFunction 肯定会失败。但是用引入库(*.LIB)的方式调用,则编译器自动处理转换函数名,所以总是没有问题。
小结:如果要导出C++文件中的函数,并且不让编译器改动函数名,用def文件导出函数。
二、调用动态链接库
使用隐式方式调用DLL(静态加载)
1、建立一个MFC应用程序用于测试dll,然后将lib文件和dll文件放在项目工作目录下来。
2、在源文件顶部加入下述代码:
#pragma comment(lib,"A1.lib") //告诉编译器DL相对应的lib文件所在路径和文件名
extern"C" _declspec(dllimport) int add(int a,int b);
3、运行程序,可以连接DLL,成功!!!
使用显示方式调用DLL(动态加载)
typedef int (* lpAddFun)(int ,int); //定义一个与Add_new函数接受参数类型和返回值均相同的函数指针类型
HINSTANCE hDll;//句柄
lpAddFun addFun;//函数指针
hDll = LoadLibrary("dllTest.dll");//动态加载DLL模块句柄
if(hDll)
{
addFun = (lpAddFun) GetProcAddress(hDll,"Add_new");//得到所加载DLL模块中函数的地址
if(addFun)
{
int result = addFun(2,3);
printf("%d",result); } FreeLibrary(hDll);//释放已经加载的DLL模块
}
loadlibrary函数的作用是将指定的可执行模块映射到调用进程的地址空间,它不仅能够加载DLL,还能加载exe文件。使用动态加载DLL时,客户端程序不再需要包含导出函数声明的头文件和引入库文件,只需要.dll文件即可。
动态加载:
DLL(动态库)导出函数名乱码含义
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"。
如果要用DEF文件输出一个"C++"类,则把要输出的数据和成员的修饰名都写入.def模块定义文件
所以... 通过def文件来导出C++类是很麻烦的,并且这个修饰名是不可避免的