分享

vs2005生成DLL

 Qin Hantang 2012-03-09

vs2005生成DLL

(2011-10-23 15:26:19)
标签:

杂谈

分类: 备忘

一、DLL的生成方式

VS2005使用DEF文件来生成DLL与Lib

VS2005使用DEF文件来生成DLL与Lib时,与VC6的设置是不一样的,VC6工程中,只要有DEF文件并将其添加到工程中,VC6就可以自动地生成DLL与其相应的Lib文件了。

但是VS2005不一样,需要指定DEF文件,方法如下:


例如:
把 sqlite3.def 添加到工程中.

选择 工程 > 属性中的链接器,然后找到"输入"这一项. 在 "模块定义文件" 中输入  sqlite3.def

注意: 你需要在 Debug 和 Release 中都输入该项才行.


__declspec(dllexport) 和 *.def文件的比较
文章出处:DIY部落(http://www./course/3_program/c++/cppxl/2008105/147591.html)


一、__declspec(dllexport) 
在 32 位编译器版本中,可以使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 在link时会将导出指令添加到obj文件中,因此不需要使用 .def 文件。当然,即使用了__declspec(dllexport)依然可以使用*.def文件,因为不同编译器对于类的成员函数的name mangling规则不同,可以定义.def文件通过序号调用。为每个dll写def显得很繁杂,目前def使用已经比较少了,更多的是使用__declspec(dllexport)在源代码中定义dll的输出函数。

若要输出类的所有成员:数据or函数,__declspec(dllexport)要放在类名左边声明:
class __declspec(dllexport) Class1{}
如果类没有数据成员,__declspec(dllexport)放在class关键字前声明就会被编译器忽略,就没有lib生成,如下:
__declspec(dllexport) class Class1{}


使用 __declspec(dllexport) 的优缺点(zz)
使用 __declspec(dllexport) 非常方便,因为不必考虑维护 .def 文件和获取导出函数的修饰名。例如,如果您设计的 DLL 供自己控制的应用程序使用,则此方法很适用。如果通过新的导出函数重新生成 DLL,还必须重新生成应用程序,因为如果使用不同版本的编译器进行重新编译,则导出的 C++ 函数的修饰名可能会发生变化。

二、def文件 
其实def文件的功能相当于extern “C” __declspec(dllexport)

def文件中PRIVTATE的作用

The optional keyword PRIVATE prevents entryname from being placed in the import library generated by LINK. It has no 
effect on the export in the image also generated by LINK.用了PRIVATE,生成的lib里没有对应方法或者数据的entryname因此不能被客户隐式调用。

使用 .DEF 文件的优缺点(zz)
在 .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 文件中的导出名相匹配。
二、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版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。
当应用程序对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。
例:在应用程序中调用dll文件

——在应用程序中要首先装入dll后才能调用导出表中的函数,例如用mfc

创建基于对话框的工程test,并在对话框上放置"load"按钮,先添加装载代码。
1.首先在testdlg.cpp的首部添加变量设置代码:

//设置全局变量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 sample.dll has already been load.");

return;

}

//装载sample.dll,未加路径,将在三个默认路径中寻找 (1)windows的系统目录:\windows\system;

//(2)dos中path所指出的任何目录;

//(3)程序所在的目录;


glibsample=Loadlibrary("sample.dll");

//返回dll中showme()函数的地址

showme=(Showme)GetProcAddress(glibsample,"showme");


先看看EXPORTS语法规则:

entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]

 

1.entryname是要导出的函数名或变量名。这是必选项。如果导出的名称与 DLL 中的名称不同,则通过internalname指定 DLL 中导出的名称。例如,如果 DLL 导出函数 func1(),要将它用作 func2(),则应指定:2.@ordinal 允许指定是序号而不是函数名将进入 DLL 的导出表。这有助于最小化 DLL 的大小。.LIB 文件将包含序号与函数之间的映射,这使您得以像通常在使用 DLL 的项目中那样使用函数名。

 

EXPORTS func2=func1

 

此时用Dependency工具查看dll看到的函数名就是func2了,这个很好理解。

 

可选的 NONAME 关键字允许只按序号导出,并减小结果 DLL 中导出表的大小。但是,如果要在 DLL 上使用 GetProcAddress,则必须知道序号,因为名称将无效。

NONAME参数导出的dll和lib函数名测试如下:

用Dependency工具查看dll:

dumpbin -exports use_def_create_dll.lib命令查看lib导入库文件函数名如下:

可选的 PRIVATE 关键字禁止将 entryname 放到由 LINK 生成的导入库中。它对同样是由 LINK 生成的图像中的导出无效。

可以看出NONAME参数导出的dll中就只有序号了,什么含义也不知道,一般情况下就只有开发人员自己知道到底这个方法有什么用,同样,调用时用GetProcAddress调用也必须得知道序号,而真正的函数名写进了lib导入库中,所以如果隐式调用dll,把导入库加到附加依赖项中就可以不用管了,直接按照头文件调用;而PRIVATE参数就刚好相反,函数名写到dll里面去了,lib中就连个序号都没有,这时带着头文件,用GetProcAddress显式调用dll中的函数也是很方便的。

至于DATA参数,测试的时候发现用了跟没用并没有什么区别,暂时等待高手解决……

3.

 

有三种导出定义的方法,按照建议的使用顺序依次为:

1.代码中的 __declspec(dllexport) 关键字

2.def 文件中的 EXPORTS 语句

3LINK 命令的

 

/EXPORT规范

 

 

所有这三种方法可以用在同一个程序中。LINK 在生成包含导出的程序时还创建导入库,除非生成中使用了 .exp 文件。

如何从__declspec(dllexport)和def两种导出函数方法中作出选择,可以从以下的几个方面考虑:

 

如果需要使用导出顺序值(export ordinal value),那么应该使用DEF文件来导出函数。只在使用DEF文件导出函数才能指定导出函数的顺序值。使用顺序值的一个好处是当向DLL中添加新的函数时,只要新的导出函数的顺序值大于原有的导出函数,就没有必要重新链接使用隐含链接的应用程序。相反,如果使用__declspec(dllexport)来导出函数,如果想DLL中添加了新的函数,使用隐含链接的应用程序有可能需要重新编译和链接。

使用DEF文件从C++文件导出函数,应该在定义函数是使用extern "C"或者在DEF文件中指定导出函数的decorated name,否则,由于编译器所产生的decorated name是基于特定编译器的,链接到该DLL的应用程序也必须使用创建DLL的同一版本的Visual C++来编译和链接。

由于使用__declspec(dllexport)关键字导出函数不需要编写DEF文件,因此,如果编写的DLL只供自己使用,使用__declspec(dllexport)较为简单。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多