FreeLibrary(hinstance);
return0;
}
下面我们来逐一分析。
首先,语句typedefint(*lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。
随后,在main函数中定义了lpAddFun的实例addFun;
其次,在函数main中定义了一个DLLHINSTANCE句柄实例hDll,通过Win32Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;
再次,在函数main中通过Win32Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。
经由函数指针addFun进行了对DLL中add函数的调用;
最后,应用工程使用完DLL后,在函数main中通过Win32Api函数FreeLibrary释放了已经加载的DLL模块。
通过这个简单的例子,我们获知DLL定义和调用的一般概念:
(1)DLL中需以某种特定的方式声明导出函数(或变量、类);
(2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。
2)静态连接:
代码如下:
#include
usingnamespacestd;
#pragmacomment(lib,"Testlib.lib")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息
extern"C"__declspec(dllimport)add(intx,inty);//声明导入函数
intmain()
{
intresult=add(2,3);
cout<< result<< endl;
return0;
}
注意:
(1)、在编写dll文件时,必须以某种方式声明导出函数(dll文件内有导出函数和内部函数两种,内部函数供dll内部调用),可以用自定义模块文件(.def文件)来声明导出函数,也可以使用_declspec(dllexport)前缀来声明导出函数。
PS:
.def文件除了声明导出函数之外还可以消除函数修饰符的影响,并产生动态链接库导入库(.lib文件)
(2)、在显示调用dll时,只需要.dll文件即可,而且只能通过函数指针来调用dll中的导出函数。
在隐式调用dll时,要将相应的.lib文件加入到工程中,并且在调用dll的导出函数之前,必须对使用的函数进行声明(或者包含进相应的dll文件的头文件也可)。
(3)、.def文件只在生成dll的过程中起作用,在应用程序调用dll时,不起作用。
∙DLL的调用方法
1.动态链接库(DynamicLinkLibrary),简称DLL。
DLL是一个包含可由多个程序同时使用的代码和数据的库。
它允许程序共享执行特殊任务所必需的代码和其他资源,一般来说,DLL是一种磁盘文件,以.dll、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL。
它由全局数据、服务函数和资源组成,在运行时被系统加载到调用进程的虚拟空间中,成为调用进程的一部分。
DLL的调用可以分为两种:
一种是隐式调用,一种是显示调用这里简单分享DLL的两种调用方法。
隐式的调用
这种调用方式需要把产生动态连接库时产生的.LIB文件加入到应用程序的工程中,在使用DLL中的函数时,只须声明一下后就可以直接通过函数名调用DLL的输出函数,调用方法和程序内部其他的函数是一样的。
隐式调用不需要调用LoadLibrary()和FreeLibrary()。
程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。
该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是并不含有实际的代码。
LIB文件作为DLL的替代文件被编译到应用程序项目中。
当程序员通过隐式调用方式编译生成应用程序时,应用程序中的调用函数与LIB文件中导出符号相匹配,这些符号或标识号被写入到生成的EXE文件中。
LIB文件中也包含了对应的DLL文件名(但不是完全的路径名),链接程序也将其存储在EXE文件内部。
当应用程序运行过程中需要加载DLL文件时,Windows根据这些信息发现并加载DLL,然后通过符号名或标识号实现对DLL函数的动态链接。
所有被应用程序调用的DLL文件都会在应用程序EXE文件加载时被加载在到内存中。
显式调用
这种调用方式是指在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态连接库调进来,并指定DLL的路径作为参数。
LoadLibary返回HINSTANCE参数,应用程序在调用GetProcAddress函数时使用这一参数。
当完成对动态链接库的导入以后,再使用GetProcAddress()获取想要引入的函数,该函数将符号名或标识号转换为DLL内部的地址,之后就可以象使用本应用程序自定义的函数一样来调用此引入函数了。
在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态连接库。
DLL的优点
简单的说,dll有以下几个优点:
1)节省内存。
同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。
如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)。
2)不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。
事实上,很多软件都是以这种方式升级的。
例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。
3)Dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。
这一点上DLL还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。
显示/隐式加载dll例子:
首先生成dll文件
项目属性,配置属性,常规,配置类型,动态库(dll)
dll.h
intadd(intleft,intright);
dll.cpp
#include"dll.h"
intadd(intleft,intright)
{
returnleft+right;
}
然后会在相应工程目录debug目录下找到dll和lib文件
----------------------------------------------------------------
调用dll
把dll.hXXX.dllXXX.lib文件拷贝到新工程目录下
加载dll.h到工程里,下面开始使用dll
import.cpp
#include"dll.h"
//显示加载无需lib文件
HINSTANCEhinstance=LoadLibrary("XXX.dll");
typedefint(*_add)(intleft,intright);
_addaddofdll=(_add)GetProAddress(hinstance,"add");
//隐式加载需要lib一起只用
#pramacomment(lib,"XXX.lib");
下面可以正常使用add或者addofdll两个函数了
-----------------------------------------------------------
当函数功能比较小的时候,其实可以不用dll,直接用静态连接库lib,虽然它可以使得最后的exe文件变大
项目属性,配置属性,常规,配置类型,静态库(lib)
定义都和一样,使用的时候直接根据XXX.h文件找到你想使用的函数,然后#pramacomment(lib,"XXX.lib");后便可使用。
动态库的创建:
1在VC里创建一个Win32项目,比如项目名称叫MyTestDLL,选择DLL和空项目,确定,一个空的DLL工程便创建好;
2添加头文件和源文件以及def文件,之所以使用def文件,是因为对于C++里面的函数,编译器会生成原函数名不同的名字,使用def文件可以直接避免这个问题,也不需要定义__declspec(dllexport)和__declspec(dllimport)这些修饰符。
定义要在DLL里包含的函数,比如:
intAdd(inta,intb)
{
returna+b;
}
在def文件里,添加要导出的函数名,如下格式:
然后编译工程,就会生成MyTestDLL.dll和MyTestDLL.lib这两个文件。
对于动态库的显示调用,在编译时,直接使用一个dll文件,然后在程序中LoadLibrary即可。
对于隐式调用,在编译时,需要同时有头文件、dll文件和lib文件,并且也需要和调用静态库一样,使用预处理指令
#pragmacomment(lib,"libname")
静态库的创建很简单,在调用工程中,编译时,只需要头文件和lib文件,也需要上面的预处理指令,注意,“libname”可以带也可以不带“.lib”后缀。
对于动态库,调用工程不管编译时采用的是隐式还是显示,运行时都需要dll文件;对于静态库,调用工程则不需要在运行期引用静态库文件。
动态库和静态库和运行时库和引入库的区别
1。
运行时库:
Unix中一个典型的运行时库例子就是libc,它包含标准的C函数,如,print(),exit()等等,用户能创建他们自己的运行库(在Windows中是DLL),而具体的细节依赖编译器和操作系统的。
2。
静态库:
函数和数据被编译进一个二进制文件(通常扩展名为.lib),静态库实际上是在链接时被链接到EXE的,库本身不需要与可执行文件一起发行。
3。
动态库:
用VC++创建的动态库包含两个文件,一个lib文件和一个dll文件,这个lib文件就是引入库,不是静态库,引入库有时也叫输入库或导入库。
注:
windows操作系统下动态库和运行时库的扩展名都是.dll,COM组件的扩展名也是.dll,动态库的引入库和静态库的扩展名都是.lib。
windows下调用动态库的方法:
1。
隐式加载:
即在程序中包含lib文件和.h文件,隐式链接有时称为静态加载或加载时动态链接。
例如:
#include"somedll.h"
#pragmacomment(lib,"somedll.lib")
然后就可以直接调用此dll中的函数,注意运行时仍然需要somedll.dll。
2。
显示加载:
使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函数的原型。
显式链接有时称为动态加载或运行时动态链接。
3。
区别:
如果在进程启动时未找到DLL,操作系统将终止使用隐式链接的进程。
同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。
有关Win32DLL,Unix共享库及普通库的详细库结构信息请参考《链接器与加载器》一书。
MSDN:
1。
确定要使用的链接方法:
有两种类型的链接:
隐式链接和显式链接。
隐式链接
应用程序的代码调用导出DLL函数时发生隐式链接。
当调用可执行文件的源代码被编译或被汇编时,DLL函数调用在对象代码中生成一个外部函数引用。
若要解析此外部引用,应用程序必须与DLL的创建者所提供的导入库(.LIB文件)链接。
导入库仅包含加载DLL的代码和实现DLL函数调用的代码。
在导入库中找到外部函数后,会通知链接器此函数的代码在DLL中。
要解析对DLL的外部引用,链接器只需向可执行文件中添加信息,通知系统在进程启动时应在何处查找DLL代码。
系统启动包含动态链接引用的程序时,它使用程序的可执行文件中的信息定位所需的DLL。
如果系统无法定位DLL,它将终止进程并显示一个对话框来报告错误。
否则,系统将DLL模块映射到进程的地址空间中。
如果任何DLL具有(用于初始化代码和终止代码的)入口点函数,操作系统将调用此函数。
在传递到入口点函数的参数中,有一个指定用以指示DLL正在附带到进程的代码。
如果入口点函数没有返回TRUE,系统将终止进程并报告错误。
最后,系统修改进程的可执行代码以提供DLL函数的起始地址。
与程序代码的其余部分一样,DLL代码在进程启动时映射到进程的地址空间中,且仅当需要时才加载到内存中。
因此,由.def文件用来在Windows的早期版本中控制加载的PRELOAD和LOADONCALL代码属性不再具有任何意义。
显式链接
大部分应用程序使用隐式链接,因为这是最易于使用的链接方法。
但是有时也需要显式链接。
下面是一些使用显式链接的常见原因:
直到运行时,应用程序才知道需要加载的DLL的名称。
例如,应用程序可能需要从配置文件获取DLL的名称和导出函数名。
如果在进程启动时未找到DLL,操作系统将终止使用隐式链接的进程。
同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。
例如,进程可通知用户所发生的错误,并让用户指定DLL的其他路径。
如果使用隐式链接的进程所链接到的DLL中有任何DLL具有失败的DllMain函数,该进程也会被终止。
同样是在此情况下,使用显式链接的进程则不会被终止。
因为Windows在应用程序加载时加载所有的DLL,故隐式链接到许多DLL的应用程序启动起来会比较慢。
为提高启动性能,应用程序可隐式链接到那些加载后立即需要的DLL,并等到在需要时显式链接到其他DLL。
显式链接下不需将应用程序与导入库链接。
如果DLL中的更改导致导出序号更改,使用显式链接的应用程序不需重新链接(假设它们是用函数名而不是序号值调用GetProcAddress),而使用隐式链接的应用程序必须重新链接到新的导入库。
下面是需要注意的显式链接的两个缺点:
如果DLL具有DllMain入口点函数,则操作系统在调用LoadLibrary的线程上下文中调用此函数。
如果由于以前调用了LoadLibrary但没有相应地调用FreeLibrary函数而导致DLL已经附加到进程,则不会调用此入口点函数。
如果DLL使用DllMain函数为进程的每个线程执行初始化,显式链接会造成问题,因为调用LoadLibrary(或AfxLoadLibrary)时存在的线程将不会初始化。
如果DLL将静态作用域数据声明为__declspec(thread),则在显式链接时DLL会导致保护错误。
用LoadLibrary加载DLL后,每当代码引用此数据时DLL就会导致保护错误。
(静态作用域数据既包括全局静态项,也包括局部静态项。
)因此,创建DLL时应避免使用线程本地存储区,或者应(在用户尝试动态加载时)告诉DLL用户潜在的缺陷。
2。
隐式链接:
为隐式链接到DLL,可执行文件必须从DLL的提供程序获取下列各项:
包含导出函数和/或C++类的声明的头文件(.h文件)。
类、函数和数据均应具有__declspec(dllimport),有关更多信息,请参见dllexport,dllimport。
要链接的导入库(.LIBfiles)。
(生成DLL时链接器创建导入库。
)
实际的DLL(.dll文件)。
使用DLL的可执行文件必须包括头文件,此头文件包含每个源文件中的导出函数(或C++类),而这些源文件包含对导出函数的调用。
从编码的角度讲,导出函数的函数调用与任何其他函数调用一样。
若要生成调用可执行文件,必须与导入库链接。
如果使用的是外部生成文件,请指定导入库的文件名,此导入库中列出了要链接到的其他对象(.obj)文件或库。
操作系统在加载调用可执行文件时,必须能够定位DLL文件。
3。
显式链接:
在显式链接下,应用程序必须进行函数调用以在运行时显式加载DLL。
为显式链接到DLL,应用程序必须:
调用LoadLibrary(或相似的函数)以加载DLL和获取模块句柄。
调用GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。
由于应用程序是通过指针调用DLL的函数,编译器不生成外部引用,故无需与导入库链接。
使用完DLL后调用FreeLibrary。
typedefUINT(CALLBACK*LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCEhDLL;//HandletoDLL
LPFNDLLFUNC1lpfnDllFunc1;//Functionpointer
DWORDdwParam1;
UINTuParam2,uReturnVal;
hDLL=LoadLibrary("MyDLL");
if(hDLL!
=NULL)
{
lpfnDllFunc1=(LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if(!
lpfnDllFunc1)
{
//handletheerror
FreeLibrary(hDLL);
returnSOME_ERROR_CODE;
}
else
{
//callthefunction
uReturnVal=lpfnDllFunc1(dwParam1,uParam2);
}
}
4。
将可执行文件链接到DLL
可执行文件以下列两种方式之一链接到(或加载)DLL:
隐式链接
显式链接
隐式链接有时称为静态加载或加载时动态链接。
显式链接有时称为动态加载或运行时动态链接。
在隐式链接下,使用DLL的可执行文件链接到该DLL的创建者所提供的导入库(.lib文件)。
使用DLL的可执行文件加载时,操作系统加载此DLL。
客户端可执行文件调用DLL的导出函数,就好像这些函数包含在可执行文件内一样。
在显式链接下,使用DLL的可执行文件必须进行函数调用以显式加载和卸载该DLL,并访问该DLL的导出函数。
客户端可执行文件必须通过函数指针调用导出函数。
可执行文件对两种链接方法可以使用同一个DLL。
另外,由于一个可执行文件可隐式链接到某个DLL,而另一个可显式附加到此DLL,故这些机制不是互斥的。
vc调用dll
调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同。
Windows提供了两种将DLL映像到进程地址空间的方法:
1.隐式的加载时链接
这种方法需要DLL工程经编译