这里总结一下vs下创建dll并使用dll导出函数的方法,当然方法还有很多,找出一个自己比较习惯的方法就好!
- 隐式加载DLL方式
- 创建DLL工程
文件->新建->项目->visual c++->win32->win32控制台应用程序(win32项目也可以)
填写项目名称MyDLL->确定->下一步->DLL(附加选项 对空项目打钩)->完成。
到这里DLL工程就创建完毕了,下面新建两个文件MyDLL.cpp和MyDLL.h。
MyDLL.cpp内容如下:
1 #include <iostream> 2 using namespace std; 3 #include "MyDLL.h" 4 5 int Add(int &a,int &b) 6 { 7 return a+b; 8 }
MyDLL.h内容如下:
1 #pragma once 2 #define DLL_EXPORT __declspec(dllexport) 3 4 extern "C" DLL_EXPORT int Add(int &a,int &b);
点击生成,则dll文件就生成了,vs2008不能直接生成lib文件,这个时候就需要我们在建立dll工程的时候 再新建一个def文件,默认生成然后重新生成就能够得到lib文件了
2. 调用DLL文件导出的接口函数
有一点应当清楚:应用程序如果想要访问某个DLL中的函数,那么这个函数必须是已经被导出的函数。为了查看一个DLL中的导出函数,可以利用visual studio提供的命令行工具:Dumpbin来实现。Dumpbin.exe 在vs安装目录下的VC98\bin中。
如果想要查看一个DLL提供的导出函数,可以使用/EXPORTS选项来运行Dumpbin命令。因此,在DLL文件所在的目录下,在命令行提示符后输入下述命令并回车:
dumpbin -exports MyDLL.dll
如果能看见相关函数的信息,则说明有导出函数,否则DLL文件没有导出函数。
为了让DLL导出一些函数,需要在每一个将要被导出的函数前面添加标识符:
_declspec (dllexport)
例如上面的可以修改为:
1 _declspec(dllexport) int add(int &a,int &b) 2 { 3 return a+b; 4 }
在上面的DLL工程中,编译后生成了几个新文件,MyDLL.lib文件就是引入库文件,该文件中保存的是MyDLL.dll中导出的函数和变量的符号名:MyDLl.exp文件是一个输出文件库,这里改文件并不重要。
在上面创建的DLL工程中,再新建一个测试项目来直接调用DLL导出函数。
右键解决方案->添加->新建项目->建立一个空的常规工程 testMyDLL,下面新建两个文件testMyDLL.cpp和testMyDLL.h
testMyDLL.cpp内容如下:
1 #include "testMyDLL.h" 2 #pragma comment(lib,"..\\debug\\MyDLL.lib") 3 4 #include <iostream> 5 using namespace std; 6 7 int main() 8 { 9 int a =3; 10 int b =2; 11 cout<<Add(a,b)<<endl; 12 13 getchar(); 14 15 }
testMyDLL.h内容如下:
1 #pragma once 2 #define DLL_EXPORT __declspec(dllexport) 3 4 extern "C" DLL_EXPORT int Add(int &a,int &b);
这里需要注意testMyDLL.cpp文件中调用lib的这句话:
#pragma comment(lib,"..\\debug\\MyDLL.lib") //指定与静态库链接,即生成的.obj文件与MyDLL.lib一起链接
这里需要指明lib所在的文件夹,当然我们也可以在生成dll的MyDLL工程中,指定lib和dll文件的输出路径,直接到testMyDLL工程下。如果不用#pragma comment 方式我们也可以采取类似配置opencv的方法。
或者直接复制MyDLL.lib和MyDLL.dll文件到testMyDLL工程所在目录下,这个文件中就包含了MyDLL.dll中导出的函数的符号名。然后在testMyDLL工程中,选择【Project\Settings】菜单命令,打开工程设置对话框,选择link选项卡,在“Object/library modules"选项编辑框中输入:MyDLL.lib。当应用程序需要调用某个动态链接库提供的函数时,在程序链接时只需要包含改链接库提供的输入哭文件就可以了。在引入库文件中并没有包含实际的代码,它只是用来为链接程序提供必要的信息,以便在可执行文件中建立动态链接库时需要用到的重定位表。
当应用程序运行时,系统将为它分配一个4GB的地址空间,然后加载模块会分析该应用程序的输入信息,从中找到该程序将要访问的动态链接库信息,然后在用户机器上搜索这些动态链接库,今儿加载它们。搜索的顺序依次是:
- 程序的的执行目录
- 当前目录
- 系统目录 C:\WINNT\system32,C:\WINNT\system,C:\WINNT
- path环境变量中所列出的路径
为了让可执行文件能够正常运行,就必须加载模块能够找到该应用程序所需的所有动态链接库,如果加载模块未能找到其中的一个动态链接库,可执行文件就将终止运行。在实际编程中,可以把这些动态链接库放置在加载模块将要搜索的目录中的任一目录下。对本例来说,可以吧MyDLL.dll放置在testMyDLL工程所在目录下,然后通过通过VS按下F5执行。如果想通过直接双击testMyDLL.exe文件执行,则应当把MyDLL.dll文件复制到testMyDLL.exe所在目录下。
- Depends工具
为了查看一个可执行模块依赖的动态链接库,除了利用上面介绍的Dumpbin命令以外,还可以利用Visual studio提供的一个图形化工具:Depends来实现。此处不再赘述。
- _declspec(dllimport)声明外部函数
除了使用extern关键字表明函数是外部定义的之外,这里我们使用了标识符:_declspec(dllimport)来表明函数是从动态链接库中引入的。与使用extern关键字这种方式相比,使用后者时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码。因此,如果调用的函数来自于动态链接库,应该采用这种方式声明外部函数。
- 补充
一个DLL实现之后,通常都会交给客户程序,以便后者能访问该DLL。但是客户端程序如何知道该DLL中有哪些导出函数呢?对上述DLL例子,因为该程序使用的动态链接库都是我们自己写的,所以我们清楚该DLL中的导出函数。如果DLL程序的是闲着和使用者不是同一个人,那么后者只能通过前面介绍的一些工具来查看该DLL提供的导出函数,并猜测这些函数的原型。这种做法对DLL的调用不是很方便。通常在编写动态链接库时,都会提供一个头文件,在此文件中提供DLL导出的函数原型的声明,以及函数的有关注释文档。
我们这里的额MyDLL.h头文件是给该DLL的客户端,即调用该DLL的程序使用的,因此在声明Add函数时,使用的是”dllimport“关键字,向客户端给表明它们是从动态链接库中导入的。如果MyDLL.h和MyDLL.cpp不在同一目录下的话,使用下面语句包含:
#include "..\\MyDLL\\MyDLL.h"
通过上述方法,在发布DLL动态链接库的同时,可以将MyDLL.h这个头文件一起提供给使用者。(我们知道,在程序编译时,头文件不参与编译,源文件单独编译。)
- 从DLL中导出C++类
上面我们介绍了如何从动态链接库中导出函数,一共其他程序使用。实际上,在一个动态链接库中还可以导出一个C++类。为了实现这样的功能,仍以MyDLL为例,首先打开MyDLL工程,然后在MyDLL.h中添加下列代码:
class declspec(dllexport) Point { public: void output(int x,int y); };
为了从动态链接库中导出一个类,需要在class关键字和类名之间加入导出标识符,这样就可以导出整个类来了。但是,应该注意,在访问该类的时候,仍受限于函数自身的访问权限。也就是说,如果该类的某个函数访问权限是private,那么外部程序仍然无法访问这个函数。
注意:当重新生成dll和lib文件时,我们要注意更新我们复制的dll和lib文件,因为机器搜索动态链接库的顺序,所以当未能及时更新优先搜索目录下的dll和lib文件时,容易发生编译链接错误。
- 解决名字改编问题
C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改变规则不一样,因此改编后的名字是不一样的。这样如果利用不同编译器分别生成DLL和访问该DLL的客户端程序的话,后者在访问该DLL的导出函数时就会出现问题。例如,如果用C++语言编写了一个DLL,那么用C语言编写的客户端程序访问该DLL中的函数时就会出现问题。因为后者将使用函数原始名称来调用DLL中的函数,而C++编译器已经对该名称进行了改编,所以C语言编写的客户端程序就找不到所需的DLL导出函数。对上述MyDLL.dll和testMyDLL.exe程序来说,因为采用的是同一编译器,后者知道该DLL中导出函数改编后的名称,所以调用时没有出现问题。
鉴于以上原因,我们希望动态链接库文件在编译时,导出函数的名称不要发生改变。为了实现这一目的,在定义导出函数时,需要加上限定符:extern "C"。注意双引号间的C一定要大写。
利用限定符:extern "C" 可以解决C++和C之间相互调用时函数命名的问题。但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。因此要屏蔽掉上面的类。
另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符:extern "C" ,该函数的名字仍然发生改变。为了说明这种情况,再次在函数声明前,_declspec(dllexport) int 后 函数名称前添加_stdcall关键字。如:
_declspec(dllexport) int _stdcall Add(int &a,int &b);
在MyDLL.cpp中也要添加_stdcall关键字:
int _stdcall Add(int &a,int &b) { return a+b; }
如果没有添加_stdcall 关键字,那么函数额调用约定就是C调用约定。
生成dll文件,利用Dumpbin命令的exports选项查看该动态链接的导出情况,发现这时的导出函数名仍然发生了变化。也即是说,如果函数的调用约定发生了变化,即使在声明这些导出函数时使用了extern”C"关键字,它们的名字仍然发生改编。我们知道,C语言和Delphi语言使用的调用标准是不一样的,后者使用的是pascal调用约定,即标准调用约定:_stdcall。
如果现在需要利用C语言编写一个DLL,然后利用Delphi编写的客户端程序访问的话,那么在导出函数时,应指定使用标准的的函数调用约定。但是,这仍会出现问题,因为这时函数名称会发生改编。这种情况下,可以通过一个称为模块定义文件(DEF)的方式来解决名字改编问题。
//后续知识:
- 显式加载方式加载DLL
- LoadLibary
- 调用约定
- 根据序号访问DLL中的导出函数
- DllMain函数
- MFC DLL
//*********
//下面是来自CSDN上的相关资料
DLL 开发
本节介绍您在开发自己的 DLL 时应该考虑的问题和要求。
DLL 的类型
当您在应用程序中加载 DLL 时,可以使用两种链接方法来调用导出的 DLL 函数。这两种链接方法是加载时动态链接和运行时动态链接。
加载时动态链接
在加载时动态链接中,应用程序像调用本地函数一样对导出的 DLL 函数进行显式调用。要使用加载时动态链接,请在编译和链接应用程序时提供头文件 (.h) 和导入库文件 (.lib)。当您这样做时,链接器将向系统提供加载 DLL 所需的信息,并在加载时解析导出的 DLL 函数的位置。
运行时动态链接
在运行时动态链接中,应用程序调用 LoadLibrary 函数或 LoadLibraryEx 函数以在运行时加载 DLL。成功加载 DLL 后,可以使用GetProcAddress 函数获得要调用的导出的 DLL 函数的地址。在使用运行时动态链接时,无需使用导入库文件。
下面的列表说明了有关何时使用加载时动态链接以及何时使用运行时动态链接的应用程序条件:
- 启动性能
如果应用程序的初始启动性能很重要,则应使用运行时动态链接。 - 易用性
在加载时动态链接中,导出的 DLL 函数类似于本地函数。这使您可以方便地调用这些函数。 - 应用程序逻辑
在运行时动态链接中,应用程序可以分支,以便按照需要加载不同的模块。在开发多语言版本时,这一点很重要。
DLL 入口点
在创建 DLL 时,可以有选择地指定入口点函数。当进程或线程将它们自身附加到 DLL 或者将它们自身从 DLL 分离时,将调用入口点函数。您可以使用入口点函数根据 DLL 的需要来初始化数据结构或者销毁数据结构。此外,如果应用程序是多线程的,则可以在入口点函数中使用线程本地存储 (TLS) 来分配各个线程专用的内存。下面的代码是一个 DLL 入口点函数的示例。
1 BOOL APIENTRY DllMain( 2 HANDLE hModule, // Handle to DLL module 3 DWORD ul_reason_for_call, // Reason for calling function 4 LPVOID lpReserved ) // Reserved 5 { 6 switch ( ul_reason_for_call ) 7 { 8 case DLL_PROCESS_ATTACHED: 9 // A process is loading the DLL. 10 break; 11 case DLL_THREAD_ATTACHED: 12 // A process is creating a new thread. 13 break; 14 case DLL_THREAD_DETACH: 15 // A thread exits normally. 16 break; 17 case DLL_PROCESS_DETACH: 18 // A process unloads the DLL. 19 break; 20 } 21 return TRUE; 22 }
当入口点函数返回 FALSE 值时,如果您使用的是加载时动态链接,则应用程序不启动。如果您使用的是运行时动态链接,则只有个别 DLL 不会加载。
入口点函数只应执行简单的初始化任务,不应调用任何其他 DLL 加载函数或终止函数。例如,在入口点函数中,不应直接或间接调用 LoadLibrary 函数或 LoadLibraryEx 函数。此外,不应在进程终止时调用 FreeLibrary 函数。
注意:在多线程应用程序中,请确保将对 DLL 全局数据的访问进行同步(线程安全),以避免可能的数据损坏。为此,请使用 TLS 为各个线程提供唯一的数据。
导出 DLL 函数
要导出 DLL 函数,您可以向导出的 DLL 函数中添加函数关键字,也可以创建模块定义文件 (.def) 以列出导出的 DLL 函数。
要使用函数关键字,您必须使用以下关键字来声明要导出的各个函数:
要在应用程序中使用导出的 DLL 函数,您必须使用以下关键字来声明要导入的各个函数:
通常情况下,您最好使用一个包含 define 语句和 ifdef 语句的头文件,以便分隔导出语句和导入语句。
您还可以使用模块定义文件来声明导出的 DLL 函数。当您使用模块定义文件时,您不必向导出的 DLL 函数中添加函数关键字。在模块定义文件中,您可以声明 DLL 的 LIBRARY 语句和 EXPORTS 语句。下面的代码是一个定义文件的示例。
1 // SampleDLL.def 2 // 3 LIBRARY "sampleDLL" 4 5 EXPORTS 6 HelloWorld
示例 DLL 和应用程序
在 Microsoft Visual C++ 6.0 中,可以通过选择“Win32 动态链接库”项目类型或“MFC 应用程序向导 (dll)”来创建 DLL。
下面的代码是一个在 Visual C++ 中通过使用“Win32 动态链接库”项目类型创建的 DLL 的示例。
1 // SampleDLL.cpp 2 // 3 4 #include "stdafx.h" 5 #define EXPORTING_DLL 6 #include "sampleDLL.h" 7 8 BOOL APIENTRY DllMain( HANDLE hModule, 9 DWORD ul_reason_for_call, 10 LPVOID lpReserved 11 ) 12 { 13 return TRUE; 14 } 15 16 void HelloWorld() 17 { 18 MessageBox( NULL, TEXT("Hello World"), TEXT("In a DLL"), MB_OK); 19 } 20 // File: SampleDLL.h 21 // 22 #ifndef INDLL_H 23 #define INDLL_H 24 25 #ifdef EXPORTING_DLL 26 extern __declspec(dllexport) void HelloWorld() ; 27 #else 28 extern __declspec(dllimport) void HelloWorld() ; 29 #endif 30 31 #endif 32 下面的代码是一个“Win32 应用程序”项目的示例,该示例调用 SampleDLL DLL 中的导出 DLL 函数。 33 34 // SampleApp.cpp 35 // 36 37 #include "stdafx.h" 38 #include "sampleDLL.h" 39 40 int APIENTRY WinMain(HINSTANCE hInstance, 41 HINSTANCE hPrevInstance, 42 LPSTR lpCmdLine, 43 int nCmdShow) 44 { 45 HelloWorld(); 46 return 0; 47 }
注意:在加载时动态链接中,您必须链接在生成 SampleDLL 项目时创建的 SampleDLL.lib 导入库。
在运行时动态链接中,您应使用与以下代码类似的代码来调用 SampleDLL.dll 导出 DLL 函数。
1 ... 2 typedef VOID (*DLLPROC) (LPTSTR); 3 ... 4 HINSTANCE hinstDLL; 5 DLLPROC HelloWorld; 6 BOOL fFreeDLL; 7 8 hinstDLL = LoadLibrary("sampleDLL.dll"); 9 if (hinstDLL != NULL) 10 { 11 HelloWorld = (DLLPROC) GetProcAddress(hinstDLL, "HelloWorld"); 12 if (HelloWorld != NULL) 13 (HelloWorld); 14 15 fFreeDLL = FreeLibrary(hinstDLL); 16 } 17 ...
当您编译和链接 SampleDLL 应用程序时,Windows 操作系统将按照以下顺序在下列位置中搜索 SampleDLL DLL:
- 应用程序文件夹
- 当前文件夹
- Windows 系统文件夹
注意:GetSystemDirectory 函数返回 Windows 系统文件夹的路径。 - Windows 文件夹
注意:GetWindowsDirectory 函数返回 Windows 文件夹的路径。
.NET Framework 程序集
在引入 Microsoft .NET 和 .NET Framework 以后,大多数与 DLL 相关联的问题已经通过使用程序集消除了。程序集是在 .NET 公共语言运行库 (CLR) 控制之下运行的逻辑功能单元。程序集实际上是作为 .dll 文件或 .exe 文件存在的。但是,在内部,程序集与 Microsoft Win32 DLL 大不相同。
程序集文件包含程序集清单、类型元数据、Microsoft 中间语言 (MSIL) 代码和其他资源。程序集清单包含程序集元数据,以提供使程序集成为自描述程序集所需的全部信息。程序集清单中包含以下信息:
- 程序集名称
- 版本信息
- 区域性信息
- 强名称信息
- 程序集文件列表
- 类型引用信息
- 引用和依赖程序集信息
程序集中包含的 MSIL 代码是无法直接执行的,需要通过 CLR 来执行。默认情况下,当您创建一个程序集时,该程序集是应用程序专有的。要创建共享程序集,需要为该程序集分配强名称,然后在全局程序集缓存中发布该程序集。
下表说明了程序集的一些功能,并将其与 Win32 DLL 的功能进行了比较:
- 自描述
当您创建程序集时,CLR 运行该程序集所需的全部信息都包含在程序集清单中。程序集清单包含一个依赖程序集列表。因此,CLR 可以维护一组在应用程序中使用的一致的程序集。在 Win32 DLL 中,当您使用共享 DLL 时,无法维护应用程序中使用的一组 DLL 之间的一致性。 - 版本控制
在程序集清单中,版本信息由 CLR 记录和实施。另外,可以通过版本策略来实施版本特定用法。在 Win32 DLL 中,无法由操作系统实施版本控制。相反,您必须确保 DLL 向后兼容。 - 并行部署
程序集支持并行部署。一个应用程序可以使用一个版本的程序集,而另一个应用程序可以使用另一不同版本的程序集。从 Windows 2000 开始,通过将 DLL 放置到应用程序文件夹中支持并行部署。另外,Windows 文件保护能够防止系统 DLL 被未经授权的代理改写或替换。 - 独立和隔离
通过使用程序集开发的应用程序可以是独立的,并且与计算机中正在运行的其他应用程序隔离。这一特性有助于创建零干扰安装。 - 执行
程序集在程序集清单所提供的并且由 CLR 控制的安全权限下运行。 - 语言无关性
可以通过使用任何一种受支持的 .NET 语言来开发程序集。例如,可以在 Microsoft Visual C# 中开发程序集,然后在 Microsoft Visual Basic .NET 项目中使用该程序集。