Visual Studio 2010 中的DLL基础知识

来源:互联网 发布:淘宝商品土鸡品牌 编辑:程序博客网 时间:2024/05/19 13:45

对于DLL的链接,新手还是会有很大麻烦,这里把一些基础知识列出来,本文所有内容源于MSDN文档

从DLL中导出函数

DLL和一般可执行程序的区别:

  主要是DLL只能在系统中运行一个实例并且它无法拥有一些一般程序有的事物,比如栈,全局内存,文件句柄和消息队列等等。同时DLL文件包含一个导出表(Exports table),这个导出表包含了dll导出给其他程序用的所有函数。它们是这个DLL的进入点。也只有在这个导出表上的函数才可以被其他程序执行。

  P.S. 可以用DUMPBIN工具来查看dll的导出表。

从DLL导出函数有两种方法:

  1. 创建一个模块定义文件.def,这种情况适用于当你希望使用序号来导出函数时
  2. 在函数定义中使用 __declspec(dllexport)关键字

  在导出函数是请确保使用__stdcall调用习惯http://msdn.microsoft.com/en-us/library/zxk0tw93.aspx

dllexport和dllimport

dllexport 和dllimport是微软自己的C++标识符,我们可以用它来导入和导出函数,数据以及对象,用法如下:

__declspec( dllimport ) declarator__declspec( dllexport ) declarator

使用dllexport声明的函数无需再使用.def文件。

dll接口指的是dll中所有已知的被导出的函数和数据的引用。 即所有被声明为dllimport或者dllexport的东西。但是dllexport和dllimport是有区别的。

对于定义,只能被声明为dllexport。比如

__declspec( dllimport ) int func() {   // Error; dllimport                                       // prohibited on definition.   return 1;}
__declspec( dllimport ) int i = 10;  // Error; this is a                                     // definition.

这里具有语义错误,因为dllimport只能用于导入声明而不能用于导入定义

__declspec( dllexport ) int i = 10;     // Okay--export definition

但是dllexport则可以导出定义。也就是所,dllexport表示是一个定义而dllimport表示是一个声明。如果需要使用dllexport来表示声明,就必须在前面加一个extern

#define DllImport   __declspec( dllimport )#define DllExport   __declspec( dllexport )extern DllImport int k; // These are both correct and imply aDllImport int j;        // declaration.

MSDN上的这个文档给出了一些更复杂的例子,由于里面有一些隐含的意义所以刚看的时候很容易搞混了,我注释一下:

static __declspec( dllimport ) int l; // Error; not declared extern.                                      // 错误的原因是因为加上了static 以后int l;实际上是一个定义而非声明,它被默认初始化为0了void func() {    static __declspec( dllimport ) int s;  // Error; not declared                                           // extern.                                           // 错误原因同上    __declspec( dllimport ) int m;         // Okay; this is a                                            // declaration.    __declspec( dllexport ) int n;         // Error; implies external                                           // definition in local scope.                                           // 错误的原因是这个在一个函数的作用域内尝试导出一个定义    extern __declspec( dllimport ) int i;  // Okay; this is a                                           // declaration.                                           // dllimport加不加extern都是表示声明    extern __declspec( dllexport ) int k;  // Okay; extern implies                                           // declaration.    __declspec( dllexport ) int x = 5;     // Error; implies external                                           // definition in local scope.}


网上搜了一下,似乎对dllimport和dllexport的讨论都满是困惑,其实,即使翻了msdn的文档也不一定就看得懂。从他们提供的代码里我们可以清楚的看出这两个关键字的用法的区别:
这个是一个dll文件中准备要导出的函数Test

// lib_link_input_1.cpp// compile with: /LD__declspec(dllexport) int Test() {   return 213;}


这个是使用那个函数的程序:

// lib_link_input_2.cpp// compile with: /EHsc lib_link_input_1.lib__declspec(dllimport) int Test();#include <iostream>int main() {   std::cout << Test() << std::endl;}


所以,由此可见,其实dllimport和dllexport的区别就是,后者是dll中要导出给别人用的东西所需要的修饰符。而前者则是要从别的dll那里导入东西来用的时候用的修饰符。

关于dllimport的详细解释可以参考这篇文章:http://msdn.microsoft.com/en-us/library/8fskxacy.aspx

此外,值得注意的是dllimport对于函数声明是可选的,不过显式的使用它能加速编译,代码的可读性也更好,具体的细节在这篇文档里有说明。而对于dll的公共数据和对象则必须显式的指明dllimport。这里有一个很好的技巧来使得dll和客户程序可以使用同一个头文件:

#ifdef _EXPORTING   #define CLASS_DECLSPEC    __declspec(dllexport)#else   #define CLASS_DECLSPEC    __declspec(dllimport)#endifclass CLASS_DECLSPEC CExampleA : public CObject{ ... class definition ... };

通过定义这个_EXPORTING预处理符号,使得编译器可以明白现在是在编译dll还是导入定义

在类中使用dllimport和dllexport

要对类使用这两个属性的话有几种做法,第一种是直接dllexport整个类:

#define DllExport   __declspec( dllexport )class DllExport C {   int i;   virtual int func( void ) { return 1; }};

此时,类的所有成员函数和静态数据都将被导出,在这种情况下是不可以再对类成员显式的定义dllexport或者dllimport的。因此,除了纯虚函数外其他的所有成员都必须提供定义。(除了纯虚析构函数,因为它总是被基类的析构函数调用,所以必须提供一个定义)

使用dllimport来导入一个类声明的话,它的所有成员函数和静态成员都被导入。

可导出的类的继承问题

所有的可导出类的基类应该都要是可导出的,否则的话,编译器会产生一个警告(不是错误)。此外,这个类的所有可访问的成员也都必须是可导出的,这样才可以允许你dllexport一个你自己实现的dllimport的类的子类

链接方式的选择

可执行文件链接DLL的方式有两种:

  • Implicity linking (隐式)
  • Explicit linking (显式)

对于隐式链接,程序使用DLL链接到DLL创建者提供的一个用于导入的库(.lib)文件。系统在应用程序使用这个DLL的时候加载它。这种情况下应用程序调用这个DLL的导出的函数就好像是在调用程序内部的函数一样

对于显式链接,程序好似用DLL必须先显式的加载和卸载DLL并且访问DLL的导出的函数。因此应用程序只能通过函数指针来调用DLL中导出的函数。

对于任何DLL两种调用都可以使用。大部分程序都会使用隐式链接,因为这种方式最容易使用。下面开始先说明一下如何选择两种链接方式

隐式链接

DLL的创建者必须提供一个.LIB文件来作为导入库提供给应用程序的进行外部引用。导入库仅包含加载DLL的代码和实现DLL函数调用的代码,因此程序在导入库中找到外部函数后就会通知连接器说:这个函数的代码在DLL中,去解析对DLL的外部引用。而链接器就会在可执行文件中添加信息通知系统在进程启动时到什么地方去找DLL代码。系统找到DLL以后就会把DLL模块映射到进程的地址空间去。

如果这个DLL有初始化代码的入口点函数的话,操作系统还会先调用这个函数。系统还会修改进程的可执行代码以提供DLL函数的其实地址。

显式链接

显式链接的两个主要缺点是:

  • 如果DLL具有DLLMain这个入口点函数,操作系统在调用LoadLibrary的线程中会调用此函数,但是如果之后没有调用FreeLibrary,那么下次有其他程序再调用LoadLibrary的时候就不会调用这个入口点函数了。所以,如果DLL使用入口点函数来为进程的每个线程初始化的话,显式链接就会出问题。
  • 如果DLL将静态作用域数据声明为 __declspec(thread)的话,显式链接会导致保护错误。因此创建DLL时必须避免使用线程本地存储区。

所以一般在下面这些情况的时候才使用显式链接

  • 知道运行时才知道DLL的名称
  • 启动时未找到DLL的话系统会终止使用隐式链接的进程。但是不会终止显式的。
  • 隐式链接的DLL中如果有失败的DllMain函数的话进程也会被干掉。同样显式的不会
  • 程序在运行时会加载所有隐式的DLL,因此会导致程序启动很慢。

DLL的查找顺序

Windows查找DLL的顺序如下:

  1. 进程运行目录
  2. 当前目录
  3. Windows system 目录,GetSystemDirectory函数返回此目录
  4. Windows 目录,GetWindowsDirectory函数返回此目录
  5. PATH环境变量中列出的目录

特别注意的是LIBPATH环境变量是没用的

 

隐式链接的步骤

要进行隐式链接需要得到这3个东西

  1. 一个包含了导出函数的声明或者C++类的头文件,所有的类,函数,数据必须标识为__declspec(dllimport)
  2. 一个用来链接的.lib文件(链接器在DLL编译的时候会生成这个导入库)
  3. 实际的dll文件

具体在VS2010的操作中只需要在下面这个设置中填入lib文件的地址即可:

 

显式链接的步骤

todo: link http://msdn.microsoft.com/en-us/library/784bt7z7.aspx

 

复杂的情况

如果你的两个dll互相导入对方导出的函数,那么就需要使用LIB命令,具体见这里:http://msdn.microsoft.com/en-us/library/kkt2hd12.aspx

原创粉丝点击