2023年12月24日发(作者:)
1. 什么是lib文件,lib和dll的关系如何
(1)lib是编译时需要的,dll是运行时需要的。
如果要完成源代码的编译,有lib就够了。
如果也使动态连接的程序运行起来,有dll就够了。
在开发和调试阶段,当然最好都有。
(2)一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。
(3)在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,DLL库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行时再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。
2、严重警告:
(1) 用 extern "C" _declspec(dllexport) 只可以导出全局函数,不能导出类的成员函数
(2) 使用extern "C" _declspec(dllexport)输出的函数可以被c语言调用,否则则不可
(3) 注意标准调用约定的问题,输出与调用的函数约定应该一致,如当dll模块的函数输出采用标准调用约定_stdcall,则调用程序的导入函数说明也要用标准约定
(4) 用extern "C" _declspec(dllexport) 和 EXPOTRT导出的函数不改变函数名,可以给c++或c编写的exe调用.
假如没有extern "C",导出的函数名将会改变,只能给c++编写的exe调用
(5)在动态加载动态链接库函数时注意GetProcAddress(hInst,"add")中第二个参数是否为动态链接库导出的函数名,因为在生成动态库时可能会改变动态库导出函数的函数名,加上修饰符
(6)dll初始化全局变量时,全局变量要放在共享数据断,并且初始化每一个变量,在StartHook函数里初始化其值,记得一进函数就初始化
(7)调试时,编译器会自动查找其目录下(不含debug和release目录)的dll文件,所以dll文件应该放在主文件目录下,但生成的应用程序则只会在同一个目录下找dll(不需要lib文件),所以单纯的运行exe,不通过编译器,那就要把dll文件放在与exe相同的目
录下
(8)用#pragma comment(lib,"")导入lib文件,不需要在设置里修改
(9) dll里的指针变量不要用new
DLL 调用方式
DLL(动态连接库),可以分为动态调用于静态调用。下面我分别举一个例子说说。
1)动态调用:
首先:在VC++6.0中创建 Win32 Dynamic-link library工程创建一个动态连接库工程:
在头文件TestDll.h中写下代码
extern "C" int __declspec(dllexport) add(int numa, int numb);//声明导出函数
在源文件中实现改函数:
int __declspec(dllexport) add(int numa, int numb)
{
return numa + numb;
}
然后,创建一个测试程序,TestDemo,创建一个.cpp文件,然后放下代码:
HINSTANCE hinstance;
typedef int (*lpAdd)(int a, int b);
lpAdd lpadd;
int main()
{
hinstance = LoadLibrary("E:");
lpadd = (lpAdd)GetProcAddress(hinstance, "add");
cout << "2 + 3 = " << lpadd(2, 3) << endl;
FreeLibrary(hinstance);
return 0;
}
下面我们来逐一分析。
首先,语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun;
其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;
再次,在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用;
最后,应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。
通过这个简单的例子,我们获知DLL定义和调用的一般概念:
(1)DLL中需以某种特定的方式声明导出函数(或变量、类);
(2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。
2)静态连接:
代码如下:
#include
using namespace std;
#pragma comment(lib,"")
//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息
extern "C" __declspec(dllimport) add(int x,int y);//声明导入函数
int main()
{
int result = add(2,3);
cout << result << endl;
return 0;
}
注意:(1)、在编写dll文件时,必须以某种方式声明导出函数(dll文件内有导出函数和内部函数两种,内部函数供dll内部调用),可以用自定义模块文件(.def文件)来声明导出函数,也可以使用_declspec(dllexport)前缀来声明导出函数。
PS:.def文件除了声明导出函数之外还可以消除函数修饰符的影响,并产生动态链接库导入库(.lib文件)
(2)、在显示调用dll时,只需要.dll文件即可,而且只能通过函数指针来调用dll中的导出函数。
在隐式调用dll时,要将相应的.lib文件加入到工程中,并且在调用dll的导出函数之前,必须对使用的函数进行声明(或者包含进相应的dll文件的头文件也可)。
(3)、.def文件只在生成dll的过程中起作用,在应用程序调用dll时,不起作用。
•
DLL的调用方法
1. 动态链接库(Dynamic Link Library),简称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
int add(int left, int right);
#include"dll.h"
int add(int left, int right)
{
return left+right;
}
然后会在相应工程目录debug目录下找到dll和lib文件
----------------------------------------------------------------
调用dll
把dll.h 文件拷贝到新工程目录下
加载dll.h到工程里,下面开始使用dll
#include"dll.h"
//显示加载无需lib文件
HINSTANCE hinstance = LoadLibrary("");
typedef int (*_add) (int left, int right);
_add addofdll = (_add)GetProAddress(hinstance, "add");
//隐式加载需要lib一起只用
#prama comment(lib, "");
下面可以正常使用add或者addofdll两个函数了
-----------------------------------------------------------
当函数功能比较小的时候,其实可以不用dll,直接用静态连接库lib,虽然它可以使得最后的exe文件变大
项目属性,配置属性,常规,配置类型,静态库(lib)
定义都和一样,使用的时候直接根据XXX.h文件找到你想使用的函数,然后#prama comment(lib, "");后便可使用。
动态库的创建:
1 在VC里创建一个Win32项目,比如项目名称叫MyTestDLL,选择DLL和空项目,确定,一个空的DLL工程便创建好;
2 添加头文件和源文件以及def文件,之所以使用def文件,是因为对于C++里面的函数,编译器会生成原函数名不同的名字,使用def文件可以直接避免这个问题,也不需要定义__declspec(dllexport)和__declspec(dllimport)这些修饰符。
定义要在DLL里包含的函数,比如:
int Add(int a, int b)
{
return a + b;
}
在def文件里,添加要导出的函数名,如下格式:
然后编译工程,就会生成和这两个文件。
对于动态库的显示调用,在编译时,直接使用一个dll文件,然后在程序中LoadLibrary即可。
对于隐式调用,在编译时,需要同时有头文件、dll文件和lib文件,并且也需要和调用静态库一样,使用预处理指令
#pragma comment(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"
#pragma comment( lib, "")
然后就可以直接调用此dll中的函数,注意运行时仍然需要。
2。显示加载:使用loadlibrary,GetProcAddress,FreeLibrary,不需要.h文件和.lib文件,但是要知道函数的原型。显式链接有时称为动态加载或运行时动态链接。
3。区别:如果在进程启动时未找到 DLL,操作系统将终止使用隐式链接
的进程。同样是在此情况下,使用显式链接的进程则不会被终止,并可以尝试从错误中恢复。
有关Win32 DLL,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。
要链接的导入库(.LIB files)。(生成 DLL 时链接器创建导入库。)
实际的 DLL(.dll 文件)。
使用 DLL 的可执行文件必须包括头文件,此头文件包含每个源文件中的导出函数(或 C++ 类),而这些源文件包含对导出函数的调用。从编码的角度讲,导出函数的函数调用与任何其他函数调用一样。
若要生成调用可执行文件,必须与导入库链接。如果使用的是外部生成文件,请指定导入库的文件名,此导入库中列出了要链接到的其他对象
(.obj) 文件或库。
操作系统在加载调用可执行文件时,必须能够定位 DLL 文件。
3。显式链接:
在显式链接下,应用程序必须进行函数调用以在运行时显式加载 DLL。为显式链接到 DLL,应用程序必须:
调用 LoadLibrary(或相似的函数)以加载 DLL 和获取模块句柄。
调用 GetProcAddress,以获取指向应用程序要调用的每个导出函数的函数指针。由于应用程序是通过指针调用 DLL 的函数,编译器不生成外部引用,故无需与导入库链接。
使用完 DLL 后调用 FreeLibrary。
typedef UINT (CALLBACK* LPFNDLLFUNC1)(DWORD,UINT);
...
HINSTANCE hDLL; // Handle to DLL
LPFNDLLFUNC1 lpfnDllFunc1; // Function pointer
DWORD dwParam1;
UINT uParam2, uReturnVal;
hDLL = LoadLibrary("MyDLL");
if (hDLL != NULL)
{
lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(hDLL,
"DLLFunc1");
if (!lpfnDllFunc1)
{
// handle the error
FreeLibrary(hDLL);
return SOME_ERROR_CODE;
}
else
{
// call the function
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工程经编译产生的LIB文件,此文件中包含了DLL允许应用程序调用的所有函数的列表,当链接器发现应用程序调用了LIB文件列出的某个函数,就会在应用程序的可执行文件的文件映像中加入一些信息,这些信息指出了包含这个函数的DLL文件的名字。当这个应用程序运行时,也就是它的可执行文件被操作系统产生映像文件时,系统会查看这个映像文件中关于DLL的信息,然后将这个DLL文件映像到进程的地址空间。
系统通过DLL文件的名称,试图加载这个文件到进程地址空间时,它寻找DLL 文件的路径按照先后顺序如下:
·程序运行时的目录,即可执行文件所在的目录;
·当前程序工作目录
·系统目录:对于Windows95/98来说,可以调用GetSystemDirectory函数来得到,对于WindowsNT/2000来说,指的是32位Windows的系统目录,也可以调用GetSystemDirectory函数来得到,得到的值为SYSTEM32。
·Windows目录
·列在PATH环境变量中的所有目录
VC中加载DLL的LIB文件的方法有以下三种:
①LIB文件直接加入到工程文件列表中
在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中“Add Files to Project”菜单,在弹出的文件对话框中选中要加入DLL的LIB文件即可。
②设置工程的 Project Settings来加载DLL的LIB文件
打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件。
③通过程序代码的方式
加入预编译指令#pragma comment (lib,”*.lib”),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如;在Release方式下,产生的LIB文件是Release版本,如。
当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明。
2 显式的运行时链接 ,(我用的是此方法)
隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,其能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下:
①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。
②使用GetProcAddress函数得到要调用DLL中的函数的指针。
③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。
extern "c"与.def文件的作用
首先,我们需要知道C和C++编译器对函数名字的处理方式是不一样的;其次,就是同为C编译器的两个不同产品,在编译时对函数名字的处理方式也是有区别的,比如microsoft vc++与dev c++。所以,extern "C"与.def文件正是为了解决这两种情况而引入的处理方法。
第一、extern "C"的作用
比如一个C源程序A.c要使用C++编写的库函数,在A.c中#include "B.h",其中B.h中有要使用的函数的原形声明func。当编译链接源程序时,却发现了“链接错误,未决的外部符号...”的错误,这是什么原因呢?
原因就是,C编译器编译A.c时,将func编译为func,当链接时链接器去C++库中寻找func,但是C++的编译器在编译库时将func编译成_func@yyy@rrr,自然链接器就找不着相应的函数的信息了,所以就会报错!有什么办法可以处理这种情况呢?——可以在编写C++库的时候,为每一个函数(或导出函数)加上extern "C",它的含义是告知C++编译器在编译这些函数的时候,以C编译器的方式处理函数名。这样生成的库中的函数名字就是func了,当C程序调用库函数,编译链接时,链接器就能找到期望的信息,则链接成功。
第二、.def文件的作用(仅与VC++编程相关)
前面提到,不同厂商开发的两个C编译器也会有一些差异,最突出的就是microsoft的C编译器,它对函数名字的处理很特别(究竟是什么样子,可以使用Dumpbin工具查看dll的导出函数),所以要在使用他方编写的库时,程序链接能成功,有两种方法:1使用库编写者使用的C编译器(这里指VC++),显然这种方法不合理;2库的编写者在使用VC++编写库时使用.def文件。
.def文件的作用即是,告知编译器不要以microsoft编译器的方式处理函数名,而以指定的某方式编译导出函数(比如有函数func,让编译器处理后函数名仍为func)。这样,就可以避免由于microsoft VC++编译器的独特处理方式而引起的链接错误。
用.def文件描述dll的输出函数,比如
EXPORTS
DllFun1 @1 NONAME
DllFun2 @2 NONAME
DllFun3 @3 NONAME
DllFun4 @4 NONAME
...
@后面是dll输出函数的顺序号,顺序号要求在1到max(函数个数)之间。
用.def声明了输出函数以后,不要在头文件里再声明AFX_EXT_API或者是dllexport之类的声明,而用extern "C"声明了输出函数以后,要引用原来输出函数的头文件。如果不是用extern
"C"声明的(包括用.def声明的),那么头文件里无需用AFX_EXT_API或者是dllexport之类的声明。
.def文件的作用
在VC++中,生成DLL可以不使用.def文件。只需要在VC++的函数定义前要加__declspec(dllexport)修饰就可以了。但是使用__declspec(dllexport)和使用.def文件是有区别的。如果DLL是提供给VC++用户使用的,你只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL。但是如果你的DLL是供其他程序如VB、delphi,以及.NET用户使
用的,那么会产生一个小麻烦。因为VC++对于__declspec(dllexport)声明的函数会进行名称转换,如下面的函数:
__declspec(dllexport) int __stdcall IsWinNT()
会转换为IsWinNT@0,这样你在VB中必须这样声明:
Declare Function IsWinNT Lib "" Alias "IsWinNT@0" () As Long
@的后面的数由于参数类型不同而可能不同。这显然不太方便。所以如果要想避免这种转换,就要使用.def文件方式。
EXPORTS后面的数可以不给,系统会自动分配一个数。对于VB、PB、Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。
例子:我们用VC6.0制作一个dll,不使用.def文件,在头文件中这样写
#ifndef LIB_H
#define LIB_H
extern "C" int _declspec(dllexport)add(int x,int y);
#endif
如果是.def文件,可以这样
LIBRARY "xxx_dll"
EXPORTS
add PRIVAT
动态链接库(Dynamic Link Library)学习笔记
我对动态链接和动态链接库的概念并不陌,但一直以来就停留在概念的层面上,没有更深入的了解。今天抽空看了一下有关动态链接和动态链接库的文章,有了一些新的认识,当然不能忘了写在这里。那么现在就开始...
什么是动态链接和动态链接库
动态链接(Dynamic Linking)是相对于静态链接(Static Linking)而言的。程序设计中,为了能做到代码和模块的重用,程序设计者常常将常用的功能函数做成库,当程序需要实现某种功能时,就直接调用库文件中的函数,从而实现了代码的重用。早期的程序设计中,可重用的函数模块以编译好的二进制代码形式放于静态库文件中,在MS的操作系统中是Lib为后缀的文件。程序编写时,如果用户程序调用到了静态库文件中的函数,则在程序编译时,编译器会自动将相关函数的二进制代码从静态库文件中复制到用户目标程序,与目标程序一起编译成可执行文件。这样做的确在编码阶段实现了代码的重用,减轻了程序设计者的负担,但并未在执行期实现重用。如一个程序使用了静态库中的 f() 函数,那么当有多个实例运行时,内存中实际上存在了多份f()的拷贝,造成了内存的浪费。
随着技术的进步,出现了新的链接方式,即动态链接,从根本上解决了静态链接方式带来的问题。动态链接的处理方式与静态链接很相似,同样是将可重用代码放在一个单独的库文件中(在MS的操作系统中是以dll为后缀的文件,Linux下也有动态链接库,被称为Shared
Object的so文件),所不同的是编译器在编译调用了动态链接库的程序时并不将库文件中的函数执行体复制到可执行文件中,而是只在可执行文件中保留一个函数调用的标记。当程序运行时,才由操作系统将动态链接库文件一并加载入内存,并映射到程序的地址空间中,这样就保证了程序能够正常调用到库文件中的函数。同时操作系统保证当程序有多个实例运行时,动态链接库也只有一份拷贝在内存中,也就是说动态链接库是在运行期共享的。
使用动态链接方式带来了几大好处:首先是动态链接库和用户程序可以分开编写,这里的分开即可以指时间和空间的分开,也可以指开发语言的分开,这样就降低了程序的耦合度;其次由于动态链接独特的编译方式和运行方式,使得目标程序本身体积比静态链接时小,同时运行期又是共享动态链库,所以节省了磁盘存储空间和运行内存空间;最后一个是增加了程序的灵活性,可以实现诸如插件机制等功能。用过winamp的人都知道,它的很多功能都是以插件的形式提供的,这些插件就是一些动态链接库,主程序事先规定好了调用接口,只要是按照规定的调用接口写的插件,都能被winamp调用。
WIndow 95、98、NT系列等系统都提供了动态链接库的功能,并且这些操作系统的系统调用大多都是通过动态链接库实现的,最常见的NT系列OS中的,,等动态链接库文件就包含了大量的系统调用。在windows家族中,NT内核的操作系统在动态链接库机制上较之前的95、98系统要更安全。95、98系统在程序调用动态链接库时,将动态链接库加载到2G-3G之间的被称为进程共享空间的虚拟地址空间,并且所有进程关于这1G的虚拟地址空间的页表都是相同的,也就是说对于所有的进程,这片共享区的页表都指向同一组物理页,这样一来,加载入内存的的动态链接库对所有正在运行的进程都是可见的。如果一个动态链接库被其中一个进程更改,或其自身崩溃,将影响到所有调用它的进程,如果该动态链接库是系统的动态链接库,那么将导致系统的崩溃。在Windows NT系统中,动态链接库被映射到进程的用户地址空间中,并用Copy On
Write机制保证动态链接库的共享安全,Copy On Write可以理解为写时拷贝。一般情况下,多个运行的进程还是按原来的模式共享同一个动态链接库,直到有进程需要向动态链接库的某个页面写数据时,系统将该页做一个拷贝,并将新复制页面的属性置为可读可写,最后修改进程的页表使之指向新拷贝的物理页。这样无论该进程怎么修改此页的数据,也不会影响到其他调用了此动态链接库的进程了。
Windows下动态链接库的编写
因为本人对linux没有太多研究,所以这里只介绍windwos环境下动态链接库的编写。
在VC中新建一个空的Win32动态链接库工程(Win32 Domanic Library),然后添加一个C++ Sourse File到工程,我这里的文件名取。然后在文件中添加如下内容:
//
_declspec(dllexport) int add(int a,int b)
{
return a+b;
}
_declspec(dllexport) int subtract(int a,int b)
{
return a-b;
}
接下来编译链接,就会在debug目录下生成一个调试版本的动态链接库,该链接库包含了add和subtract两个可供外部调用的函数。我们注意到,在源文件中多了一个没有见过的语句 _declspec(dllexport) ,这个语句的作用就是向编译器指出我需要在生成的动态链接库中导出的函数,没有导出的函数是不能被其他程序调用的。要知道一个动态链接库导出了什么函数,可以在命令提示行用命令"dumpbin -exports "来查看(也可以用VC工具包中的depends使用程序来查看)。以下是用dumpbin命令查看而生成的信息:
Dump of file
File Type: DLL
Section contains the following exports for
0 characteristics
4420BEA4 time date stamp Wed Mar 22 11:04:04 2006
0.00 version
1 ordinal base
2 number of functions
2 number of names
ordinal hint RVA name
1 0 0000100A ?add@@YAHHH@Z
2 1 00001005 ?subtract@@YAHHH@Z
Summary
7000 .data
1000 .idata
3000 .rdata
2000 .reloc
2A000 .text
可以看到,我们编写的动态链接库导出了两个函数,分别名为?add@@YAHHH@Z
和 ?subtract@@YAHHH@Z,为什么名字不是add和subtract呢?这是因为C++为了支持函数的重载,会在编译时将函数的参数类型信息以及返回值类型信息加入到函数名中,这样代码中名字一样的重载函数,在经过编译后就互相区分开了,调用时函数名也经过同样的处理,就能找到对应的函数了。编译器对函数的重命名规则是与调用方式相关的,在这里采用的是C++的默认调用方式。以此对应的还有stdcall方式、cdecl方式、fastcall方式和thiscall方式,不同调用方式的重命名规则不一样。
需要特别说一下的是stdcall方式和cdecl方式:
stdcall方式(标准调用方式)也即pascal调用方式,它的重命名规则是函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数所占字节数,之所以要跟参数字节数,是因为stdcall采用被调函数平衡堆栈方式,用函数名最后的数字告诉编译器需要为函数平衡的字节数。例如,如果我们的采用stdcall方式编译的话,导出的函数名将会是
_add@8 和 _subtract@8 ,而函数编译后的汇编代码最后一句一定是 ret8。
cdecl方式即C语言调用方式,它的重命名规则仅仅是在函数名前加下划线(奇怪的是我用vc6编译的c语言函数,名字没有任何改变),因为C语言采用的是调用函数平衡堆栈的方式,所以不需要在函数名中加入参数所占的字节数,这样的堆栈平衡方式也使C语言可以编写出参数不固定的函数;同时C语言不支持函数重载,因此不需要在函数名中加入参数类型信息和返回值类型信息。
更多关于调用方式的介绍请看我收藏的文章《C语言函数调用约定 》 。
动态链接库已经生成了,接下来就是调用的工作了。调用动态链接库有两种方式:隐式调用和显式调用,下面我们分别来看两种调用方式的具体过程:
动态链接库的隐式调用
新建一个空的Win32 Console Application,命名为DllCaller,向工程中添加名为 的C++ Sourse File,在文件中写入如下代码:
#include
using namespace std;
//extern int add(int a,int b);
_declspec(dllimport) int add(int a,int b);
int main()
{
cout<<"3+5="< return 1; } 编译,没有错误,链接,有两个错误:找不到外部引用符号。要怎样才能让我们的程序找到动态连接库中的函数呢?这里是关键的一步。到刚才的DllTest工程目录下,从debug文件夹中拷贝生成的文件和文件到DllCaller工程目录。然后依次在vc中选择菜单:Project -->Settings-->Liink, 在Object/library Modules中加入一项文件名:,这里的并不是静态库文件,而是的导入库文件,它包含了动态链接库导出的函数信息,只有在工程链接设置里添加了该文件,才能够使调用了该动态链接库的工程正确链接。完成以上步骤后,我们再编译链接工程,这次没有任何错误!程序可以顺利调用动态连接库文件,正常运行了(为了能使程序找到并加载需要的动态链接库,动态链接库文件必须与调用程序在同一个目录下,或在path环境变量指定的目录下)。 这里需要说明一点,工程中的源文件在调用动态链接库中的函数时,需要提前声明,声名有两种方式,一种是传统的extern方式,一种是_declspec(dllimport)方式,这两种方式在代码中我都给出了。其中,第二种方式能使编译过程更快,所以推荐使用。 动态链接库的显式调用 比起隐式调用,显示调用更加灵活,而且在编译链接时不需要lib导入库文件,也不需要提前声明函数。我们通过windows提供的API函数来动态加载动态连接库并调用其中的函数,用完后可以马上释放内存中的动态链接库,十分方便。下面就是显示调用动态链接库的代码: #include #include using namespace std; int main() { HINSTANCE hInstance=LoadLibrary(""); typedef int (*AddProc)(int,int); AddProc Add=(AddProc)GetProcAddress(hInstance,?add@@YAHHH@Z); if(!Add) { cout<<"动态连接库库函数未找到"< return 0; } cout<<"3+5="< FreeLibrary(hInstance); return 1; } 以上代码并不复杂,首先定义一个实例句柄用来引用由Windows API 函数LoadLibrary加载的动态链接库,LoadLibrary函数的参数是一个字符串指针,具体调用时我们需要填入需要加载的动态链接库的位置及文件名,加载成功后返回一个实例句柄。接下来我们定义一个函数指针类型,用该类型声明一个函数指针,用来存储GetProcAddress函数返回的动态库函数入口地址。GetProcAddress能从指定的动态库中查找指定名字的函数,如果查找成功则返回该函数的入口地址,如果失败则返回NULL。更多GetProcAddress函数的用法请参看MSDN。有人可能注意到,GetProcAddress函数中指定的函数名并不是add,而是?add@@YAHHH@Z。这里就和前面将的函数调用方式联系起来了,在GetProcAddress函数中,我们指定的函数名必须是编译后经过重命名的函数名,而不是源文件中定义的函数名。这样实际上给我们的调用带来了相当大的麻烦,因为我们不可能去了解每一个经过重命名的导出函数名。好在微软已经给出了解决方法,那就是在编写动态链接库时同时编写一个以def为后缀的编译命名参考文件,如果动态链接库工程中有该文件,则编译器会根据该文件指定的函数名来导出动态库函数,关于def文件的详细使用方法请参考MSDN,这里就不一一赘述。找到需要的动态库函数后,我们就可以按需要对它进行调用,之后调用FreeLibrary函数释放动态库。因为动态库是多进程共享的,因此调用FreeLibrary函数并不意味着动态库在内存中被释放,每个动态库都有一个变量用来记录它的共享引用计数,而FreeLibrary的功能只是将这个记数减一,只有当一个动态库的引用计数为0时,它才会被操作系统释放。 隐式调用与显式调用的对比 前面已经详细介绍了动态链接库的两种调用方法,相比之下,隐式调用在编程时比较简单,指定导入库文件后,不必考虑函数的重命名,就可以直接调用动态库函数。但由于隐式调用不能指定动态库的加载时机,因此在一个程序开始运行时,操作系统会将该程序需要的动态链接库都加载入内存,势必造成程序初始化的时间过长,影响用户体验。而显式调用采用动态加载的方法,用到什么加载什么,用完即释放,灵活性较高,可以使程序得到优化。具体运用中到底采用哪种方法,还要依实际情况而定 静态链接库LIB和动态链接库DLL VS2008 lib静态链接 一、 静态链接库与动态链接库区别 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。 动态库就是在需要调用其中的函数时,根据函数映射表找到该函数然后调入堆栈执行。如果在当前工程中有多处对dll文件中同一个函数的调用,那么执行时,这个函数只会留下一份拷贝。但是如果有多处对lib文件中同一个函数的调用,那么执行时,该函数将在当前程序的执行空间里留下多份拷贝,而且是一处调用就产生一份拷贝。 静态链接库与静态链接库调用规则总体比较如下: 1、 静态链接库(比较简单): 首先,静态链接库的使用需要库的开发者提供生成库的.h头文件和.lib文件。生成库的.h头文件中的声明格式如下: extern "C" 函数返回类型 函数名(参数表); 在调用程序的.cpp源代码文件中如下: #include "..lib.h" #pragma comment(lib,"..") //指定与静态库一起链接(或者在IDE的lib栏中填入lib文件的路径,在IDE的lib目录栏填入lib所在文件夹目录) 其次因为静态链接库是将全部指令都包含入调用程序生成的EXE文件中。因此如果用的是静态链接库,那么也就不存在“导出某个函数提供给用户使用”的情况,要想用就得全要!要不就都别要! 个人理解:其实可以认为静态链接看做与直接将要链入的lib工程的源码引入编译等效,因为实际结果来看也应该是等效的,最终都只是一个exe文件,功能会是完全一样的,只是多了一些编译链接的过程. 2、 动态链接库: 动态链接库的使用需要库的开发者提供生成的.lib文件和.dll文件。或者只提供dll文件。 首先我们必须先注意到DLL内的函数分为两种: 1)导出函数,可供应用程序调用; 2) DLL内部函数,只能在 DLL 程序使用,应用程序无法调用它们。 因此调用程序若想调用DLL中的某个函数就要以某种形式或方式指明它到底想调用哪一个函数。 对于DLL的导出,可以采用如下方法: #ifdef WLL_EXPORTS #define WLL_API __declspec(dllexport) #else #define WLL_API __declspec(dllimport) #endif 这是导出类的宏定义,将导出类必须加上该宏,才能被导出。 此处的WLL_EXPORTS会出现在 project setting C++ PreProcessor的PreProcessor definition中,这个MACRO表明其要定义一个导出宏。当前库编译时,加了WLL_API的类将被导出,而包含该头文件的其他调用DLL或EXE,由于没有定义WLL_API宏,将申明为导入该类。 动态库函数的调用,可以采用静态链接的方式 ,主要步骤如下: 1) 包含DLL中导出的头文件。//对要从dll文件中导入的函数进行声明(不使用.h头文件,只进行声明也可) 2) 采用#pragma comment(lib,"..")导入动态库生成的*.lib头文件。或在 projectàsettingsàLinkeràInput的Additional Dependencies中加入lib文件。//提供dll以及dll内导出函数的信息,方便加载dll文件和链接dll文件中的导出函数 3) 将动态库生成的*.dll文件放到EXE或DLL的同一目录下。 也可以采用动态加载的方式调用 ,步骤如下: 有一个int Add(int x,int y) 函数。则完整的调用过程如下: typedef int (* FunPtr)(int,int); //定义函数指针 FunPtr funPtr; Handle handle =LoadLibrary(""); funPtr =(FunPtr)GetProcAddress(handle ,"Add"); funPtr(2,3); // 2+3; FreeLibrary(handle); // 释放载入的动态库 二、 LIB文件 目前以lib后缀的库有两种,一种为静态链接库 (Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库 (Import Libary,以下简称“导入库”)。 静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。 动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。 导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。 这也是实际上很多开源代码发布的惯用方式: 1、 预编译的开发包:包含一些.dll文件和一些.lib文件。其中这里的.lib就是导入库,而不要错以为是静态库。但是引入方式和静态库一样,要在链接路径上添加找到这些.lib的路径。 而.dll则最好放到最后产生的应用程序exe执行文件相同的目录。这样运行时,就会自动调入动态链接库。 2、 用户自己编译:下载的是源代码,按照readme自己编译。生成很可能也是.dll + .lib(导入库)的库文件 3、 如果你只有dll,并且你知道dll中函数的函数原型,那么你可以直接在自己程序中使用LoadLibary调入DLL文件,GetProcAddress获取函数地址,然后调用。 三、 DLL文件 动态链接库 (DLL) 是作为共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接并与使用它们的进程分开存储的函数。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。 动态链接与静态链接的不同之处在于它允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态链接库获取 所有被引用的函数,并将库同代码一起放到可执行文件中。 使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。 API 就是应用程序编程接口。它是能用来操作组件、应用程序或者操作系统的一组函数。典型的情况下,API 由一个或多个提供某种特殊功能的 DLL 组成。 DLL 是一个文件,其中包含了在 Microsoft Windows下运行的任何应用程序都可调用的函数。运行时,DLL 中的函数动态地链接到调用它的应用程序中。无论有多少应用程序调用 DLL 中的某个函数,在磁盘上只有一个文件包含该函数,且只在它调入内存时才创建该 DLL。 您听到最多的 API 可能是 Windows API,它包括构成 Windows 操作系统的各种 DLL。每个 Windows 应用程序都直接或间接地与 Windows API 互动。Windows API 保证 Windows 下运行的所有应用程序的行为方式一致。 随着 Windows 操作系统的发展,现已发布了几个版本的 Windows API。Windows 3.1 使用 Win16 API。Microsoft Windows NT、Windows 95 和 Windows 98 平台使用 Microsoft Win32 API。除 Windows API 外,其他一些 API 也已发布。例如,邮件应用程序编程接口 (MAPI) 是一组可用于编写电子邮件应用程序的 DLL。API 传统上是为开发 Windows 应用程序的 C 和 C++ 程序员编写的,但其他的编程语言(包括VBA)也可以调用 DLL 中的函数。因为大部分 DLL 主要是为 C 和 C++ 程序员编写和整理说明的,所以调用 DLL 函数的方法与调用 VBA 函数会有所不同。在使用 API 时必须了解如何给 DLL 函数传递参数。 警告:调用 Windows API 和 其他 DLL 函数可能会给您的应用程序带来不良影响。从自己的代码中直接调用 DLL 函数时,您绕过了 VBA 通常提供的一些安全机制。如果在定义或调用 DLL 函数时出现错误(所有程序员都不可避免),可能会在应用程序中引起应用程序错误(也称为通用性保护错误,或GPF)。最好的解决办法是在运行代码以前保存该项目,并确保了解 DLL 函数调用的原理。 动态链接库 为了让DLL导出一些函数,需要在每一个将要被到处的函数前面添加标识符: _declspec(dllexport)。例: 首先用VC++新建一个Win32 Dynamic-Link Library类型的工程,工程取名为:Dll1,并在AppWizard的第一步选择“An empty Dll project”选项,然后为该工程添加一个c++源文件:,源文件中的代码如下: _declspec(dllexport) int add(int a,int b) { return a+b; } _declspec(dllexport) int subtract(int a,int b) { return a-b; } 隐式链接方式加载DLL 新建一个基于对话框的MFC应用程序,工程取名为:DllTest,在该工程的主对话框资源上放置两个按钮,其ID与Caption如下: ID Caption IDC_BTN_ADD IDC_BTN_SUBTRACT 命令响应函数如下: extern int add(int a,int b); extern int subtract(int a,int b);//外部函数需声明 void CDllTestDlg::OnBtnAdd() { // TODO: Add your control notification handler code here CString str; ("5+3=%d",add(5,3)); MessageBox(str); } void CDllTestDlg::OnBtnSubtract() { // TODO: Add your control notification handler code here CString str; ("5-3=%d",subtract(5,3)); MessageBox(str); } 然后再把之前写的复制到DllTest程序所在的目录 最后,选择【Project/Setting/Link/Object/library modules】输入:。 Depends工具 该工具不仅能够查看可执行程序,还可以查看动态链接库,主要是查看它们依赖于哪些动态链接库。现查看,如图: Add Subtract extern int add(int a,int b); extern int subtract(int a,int b); 上面的这两条声明语句也可以改成: _declspec(dllimport) int add(int a,int b); _declspec(dllimport) int subtract(int a,int b); 完善Win32 DLL例子 为DLL1工程添加一个头文件:Dll1.h,头文件 代码如下: #ifdef DLL1_API #else #define DLL1_API _declspec(dllimport) #endif DLL1_API int add(int a,int b); DLL1_API int subtract(int a,int b); 在中的代码如下: #define DLL1_API _declspec(dllexport) #include "Dll1.h" int add(int a,int b) { return a+b; } int subtract(int a,int b) { return a-b; } 测试程序中只要包含Dll1.h头文件即可,不用再声明函数了,,,从DLL中导出c++类 在Dll1.h文件中添加如下代码: class DLL1_API Point { public: void output(int x,int y); }; 在文件中实现Point这个类: void Point::output(int x,int y) { HWND hwnd=GetForegroundWindow(); //返回当前用户正在使用的窗口的句柄 HDC hdc=GetDC(hwnd); char buf[20]; memset(buf,0,20); sprintf(buf,"x=%d,y=%d",x,y); TextOut(hdc,0,0,buf,strlen(buf)); ReleaseDC(hwnd,hdc); } 还需包含头文件: #include #include 在DllTest工程的对话框中再增加一个按钮,ID为IDC_BTN_OUTPUT, Caption属性设置为:Output,命令响应函数为: void CDllTestDlg::OnButOutput() { // TODO: Add your control notification handler code here Point pt; (5,3); } 别忘了重新拷贝一下和。(^o^)/~ 解决名字改编问题: C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改编规则不一样。这样,如果利用不同的编译器分别生成DLL和访问该DLL的客户端程序的话,后者在访问该DLL的导出函数时就会出现问题。 上面写的中文件的函数名变成了这样,看着也很不爽,希望动态链接库文件 在编译时,导出函数的名称不要发生改变。为了实现这一目的,在定义导出函数时,需要加上限定符:extern “C”。注意双引号中的“C”字母一定要大写。 #define DLL1_API extern "C" _declspec(dllimport) #define DLL1_API extern "C"_declspec(dllexport) 把之前关于Point类的内容注释起来, 这时导出函数的名字改编问题解决: 利用限定符:extern “C”可以解决c++和C语言之间相互调用函数命名的问题。但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。 另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符:extern “C”,该函数的名字仍会发生改编。 例:接着在上面的程序,在add,subtract函数名前,加上_stdcall,声明和实现的地方都要加,则会出现如下图所示情况: 这种情况下,可以用模块定义文件(DEF)的方法来解决名字改编问题。 例:新建一个Win32 Dynamic-Link Library类型的工程,工程名为Dll2,并在AppWizard的第一步选择“An empty Dll project”,给该工程添加一个c++源文件:,源文件中的代码如下: int add(int a,int b) { return a+b; } int subtract(int a ,int b) { return a-b; } 为该工程添加一个模块定义文件,, 该文件中的代码如下: LIBRARY Dll2 EXPORTS jiafa=add subtract 显示加载方式加载DLL 先将最新的文件复制到DllTest工程目录下。然后在DllTest工程中,将文件中包含Dll1.h文件的那行代码注释起来,并在该工程设置对话框的Link选项卡上,删除对文件的链接。 加法按钮的命令响应函数代码如下: void CTestDlg::OnButton1() { // TODO: Add your control notification handler code here HINSTANCE hInst; hInst=LoadLibrary(""); typedef int (*ADDPROC)(int a,int b); ADDPROC Add=(ADDPROC)GetProcAddress(hInst,"add"); if(!Add) { MessageBox("获取函数地址失败!"); return; } CString str; ("5+3=%d",Add(5,3)); MessageBox(str); } 当DLL中导出函数采用的是标准调用约定时,访问该DLL的客户端程序也应该采用该约定类型来访问响应的导出函数。 动态链接库的设计(DLL) 一、相关概念 1、动态链接库 自从微软推出第一个版本的 Windows 操作系统以来,动态链接库( DLL )一直是 Windows 操作系统的基础。 动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它 DLL 调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。 Windows API 中的所有函数都包含在 DLL 中。其中有 3 个最重要的 DLL , ,它包含用于管理内存、进程和线程的各个函数; ,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数; ,它包含用于画图和显示文本的各个函数。 2、静态库和动态库 静态库:函数和数据被编译进一个二进制文件 ( 通常扩展名为 .LIB) 。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件 (.EXE 文件 ) 。 在使用动态库的时候,往往提供两个文件:一个引入库和一个 DLL 。引入库包含被 DLL 导出的函数和变量的符号名, DLL 包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库, DLL 中的函数代码和数据并不复制到可执行文件中,在运行的时候,再去加载 DLL ,访问 DLL 中导出的函数。 3、使用动态链接库的好处 可以采用多种编程语言来编写。 增强产品的功能。 提供二次开发的平台。 简化项目管理。 可以节省磁盘空间和内存。 有助于资源的共享。 有助于实现应用程序的本地化。 4、动态链接库被多个进程访问 5、动态链接库加载的两种方式 隐式链接 显示加载 二、实际操作 可以通过MSDN:索引DLL查到相关内容 创建一个新的项目选WIN32控制台应用程序 在应用程序设置中选择DLL,空项目 添加C++源文件(*.cpp),命名为DLL_ 添加代码: int add( int a, int b) { return a + b; } int sub( int a, int b) { return a - b; } 利用VS工具目录下的命令提示符进入相应DLL所在目录,然后用dumpbin命令查看导出函数: 例如:E:mytestsxLesson19DLL_1Debug>dumpbin -exports dll_ 但是仅有以上代码是不会有导出函数的,总的来说有2种方法来导出函数 __declspec(dllexport) 关键字 .DEF 文件 使用 .DEF 文件的优缺点 在 .DEF 文件中导出函数使您得以控制导出序号。当将附加的导出函数添加到 DLL 时,可以给它们分配更高的序号值(高于任何其他导出函数)。当您进行此操作时,使用隐式链接的应用程序不必与包含新函数的新导入库重新链接。这非常重要,例如,在设计将由许多应用程序使用的第三方 DLL 时。可以通过添加附加的功能不断地增强 DLL,同时确保现有应用程序继续正常使用新的 DLL。MFC DLL 是用 .DEF 文件生成的。 使用 .DEF 文件的另一个优点是:可以使用 NONAME 属性导出函数,该属性仅将序号放到 DLL 的导出表中。对具有大量导出函数的 DLL,使用 NONAME 属性可以减小 DLL 文件的大小。有关编写模块定义语句的信息,请参见模块定义语句的规则。有关序号导出的更多信息,请参见按序号而不是按名称从 DLL 导出函数。 使用 .DEF 文件的主要缺点是:在 C++ 文件中导出函数时,需要将修饰名放到 .DEF 文件中,或者通过使用外部“C”用标准 C 链接定义导出函数,以避免编译器进行名称修饰。 如果需要将修饰名放到 .DEF 文件中,可以通过使用 Dumpbin 工具或通过使用 /MAP 链接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。如果将 Visual C++ 编译器产生的修饰名放到 .DEF 文件中,则链接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .DEF 文件中的导出名相匹配。 使用 __declspec(dllexport) 的优缺点 使用 __declspec(dllexport) 非常方便,因为不需要考虑维护 .DEF 文件和获取导出函数的修饰名。但是,无法控制编译器生成的导出序号。此方法适合某些情况,例如,在设计要与控制的应用程序一起使用的 DLL 时;如果用新导出重新生成 DLL,则还需要重新生成应用程序。 下面分别介绍两种方法的使用: __declspec(dllexport) 关键字 在每个需要导出的函数前添加该关键字: dumpbin查看文件中发现如下字段: ordinal hint RVA name 1 0 000112A8 ?add@@YAHHH@Z 2 1 00011663 ?sub@@YAHHH@Z 其中hint:提示码 RVA:列出地址值 注意到先前的函数名add变为?add@@YAHHH@Z,因此这样的函数只能在由相同编译器编译的程序中进行调用,而其他程序调用则会出错。之所以篡改名字是为了C++的函数重载!(如何调用请查看调用方法1) .DEF 文件(模块定义文件) [ 推荐,适合于 DELPHI 等项目使用 C++DLL] 在dll工程项目下,添加新项->模块定义文件,添加一个新的文件DLL_ 在其中写:其中add,sub为要导出的函数的名称 LIBRARY DLL_2 EXPORTS // 表示要导出那些函数 add sub 由此生成的文件不会改变导出函数名 _declspec(dllexport) int add( int a, int b) { return a + b; } _declspec(dllexport) int sub( int a, int b) { return a - b; } 我的理解: 动态链接库 生成一个动态链接库应包含两个文件 *.dll *.lib。 lib是编译时需要的,dll是运行时需要的。 如果要完成源代码的编译,有lib就够了。 如果要使动态连接的程序运行起来,有dll就够了。 lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。 Dll文件包含函数的实现,是可执行的代码。 Lib文件是dll的一个映像文件,一般包含一些索引信息,如DLL导出的函数的名称和位置等。 两种调用方式:隐式调用、显示调用。 隐式调用:应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到exe文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址。(lib文件是必须的) 步骤: 1、保证和两个文件都有 2、将要调用的函数的声明和下面的语句写到一个头文件中 #pragma comment(lib, "") ////指定与静态库一起连接,也可以在IDE中设置lib文件的路径或者直接将lib文件添加到工程中来 显示调用:如果dll没有对应的.lib文件,那么就只能使用显示调用(动态加载)的方式了。 动态调用动态库步骤: 1、创建一个函数指针,其指针数据类型要与调用的DLL引出函数相吻合。 2、通过Win32 API函数LoadLibrary()显式的调用DLL,此函数返回DLL的实例句柄。 3、通过Win32 API函数GetProcAddress()获取要调用的DLL的函数地址,把结果赋给自定义函数的指针类型。 4、使用函数指针来调用DLL函数。 5、最后调用完成后,通过Win32 API函数FreeLibrary()释放DLL函数。 静态链接库 如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。 lib库有两种, 一种是包含了函数所在DLL文件和文件中函数位置的信息,称为导出库,就是动态lib;简单点说就是相当于一个.h头文件,对dll文件(.c文件)进行声明。 一种是函数索引和实现都在其中,编译后所有代码都嵌入到宿主程序,即静态库lib。 网上摘抄 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。 “每一个lib文件就是若干函数(假设只有函数)的定义” lib库有两种,一种是包含了函数所在DLL文件和文件中函数位置的信息,称为导出库;一种是包含函数代码本身,称为静态库,一般现有的DLL,用的是前一种库;以前在DOS下的TC/BC等,是后一种库。包含函数原型声明的,是头文件(.h)。 lib有静态lib和动态lib之分。 静态lib将导出声明和实现都放在lib中。编译后所有代码都嵌入到宿主程序 动态lib相当于一个h文件,是对实现部分(.dll文件)的导出部分的声明。编译后只是将导出声明部分编译到宿主程序中,运行时候需要相应的dll文件支持 “通过#include包含这些函数声明的头文件后,我们的应用程序就可以使用lib文件中的函数” 还要指定编译器链接相应的库文件。在IDE环境下,一般是一次指定所有用到的库文件,编译器自己寻找每个模块需要的库;在命令行编译环境下,需要指定每个模块调用的库。 “那他和直接给出那个函数定义的文件,比如.cpp文件,和头文件有什么区别,静态链接库有什么用” cpp文件是源代码,库文件是编译后的二进制代码,比如你可以调用Windows的API,但是不能看到其源代码一样。 “还有不明白的是,静态链接库中的lib文件只要用到,则整个lib文件的内容都放进了exe文件中,那它是被编译进去还是链接的时候连接进去的呢?” 是在链接的时候将lib链接到目标代码中。 静态链接库(Lib) 在VC++6.0中new一个名称为libTest的static library工程, 并新建lib.h和两个文件,lib.h和的源代码如下: //文件:lib.h #ifndef LIB_H #define LIB_H extern "C" int add(int x,int y); //声明为C编译、连接方式的外部函数 #endif //文件: #include "lib.h" int add(int x,int y) { return x + y; } 编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。 标准Turbo C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。 下面来看看怎么使用这个库,在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个文件,它演示了静态链接库的调用方法,其源代码如下: #include #include "..lib.h"//不可丢失 #pragma comment( lib, ".." ) //指定与静态库一起连接,不可少 int main(int argc, char* argv[]) { printf( "2 + 3 = %d", add( 2, 3 ) ); } 静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma comment( lib , ".." )的意思是指本文件生成的.obj文件应与一起连接,也可以在IDE中直接设置lib文件或者直接将lib文件添加到应用程序工程中,三种方式 windows sdk编程系列文章 ---- 动态链接库 本课中,我们将学习DLLs,它们到底是什么和如何创建它们。 理论: 如果您编程的时间非常长,就会发现很多的程序之间其实有相当多的重复代码。每编一个程序就重写一遍这些代码既没必要又浪费时间。 在DOS时代,一般的做法是把这些重复的代码写成一个个的函数,然后把它们按类别放到不同的库文件中去。当要使用这些函数时,只要把您的目标文件(.obj)文件和先前存放在库文件中的函数进行链接,链接时链接器会从库文件中抽取相关的信息并把它们插入到可执行文件中去。这个过程叫做静态链接。C运行时库就是一个好例子。这样的库的缺点是您在每一个调用库函数的程序中都必须嵌入同一函数的拷贝,这显然很浪费磁盘。在DOS时代毕竟每一时刻仅有一个程序在运行,所以浪费的还只是磁盘而已,在多任务的WINDOWS时代就不仅浪费磁盘,还要浪费宝贵的内存了。 在WINDOWS中,由于有多个程序同时运行,如果您的程序非常大的话,那将消耗相当多的内存。WINDOWS的解决办法是:使用动态链接库。动态链接库从表面上看也是一大堆的通用函数,不过即使有多个程序调用了它,在内存中也仅仅只有动态链接库的唯一一份拷贝。WINDOWS是通过分页机制来作到这一点的。当然,库的代码只有一份,但是每一个应用程序要有自己单独的数据段,要么就会乱掉。 不象旧时的静态链接库,它并不会把这些函数的可执行代码放入到应用程序中去,而是当程序已经在内存中运行时,如果需要调用该函数时才调入内存也即链接。这也就是为什么把它叫做“动态”的原因所在。另外您还可以动态地卸载动态链接库,当然要求这时没有其它的应用程序在使用它,否则就要一直等到最后一个使用它的函数也不再使用该动态链接库时才能去卸载它。 为了正确的调用库和给库函数分配内存空间,在编译和链接应用程序时,必须把重定位等一些消息插入到执行代码中去,以便载入正确的库,并给库函数分配正确的地址。 那么这些信息从哪里得到呢?引入库。引入库包含足够的信息,链接器从中抽取足够的信息(注意区别:静态链接库放入的是可执行代码)把它们放入到可执行文件中去。当WINDOWS的加载器装入应用程序查看到有DLL时,它会查找该库文件,如果没有查到,就报错退出,否则就把它映射进进程的地址空间,并修正函数调用语句的地址。 如果没有引入库呢?当然我们也可以调用动态链接库中的任意函数。只不过您必须知道调用的函数是否在库中而且是否在库的引出名字表中,另外还需要知道该函数的参数个数和参数的类型。 说到这里,让我想起了一件很有名的事。< 当您让系统的加载器为您加载动态库时,如果不能找到库文件,它就会提示一条“A required .DLL file, is missing”,这样您的应用程序就无法运行,即使该库对您的应用程序来说并不重要(动态库的隐式调用存在这种问题)。 • 如果您选择在程序运行时自己加载该库,就没有这种问题了(动态库的显式调用不存在这种问题)。 • 如果您知道足够的信息,就可以调用系统未公开的函数。 • 如果您调用LoadLibrary函数加载库,就必须再调用GetProcAddress函数来得到每一个您想调用的函数的地址,GetProcAddress会在动态链接库中查找函数的入口地址。由于多余的步骤,这样您的程序执行起来会慢一点,但是并不明显。 • 明白了LoadLibrary函数的优缺点,下面我们就来看看如何产生一个动态链接库。下面的代码是一个动态链接库的框架: 例子:见光盘FirstWindow15中的skeleton #include "windows.h" #include "tchar.h" TCHAR AppName[] = _T("DLL Skeleton"); TCHAR HelloMsg[] = _T("Hello, you're calling a function in this DLL"); TCHAR LoadMsg[] = _T("The DLL is loaded"); TCHAR UnloadMsg[] = _T("The DLL is unloaded"); TCHAR ThreadCreated[] = _T("A thread is created in this process"); TCHAR ThreadDestroyed[] = _T("A thread is destroyed in this process"); void TestHello() { MessageBox(NULL,HelloMsg,AppName,MB_OK); } BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { switch(fdwReason) { case DLL_PROCESS_ATTACH: MessageBox(NULL,LoadMsg,AppName,MB_OK); break; case DLL_PROCESS_DETACH: MessageBox(NULL,UnloadMsg,AppName,MB_OK); break; case DLL_THREAD_ATTACH: MessageBox(NULL,ThreadCreated,AppName,MB_OK); break; case DLL_THREAD_DETACH: MessageBox(NULL,ThreadDestroyed,AppName,MB_OK); break; } return TRUE; } ;------------------------------------------------------------------------------------- ; ;------------------------------------------------------------------------------------- LIBRARY skeleton EXPORTS TestHello 分析: 上面是一个动态链接库的框架,每一个DLL必须有一个入口点函数,WINDOWS每一次在做下面的动作时会调用该入口点函数: 当动态链接库被加载时 • 当动态链接库卸载时 • 同一进程的线程生成时 • 同一进程的线程退出时 • BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) { switch(fdwReason) { case DLL_PROCESS_ATTACH: MessageBox(NULL,LoadMsg,AppName,MB_OK); break; case DLL_PROCESS_DETACH: MessageBox(NULL,UnloadMsg,AppName,MB_OK); break; case DLL_THREAD_ATTACH: MessageBox(NULL,ThreadCreated,AppName,MB_OK); break; case DLL_THREAD_DETACH: MessageBox(NULL,ThreadDestroyed,AppName,MB_OK); break; } return TRUE; } hInstDLL是该动态链接库模块的句柄。它和进程的实例句柄不一样。如果您以后要用,可以保存它,因为以后再要获得它不容易。 根据不同的时机,reason传入的值可能是下面的四个值中的一个: DLL_PROCESS_ATTACH 动态链接库第一次插入进程的地址空间时。当传入的参数是该值时,您可以做一些初始化的工作。 • DLL_PROCESS_DETACH 动态链接库从进程的地址空间卸出时。您可以在此做一些清理的工作。譬如:释放内存等。 • DLL_THREAD_ATTACH 新线程生成。 • DLL_THREAD_DETACH 线程销毁。 • 如果想要库中的代码继续执行,返回TRUE,否则返回FALSE,那样动态链接库就不会加载了。譬如:您想分配一块内存,如果不成功的话就退出,这时您就可以返回FALSE。那样动态链接库就不会加载了。 您可以加入的函数,它们的位置并不重要,把它们放在入口点函数的前面或后面都可以。只是如果您想要它们能被其它的程序调用的话,就必须把它们的名字放到模块定义文件(.def)中去。 .def文件,动态链接库在它们自己的编译过程就需要,而不只是提供给其它要引用它的程序参考。他们如下: LIBRARY Skeleton EXPORTS TestHello 第一行是必须的。LIBRARY 定义了DLL的模块名称。它必须和动态链接库的名称相同。 EXPORTS关键字告诉链接器该DLL的引出函数,也就是其它程序可以调用的函数。举个例子:其它的程序想要调用函数TestHello ,我们就把它放到EXPORTS中。 在vc中我们把添加到skeleton工程中,在您编译链接好后,链接器会生成.lib 和.dll文件。前者是引入库,当其它的程序要调用您的动态链接库中的函数时就需要该引入库(引入库在动态库的隐式调用中有用,在动态库的显式调用中,没用),以便把必要的信息加入到其可执行文件中去。例如:见光盘usedll1 #include "windows.h" extern void TestHello(); #pragma comment(lib,"")//隐式调用 int _stdcall WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { TestHello(); return 0; } 接下来我们来看看如何使用LoadLibrary函数来加载一个DLL。例如:见光盘usedll2 #include "windows.h" #include "tchar.h" TCHAR LibName[] = _T(""); TCHAR FunctionName[] = _T("TestHello"); TCHAR DllNotFound[] = _T("Cannot load library"); TCHAR AppName[] = _T("Load Library"); TCHAR FunctionNotFound[] = _T("TestHello function not found"); HINSTANCE hLib; typedef void (*pTestHello)(); pTestHello TestHelloAddr; int _stdcall WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { hLib = LoadLibrary(LibName); if(hLib == NULL) MessageBox(NULL,DllNotFound,AppName,MB_OK); else { TestHelloAddr = (pTestHello)GetProcAddress(hLib,FunctionName); if(TestHelloAddr == NULL) MessageBox(NULL,FunctionNotFound,AppName,MB_OK); else TestHelloAddr(); FreeLibrary(hLib); } return 0; } 最后:使用LoadLibrary函数加载动态链接库,可能要自己多做一些工作,但是这种方法确实是提供了许多的灵活性。你可以使用vc自带的工具Dependency来看dll导出的函数。如图: 动态库与静态库的比较 C++ 调用.lib的方法: 一: 隐式的加载时链接,有三种方法 1 LIB文件直接加入到工程文件列表中 在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中"Add Files to Project"菜单,在弹出的文件对话框中选中要加入DLL的LIB文件。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..lib.h"即可(没有头文件当然就不用了)。 2 设置工程的 Project Settings来加载DLL的LIB文件 打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件,如(或者lib文件的路径,包括文件名)。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..lib.h"即可(没有头文件当然就不用了)。 3 通过程序代码的方式 加入预编译指令#pragma comment (lib,"*.lib"),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如;在Release方式下,产生的LIB文件是Release版本,如。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..lib.h"即可(没有头文件当然就不用了)。 当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明 二, 显式的运行时链接 ,(我用的是此方法) 隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。这种方法编程具体实现步骤如下: ①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。 ②使用GetProcAddress函数得到要调用DLL中的函数的指针。 ③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。 例:在应用程序中调用dll文件 ——在应用程序中要首先装入dll后才能调用导出表中的函数,例如用mfc 创建基于对话框的工程test,并在对话框上放置"load"按钮,先添加装载代码。 1.首先在的首部添加变量设置代码: //设置全局变量glibsample用于存储dll句柄 HINSTANCE glibsample=null; //如果定义成HANDLE类型,则出错 //第二个变量showme是指向dll库中showme()函数的指针 typedef int(* Showme)(void); Showme showme; 2.利用classwizard为"load"按钮添加装载dll的代码 void ctestdlg::onloadbutton() { //要添加的代码如下 if(glibsample!=NULL) { AfxMessageBox("the has already been load."); return; } //装载,未加路径,将在三个默认路径中寻找录:windowssystem; //(2)dos中path所指出的任何目录; //(3)程序所在的目录; glibsample=Loadlibrary(""); //返回dll中showme()函数的地址 showme=(Showme)GetProcAddress(glibsample,"showme"); 的系统目 (1)windows 本文来自CSDN博客,转载请标明出处:file:///D:/Study/VC/静态和动态库文件/vc调用 静态链接库LIB和动态链接库DLL的区别 1.什么是静态连接库,什么是动态链接库 静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。静态链接库与静态链接库调用规则总体比较如下。 对于静态链接库(比较简单): 首先,静态链接库的使用需要库的开发者提供生成库的.h头文件和.lib文件。 生成库的.h头文件中的声明格式如下: extern "C" 函数返回类型 函数名(参数表); 在调用程序的.cpp源代码文件中如下: #include "..lib.h" #pragma comment(lib,"..") //指定与静态库一起链接 第二,因为静态链接库是将全部指令都包含入调用程序生成的EXE文件中。因此如果用的是静态链接库,那么也就不存在“导出某个函数提供给用户使用”的情况,要想用就得全要!要不就都别要!:) 对于动态链接库: 动态链接库的使用需要库的开发者提供生成的.lib文件和.dll文件,或者只提供dll文件。 首先我们必须先注意到DLL内的函数分为两种: (1)DLL 导出函数,可供应用程序调用; (2)DLL 内部函数,只能在 DLL 程序使用,应用程序无法调用它们。 因此调用程序若想调用DLL中的某个函数就要以某种形式或方式指明它到底想调用哪一个函数。 几种从DLL文件导出函数的方法 使用 DEF 文件从 DLL 导出 模块定义 (.def) 文件是包含一个或多个描述 DLL 各种属性的 Module 语句的文本文件。如果不使用 __declspec(dllexport) 关键字导出 DLL 的函数,则 DLL 需要 .def 文件。 .def 文件必须至少包含下列模块定义语句: 文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。 EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。当指定序号值时,序号值的范围必须是从 1 到 N,其中 N 是 DLL 导出函数的个数。如果希望按序号导出函数,请参见按序号而不是按名称从 DLL 导出函数以及本主题。 例如,包含实现二进制搜索树的代码的 DLL 看上去可能像下面这样: LIBRARY BTREE EXPORTS Insert @1 Delete @2 Member @3 Min @4 如果使用 MFC DLL 向导创建 MFC DLL,则向导将为您创建主干 .def 文件并将其自动添加到项目中。添加要导出到此文件的函数名。对于非 MFC DLL,必须亲自创建 .def 文件并将其添加到项目中。 如果导出 C++ 文件中的函数,必须将修饰名放到 .def 文件中,或者通过使用外部“C”定义具有标准 C 链接的导出函数。如果需要将修饰名放到 .def 文件中,则可以通过使用 DUMPBIN 工具或 /MAP 链接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。如果将 Visual C++ 编译器产 生的修饰名放到 .def 文件中,则链接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .def 文件中的导出名相匹配。 如果生成扩展 DLL 并使用 .def 文件导出,则将下列代码放在包含导出类的头文件的开头和结尾: #undef AFX_DATA #define AFX_DATA AFX_EXT_DATA // #undef AFX_DATA #define AFX_DATA 这些代码行确保内部使用的 MFC 变量或添加到类的变量是从扩展 DLL 导出(或导入)的。例如,当使用 DECLARE_DYNAMIC 派生类时,该宏扩展以将 CRuntimeClass 成员变量添加到类。省去这四行代码可能会导致不能正确编译或链接 DLL,或在客户端应用程序链接到 DLL 时导致错误。 当生成 DLL 时,链接器使用 .def 文件创建导出 (.exp) 文件和导入库 (.lib) 文件。然后,链接器使用导出文件生成 DLL 文件。隐式链接到 DLL 的可执行文件在生成时链接到导入库。 请注意,MFC 本身使用 .def 文件从 导出函数和类。 使用 _declspec(dllexport) 从 DLL 导出 Microsoft 在 Visual C++ 的 16 位编译器版本中引入了 _export,使编译器得以自动生成导出名并将它们放到一个 .lib 文件中。然后,此 .lib 文件就可以像静态 .lib 那样用于与 DLL 链接。


发布评论