1.4 从命令行创建动态库
问题
如何使用命令行工具从示例1-2所示的C++源文件创建动态库?
解决方案
用以下步骤:
1. 用编译器把源文件编译成目标文件。如果使用的是 Windows 系统,用选项 -D 定义宏,确保输出动态库的符号。例如,要在示例 1-2 中创建动态库,需要定义宏GEORGERINGO_DLL。如果创建的是第三方动态库,安装指导说明会告诉你要定义什么宏。
2. 使用链接器从第 1 步中生成的目标文件创建动态库。
注意:如果你的动态库依赖于其他的库,应告诉编译器到哪里去查找这些动态库,并把其他库的名字告诉链接器。1.5节将详细讨论。
执行第1步的基本命令如表 1-8 所示,你需要相应地修改输入和输出文件的名字。执行第2 步的命令见表 1-11 所示。如果你所使用的工具集带有静态和动态运行库,则需要告诉编译器和链接器使用动态链接运行库,具体见1.23节的介绍。
表 1-11 创建动态库 libgeorgeringo.so、libgeorgeringo.dll 或 libgeorgeringo.dylib 的命令
[ascii]工具集 命令行
GCC g++ -shared -fPIC -o libgeorgeringo.so george.o ringo.o georgeringo.o
GCC (Mac OS X) g++ -dynamiclib -fPIC -o libgeorgeringo.dylib george.o ringo.o georgeringo.o
GCC (Cygwin) g++ -shared -o libgeorgeringo.dll -Wl,out-implib,libgeorgeringo.dll.a-W1,export-all-symbols -Wl,enable-auto-image-base george.o ringo.o georgeringo.o
GCC (MinGW) g++ -shared -o libgeorgeringo.dll -Wl,out-implib,libgeorgeringo.a -W1,export-all-symbols-Wl,enable-auto-image-base george.o ringo.o georgeringo.o
Visual C++ link -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Intel (Windows) xilink -nologo -dll -out:libgeorgeringo.dll -implib:libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Intel (Linux) g++ -shared -fPIC -lrt -o libgeorgeringo.so george.o ringo.o georgeringo.o georgeringo.obj
Metrowerks (Windows) mwld -shared -export dllexport -runtime dm -o libgeorgeringo.dll -implib libgeorgeringo.lib george.obj ringo.obj georgeringo.obj
Metrowerks (Mac OS X) mwld -shared -export pragma -o libgeorgeringo.dylib george.o ringo.o georgeringo.o
CodeWarrior 10.0 (Mac OS X)[4] (请查阅有关文档)Consult the Metrowerks documentation.
Borland bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj georgeringo.objimplib -c libgeorgeringo.lib libgeorgeringo.dll
Digital Mars dmc -WD -L/implib:libgeorgeringo.lib -o libgeorgeringo.dll george.obj ringo.obj georgeringo.obj user32.lib kernel32.lib[/ascii]
注意:到2005年9月为止,Comeau工具集不支持在Unix或Windows系统上创建动态库。但是Comeau Computing公司正在开发对动态库的支持,预计在2005年底,可以在一些Unix平台上(包括Linux)实现
例如,要用Borland编译器把实力1-2的源文件编译成目标文件,假设Borland工具集的目录已包含在PATH环境变量中,切换到目录georgeringo,并输入以下命令:
> bcc32 -c -q -WR -o george.obj george.cpp
george.cpp:
> bcc32 -c -q -WR -o ringo.obj ringo.cpp
ringo.cpp:
> bcc32 -c -q -WR -DGERORGERINGO_DLL -o georgeringo.obj georgeringo.cpp
georgeringo.cpp:
这里,编译器选项-WR用于指定动态运行库。这三个命令将输出目标文件george.obj、ringo.obj和georgeringo.obj。接下来,输入命令:
> bcc32 -q -WD -WR -elibgeorgeringo.dll george.obj ringo.obj
georgeringo.obj
这将生成动态库 libgeorgeringo.dll。最后,输入命令:
> implib -c libgeorgeringo.lib libgeorgeringo.dll
将生成输入库libgeorgeringo.lib。
讨论
如何处理动态库完成取决于操作系统和工具集。从程序开发人员的角度说,两个最重要的区别如下。
符号可见性
动态库可以包含类、函数和数据的定义。在某些平台上,所有这些符号都能被动态库的代码访问,其他系统则为程序开发人员提供了访问这些符号的很好控制。能够确定哪些符号可见通常是有好处的,他使得程序开发人员更明确地控制库的功用接口,而且经常能提供很好的性能。但是,这也使得动态库的创建和使用更复杂。
对于大多数的Windows工具集,为了让使用动态库的代码能访问定义在动态库中的符号,则必须在创建动态库时显式地使其为可输出的(exported),使用动态库的可执行程序或动态库在创建时必须为可输入的(imported)。一些Unix工具集也提供了这种灵活性。对于一些平台的最新GCC版本、MAC OS X的Metrowerks和Linux的Intel工具集都是如此。但是,在一些情况下,则别无选择,只能使所有符号可见。
把库传递给链接器
在Unix系统中,当链接使用了动态库的代码时,动态库可以作为链接器的输入。在Windows系统中,除非使用GCC,否则动态库不能直接指定为链接器的输入,而是使用输入库或模块定义文件。
输入库和模块定义文件
输入库,粗略地说,就是含有运行时调用DLL的函数的信息的静态库。没有必要知道它们的工作原理,只要知道如何创建和使用它们就可以了。当你创建DLL时,大多数链接器自动地生成输入库,但在一些情况下,可能需要使用一个单独的工具,成为import librarian。在表1-11中,我使用了Borland的implib.exe,以避免使用Borland链接器ilink32.exe 所要求的特殊命令行语言。
模块定义文件或.def文件时一个文本文件,它描述了由DLL输出的函数和数据。.def文件可以手工编写或由工具自动生成。示例1-5为库libgeorgeringo.dll的一个示范.def文件。
示例 1-5 libgeorgeringo.dll 的模块定义文件
[ascii]LIBRARY LIBGEORGERINGO.DLL
EXPORTS
Georgeringo @1[/ascii]
从DLL中输出符号
从Windows的DLL中输出符号有两种标准的方法:
· 使用DLL文件的__declspec(dllexport)属性,当链接使用DLL的代码时,创建一个输入库。
__declspec(dllexport)属性应插在输入函数或数据声明的开头,如示例1-6所示,__declspec(dllexport)不是C++语言的一部分,它是被大多数Windows编译器所实现的语言扩展。
·创建一个用于描述被动态库输出的函数或数据的.def文件
- 示例1-6 __declspec(dllexport)属性
__declpec(dllexport) int m = 3; // Exported data definition
extern __declpec(dllexport) int n; // Exported data declaration
__declpec(dllexport) void f( ); // Exported function declaration
class __declpec(dllexport) c { // Exported class definition
/* ... */
};
使用.def文件有一定的好处。例如,它可以使得DLL中的函数通过数字而不是名字来访问,从而可以减小DLL的大小。它还可以减小对预处理器指令(如示例1-2中的georgeringo.hpp头文件)的需要。但是,它有一些严重的缺点。例如,.def文件不能用于输出类。而且,当你添加、删除或修改了DLL中的函数时,很难记得去更新.def文件。因此,我推荐你使用__declspec(dllexport)。要学习.def文件的完整语法及用法,可以查询相关的工具集文档。
从DLL中输入符号
正如有两种方法从DLL中输出符号一样,输入符号也有两种方法:
· 在使用了DLL的源代码所包含的头文件中,使用__declspec(dllimport)属性,当链接使用了DLL的代码时,将把库输入到链接器。
·当链接使用了DLL的代码时,制定一个.def文件。
与输入符号一样,我推荐你在源代码中使用__declspec(dllimport)属性,而不是.def文件。属性__declspec(dllimport)的使用就像前面介绍的__declspec(dllexport)属性一样。而且,与__declspec(dllexport)一样,__declspec(dllimport)也不是C++语言的一部分,而是由大多数Windows编译器实现的扩展。
如果你选择使用__declspec(dllexport)和__declspec(dllimport),就必须确保在创建DLL时使用了__declspec(dllexport),在编译使用了DLL的代码时使用了__declspec(dllimport)。一种方法是使用两种头文件:一种用于创建DLL,另一种用于编译使用了DLL的代码。但是,这并不令人满意,因为要维护相同头文件的两个不同版本比较困难。
常用的方法是定义一个宏,当创建DLL时扩展为__declspec(dllexport),否则就为__declspec(dllimport)。示例1-2使用宏GEORGERINGO_DECL就是为了这个目的。在Windows系统下,如果定义了宏GEORGERINGO_SOURCE,则把GEORGERINGO_DECL扩展为__declspec(dllexport),否则扩展为__declspec(dllimport)。创建DLL libgeorgeringo.dll时定义GEORGERINGO_SOURCE,编译使用libgeorgeringo.dll的代码时则不能定义该宏,就可以获得想要的结果。
用GCC创建DLL
1.1节所介绍的GCC工具集的Cygwin和MinGW创建DLL时与其他的Windows工具集有何不同。当用GCC创建DLL时,所有函数、类和数据都默认地为输出的。通过把--no-export-all-symbols选项传递给链接器,在源文件中使用属性__declspec-(dllexport),或使用一个.def文件,就可以修改这种行为。在这三种情况下,除非使用--export-all-symbols强制让链接器输出所有符号,否则只有函数、类和数据被标识为__decl-spec(dllexport)或列举在.def文件中
因此用GCC工具集创建DLL有两种方法:一种是像普通的Windows工具集一样,显式地使用__declspec输出符号;另一种是像Unix工具集一样,自动输出所有符号。在示例1-2和表1-11中使用的是后一种方法。如果你选用这种方法,为安全起见,应考虑使用选项 --export-all-symbols,以防止你使用了含有__declspec(dllexport)的头文件。
GCC与其他Windows工具集不同的另一种方式是,不能把DLL相关的输入传递给链接器,而是传递给DLL本身。这通常比使用输入库更快。但是,这也可能产生问题,因为你的系统中可能存在某个DLL的多个版本,而你必须确保链接器选用的是正确的版本。在表1-11中,为了说明如何使用DLL的输入库,我没有选用该特性。
注意:对于Cygwin,动态库DLL xxx.dll的输出库通常命名为xxx.dll.a,而对于MinGW则通常为xxx.a。这只是一种约定而已。
GCC4.0的fvisibility选项
包括Linux和Mac OS X 在内的多个平台上的GCC最新版本为程序开发人员提供了动态库输出的符号的很好控制:命令行选项-fvisibility可用来设置动态库的符号的默认可见性。-fvisibility选项有多个可选值,但最令人感兴趣的两个是default和hidden。粗略地说,default可见性说明符号可以被其他模块中的代码访问,而hidden可见性则表示不能。要使符号输出为可选择的,在命令行中指定-fvisibility=hidden,然后用visibility属性来指定特定符号为可见的,如示例1-7所示。
- 示例 1-7 使用命令行选项visilibity属性-fvisibility=hidden
extern __attribute__((visibility("default"))) int m; // exported
extern int n; // not exported
__attribute__((visibility("default"))) void f( ); // exported
void g( ); // not exported
struct __attribute__((visibility("default"))) S { }; // exported
struct T { }; // not exported
在示例1-7中,属性__attribute__((visibility("default")))与Windows代码中的__declspec(dllexport)作用相同。
使用visibility属性与使用__declspec(dllexport)和__declspec(dllimport)有相同的问题,当创建共享库时,你可能希望该属性为可见的,但当编译使用了共享库的代码时则不希望。与__declspec(dllexport)和__declspec(dllimport)一样,这个问题可以用预处理器来解决。例如,你可以如下修改示例1-2的头文件georgeringo.hpp,可以利用visibility属性:
- georgeringo/georgeringo.hpp
#ifndef GEORGERINGO_HPP_INCLUDED
#define GEORGERINGO_HPP_INCLUDED
// define GEORGERINGO_DLL when building libgerogreringo
# if defined(_WIN32) && !defined(__GNUC__)
# ifdef GEORGERINGO_DLL
# define GEORGERINGO_DECL _ _declspec(dllexport)
# else
# define GEORGERINGO_DECL _ _declspec(dllimport)
# endif
# else // Unix
# if defined(GEORGERINGO_DLL) && defined(HAS_GCC_VISIBILITY)
# define GEORGERINGO_DECL _ _attribute_ _((visibility("default")))
# else
# define GEORGERINGO_DECL
# endif
# endif
// Prints "George, and Ringo\n"
GEORGERINGO_DECL void georgeringo( );
#endif // GEORGERINGO_HPP_INCLUDED
要使之有效,在支持-fvisibility选项的系统中创建时,必须定义宏HAS_GCC_VISIBILITY。
注意:Intel compiler for Linux最新版本也支持-fvisibility选项。
Metrowerks for Mac OS X 的符号可见性
Metrowerks for Mac OS X 提供了几个选项,用于从动态库中输出符号。当使用CodeWarrior IDE时,可以使用符号输出文件,该文件与Windows系统下的.def作用相同。你也可以选择使用选项-export all输出所有符号,当从命令行创建时这是默认的。我推荐的方法是在源代码中使用#pragma export来标识要输出的函数,当在命令行中创建动态库时指定 -export pragma。#export pragma的使用见示例1-2所示:在头文件中,在要输出的函数组的前面使用 #pragma export,在其后面再使用 #export pragma off。如果你想让你的代码在 Metrowerks之外的工具集上也可以使用,就必须在 #ifdef/#endif 指令之间使用 #pragma export,如示例 1-2 所示。
命令行选项
让我们来快速浏览一下1-11中使用的选项。每个命令行指定:
· 输入文件名:george.obj、ringo.obj 和 georgeringo.obj。
· 要创建的动态库的名称。
· 在Windows系统中,输入库的名称。
另外,链接器要求有一个选项来告诉它是创建动态库而不是可运行程序。大多数链接器使用选项-shared,但Visual C++和Intel for Windows使用的是-dll,Borland和Digital Mars使用的是-WD,GGC for Mac OS X使用的是 -dynamiclib。
表1-11中有几个选项可以使得在运行时使用动态库更有效。例如,一些Unix链接器应使用选项-fPIC(GCC和Intel for Linux)来告诉它生成与位置无关的代码。该选项更像是在某些系统中,多个进程共享动态库代码的单个副本。如果没有指定这个选项,则会导致链接器错误。同样,在Windows系统中,GCC链接器选项 --enable-auto-image-base使得它不像是操作系统在同一个地方要加载两个动态库,使用该选项可以加快DLL的加载速度。
注意:可以通过编译器,使用编译器选项 -Wl,<option> to g++ (W后面的是小写字母l)把命令行选项传递给GCC链接器。
大多数其他的选项用于指定运行时库变量,1.23节将详细介绍。
参见1.9节、1.12节、1.17节、1.19节和1.23节