c语言dll库详解,动态库DLL详解

1、dllexport 和 dllimport之间的区别

dllexport:是在动态库内部,申明将类或函数进行导出。

dllimpor:是在调用函数内部,将DLL中的类或者函数进行导入。

2、DLL的访问路径

1) 所在目录——当前进程的可执行模块所在的目录,即应用程序的可执行文件(*.exe)所在目录。

2)当前目录——进程的当前目录。

3) 系统目录——Windows操作系统安装目录的系统子目录,如“C:\windows\system32”。

4) Windows目录——Windowsc操作系统安装目录,如“C:\Windows\”。

5) 搜索目录——PATH环境变量中所包含的自动搜索路径目录。

3、MFC DLL类型

规则DLL: 可以被Windows所有的应用程序所使用

共享DLL——DLL不包含MFC库函数,需要另外安装MFC DLL

静态DLL——DLL中包含MFC库函数,不需要另外安装MFC DLL

扩展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文件即可。

动态加载:

1:灵活,可以在需要的时候用LoadLibrary进行加载,在不需要的时候用FreeLibrary进行卸载,这样可以不必占用内存。

2:可以在没有dll时候发现,而不致程序报错。

3:加载程序中有条件才运行的库。

4:热更新,在不停止程序的前提下进行更新。

5:复杂一些,需要显示获得函数地址。

6:dll没有对应的lib文件,此时只能进行动态加载。

静态加载:

1:简单方便

2:没有dll时,系统报错

3:加载运行很久的库

4:dll必需有相应的lib文件

加载方法:

1、确保有a.dll和a.lib,两个文件都有的话可以用静态加载的方式。

2、在.cpp文件中通过#pragma comment(lib, 'a.lib') 加载lib,并包含相应的头文件,就可以使用dll中的函数了~

所谓'程序库',简单说,就是包含了数据和执行码的文件。其不能单独执行,可以作为其它执行程序的一部分,来完成执行功能。库的存在,可以使得程序模块化,可以加快程序的再编译,可以实现代码重用,可以使得程序便于升级。

程序库可分三类:静态库,共享库和动态加载库。

静态库,是在执行程序运行前就已经加入到执行码中,在物理上成为执行程序的一部分;共享库,是在执行程序启动时加载到执行程序中,可以被多个执行程序共享使用。动态加载库,其实并不是一种真正的库类型,应该是一种库的使用技术,应用程序可以在运行过程中随时加载和使用库。

建议库开发人员创建共享库,比较明显的优势在于库是独立的,便于维护和更新;而静态库的更新比较麻烦,一般不做推荐。然而,它们又各有优点,后面会讲到。在C++编程中,要使用动态加载技术,需要参考文章'C++ dlopen MINI-Howto'。

静态库可以认为是一些目标代码的集合。按照习惯,一般以'.a'做为文件后缀名。使用ar(archiver)命令可以创建静态库。因为共享库有着更大的优势,静态库已经不被经常使用。但静态库使用简单,仍有使用的余地,并会一直存在。

静态库在应用程序生成时,可以不必再编译,节省再编译时间。但在编译器越来越快的今天,这一点似乎已不重要。如果其他开发人员要使用你的代码,而你又不想给其源码,提供静态库是一种选择。从理论上讲,应用程序使用了静态库,要比使用动态加载库速度快1-5%,但由于莫名的原因,实际上可能并非如此。由此看来,除了使用方便外,静态库可能并非一种好的选择。

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++类是很麻烦的,并且这个修饰名是不可避免的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值