__declspec(dllexport)引申的相关知识

来源:互联网 发布:python调用js文件 编辑:程序博客网 时间:2024/04/30 08:54

这些都是菜鸟在各个网上blog中找到的,在这里记录一下为了以后自己继续学习,希望对大家也能有所帮助~


在Windows DLL编程时,可使用__declspec(dllimport)关键字导入函数或者变量。
 
函数的导入:
     当你需要使用DLL中的函数时,往往不需要显示地导入函数,编译器可自动完成。但如果你显示地导入函数,编译器会产生质量更好的代码。由于编译器确切地知道了一个函数是否在一个DLL中,
     它就可以产生更好的代码,不再需要间接的调用转接。
     Win32的PE格式(Portable Executable Format)把所有导入地址放在一个导入地址表中。下面用一个具体实例说明使用__declspec(dllimport)导入函数和不使用的区别:
    
     假设func是一个DLL中的函数,现在在要生成的.exe的main函数中调用func函数,并且不显示地导入func函数(即没有:__declspec(dllimport)),代码示例如下:
      int main()
      {
          func();
      }
 
      编译器将产生类似这样的调用代码:  
   call func
     然后,链接器把该调用翻译为类似这样的代码:
  call 0x40000001       ; ox40000001是"func"的地址
     并且,链接器将产生一个Thunk,形如:
  0x40000001: jmp DWORD PTR __imp_func
 这里的imp_func是func函数在.exe的导入地址表中的函数槽的地址。然后,加载器只需要在加载时更新.exe的导入地址表即可。
     而如果使用了__declspec(dllimport)显示地导入函数,那么链接器就不会产生Thunk(如果不被要求的话),而直接产生一个间接调用。因此,下面的代码:
   
      __declspec(dllimport) void func1(void);
      void main(void)
      {
          func1();
      }
  
  将调用如下调用指令:
      call DWORD PTR __imp_func1
 
     因此,显示地导入函数能有效减少目标代码(因为不产生Thunk)。另外,在DLL中使用DLL外的函数也可以这样做,从而提高空间和时间效率。
 
变量的导入:
    与函数不同的是,在使用DLL中的变量时,需要显示地导入变量。使用__declspec(dllimport)关键字导入变量。若在DLL中使用.def导出变量,则应使用DATA修饰变量,而不是使用已经被遗弃的CONSTANT。
    因为CONSTANT可能需要使用指针间接访问变量,不确定什么时候会出问题。
   
    先看代码:以下是在dev-c++里建立自已的dll时的dll.h里面的代码,这里面有一个:_declspec(dllexport)

  #ifndef _DLL_H_
  #define _DLL_H_//防重复定义

  #if BUILDING_DLL
  # define DLLIMPORT __declspec (dllexport)
  #else /* Not BUILDING_DLL */
  # define DLLIMPORT __declspec (dllimport)
  #endif /* Not BUILDING_DLL */

  DLLIMPORT void HelloWorld (void);

  #endif /* _DLL_H_ */

    上面代码里面的_delcspce(dllexport)被定义为宏,这样可以提高程序的可读性!这个的作是是将函数定义为导出函数,也就是说这个函数要被包含这个函数的程序之外的程序调用!本语句中就是:void Helloword(void):


摘自msdn:
    在 32 位编译器版本中,可以使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 将导出指令添加到对象文件

    若要导出函数,__declspec(dllexport) 关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:

 __declspec(dllexport) void __cdecl Function1(void);
    若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:

 class __declspec(dllexport) CExampleExport : public CObject

    { ... class definition ... };生成 DLL 时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将 __declspec(dllexport) 添加到头文件中的声明。
    若要提高代码的可读性,请为 __declspec(dllexport) 定义一个宏并对正在导出的每个符号使用该宏:

 #define DllExport   __declspec( dllexport ) __declspec(dllexport) 将函数名存储在 DLL 的导出表中。

    若要确定用于导出函数的方法(.def 文件或 __declspec(dllexport) 关键字),请回答下列问题:

 是否要一直添加附加的导出函数?

 谁要使用 DLL?例如,是由许多无法重新生成的可执行文件使用的第三方 DLL 还是仅由可以轻松重新生成的应用程序使用的 DLL?

使用 .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,还必须重新生成应用程序,因为如果使用不同版本的编译器进行重新编译,则导出的 C++ 函数的修饰名可能会发生变化。