C++ Cookbook 中文版录入(侧重于GNU GCC的部分)

回复
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

C++ Cookbook 中文版录入(侧重于GNU GCC的部分)

帖子 vicyang »

这本书和其他C/C++的书不同,它介绍了各种环境下的编译、链接、静态库、动态库相关的知识。
由于中文版只找到扫描版的,浏览体验不佳,我打算手动录入一部分内容(主要偏向于GNU GCC 和 Makefile)

如果你们有中文文字版的,请告诉我,试打了一部分章节,真的很累。 =_=

略过的部分用类似如下的边界线作为标识
======== 此处略过 ========
最后,码字上来只是为了交流学习,如果涉及到版权问题,请告知。

随书代码
O'Reilly - C++ Cookbook CPP_cookbok_source.zip
(115.61 KiB) 已下载 39 次
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

1.0 概述

帖子 vicyang »

序言
====
略过
====

基本术语
用于构建C++应用程序的三个基本工具是编译器、链接器和归档库。这些程序和其他一些可能用到的工具集合就称为工具集。

编译器以C++源文件为输入,生成目标文件 (object file)。目标文件包含了机器可执行代码、数据和函数的符号引用。归档器(archiver)以目标文件为输入,生成静态库(static library)。链接器以目标文件集和库为输入,将符号引用解析,生成可执行程序或动态库。简单地说,链接器是把每个符号与其定义相匹配。当创建好了可执行程序或动态库后,我们就说已经链接好了,用于构建可执行程序或动态库的库文件就称为已被链接了。

一个可执行程序(或称为应用程序)就是所有可被操作系统运行的程序。动态库(也称为共享库)与可执行程序类似,只不过它不能自己运行,它由机器可运行的代码组成,当应用程序启动后,动态库的代码体被加载到内存中,且可以被一个或多个应用程序共享。在Windows操作系统中,动态库又成为动态链接库(dynamic link libraries, DLL)。

只有在构建可执行程序时,才需要可执行程序所需的目标文件和静态库。但是,当可执行程序运行时,它所依赖的动态库必须在用户的系统中。

(以下纠正了一处翻译错误)
表1-1显示了在Microsoft Windows 和 Unix 操作系统中,以上四种基本文件类型的文件扩展名。当我提及的某类文件在不同系统(Windows和Unix)下有不同扩展名时,为了能让描述简明一些,可能会省去扩展名。

表1-1 Windows 和Unix操作系统下的文件扩展名
文件类型 Windows Mac OS X Other Unix 目标文件 .obj .o .o 静态库 .lib .a .a 动态库 .dll .dylib .so 可执行程序 .exe 无扩展名 无扩展名
注意:在本章中,如果提到Unix,则同样适用于Linux。

==============
IDE 与构建系统 - 略过
==============

工具集概述
本章将介绍7种命令行工具集:GCC、Visual C++、Intel、Metrowerks、Borland、Comeau 和 Digital Mars。表1-2显示了不同工具集的命令行工具名。表1-3显示了安装这些工具集后它们在系统中的位置。Windows的工具名按Windows可运行文件的要求使用了.exe后缀名,对于Windows和Unix系统都可用的工具集,则把后缀放在括号中了。

表1-2 不同工具集的命令行工具名
工具集 编译器 链接器 归档器 GCC g++[.exe] g++ ar[.exe]ranlib[.exe] Visual C++ cl.exe link.exe lib.exe Intel (Windows) icl.exe xilink.exe xilib.exe Intel (Linux) Icpc icpc arranlib Metrowerks mwcc[.exe] mwld[.exe] mwld[.exe] Comeau como[.exe] como[.exe] Toolset-dependent Borland bcc32.exe bcc32.exe ilink32.exe tlib.exe Digital Mars dmc.exe link.exe lib.exe
表1-3 命令行工具的位置
工具集 位置 GCC (Unix) 通常为 /usr/bin or /usr/local/bin GCC (Cygwin) Cygwin安装目录下的bin子目录 GCC (MinGW) MinGW安装目录下的bin子目录 Visual C++ Visual Studio 安装位置下的 VC/bin Intel (Windows) Intel编译器安装位置下的bin子目录 Intel (Linux) Intel编译器安装位置下的bin子目录 Metrowerks CodeWarrior安装位置下的其他Metrowerks工具/命令行的Tools子目录 Comeau Comeau安装位置下的bin子目录 Borland C++Builder, C++BuilderX 或 Borland命令行安装位置下的bin子目录
GNU编译器集(GCC)
GCC是一个编译器集,可用于多种语言,包括C和C++。由于开源而出名,几乎可以运行在各种平台上,而且高度遵循C++语言标准。在很多Unix平台上,它是占主导地位的编译器,在Microsoft Windows中也被广泛使用。即使GCC不是你主要使用的工具集,通过用GCC编译代码,你也可以学到很多东西。而且,如果你想以某种方式来提高C++语言,可以使用GCC代码来测试一下。

GCC有一个libstdc++库,这是一个C++标准库的开源实现。它可以与开源STLPort C++标准库和Dinkumware标准库一起使用。
分界线
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

从命令行创建 "Hello, World" 应用程序

帖子 vicyang »

从命令行创建 "Hello, World" 应用程序

问题:如何创建如示例1-4所示的简单的 "Hello, World" 程序?

示例1-4 一个简单的"Hello, World" 程序
  • hello.cpp
    #include?<iostream> int?main() { ????std::cout?<<?"hello,?World!\n"; }
解决方案
可按照以下步骤:
1. 设置好工具集所要求的环境变量。
2. 输入一个命令,告诉编译器编译和链接程序
用于设置环境变量的脚本程序如表1-5所示。这些脚本程序位于命令行工具的相同目录中(见表1-3所示)。如果你所用的工具集没有出现在表1-5中,那么你就可以跳过第1步。否则,如果你使用的是Windows系统,则运行相应的脚本程序;如果使用的是Unix系统,则编写相应的脚本程序。

表1-5 命令行工具需要的用于设置环境变量的脚本
[ascii]工具集 脚本
Visual C++ vcvars32.bat
Intel (Windows) iclvars.bat
Intel (Linux) iccvars.sh 或 iccvars.csh
Metrowerks (Mac OS X) mwvars.sh 或 mwvars.csh
Metrowerks (Windows) cwenv.bat
Comeau Same as the backend toolset[/ascii]
编译和链接hello.cpp的命令如表1-6所示。要使这些命令正确运行,应使当前目录是含有hello.cpp的目录,且包含命令行编译器的目录应出现在PATH环境变量中。如果已在第1步中运行了脚本程序,那么后面的条件将自动满足。也有可能在你安装工具集时,安装程序就把包含命令行工具的目录添加到PATH中了。如果不是这样,你就得如表1-7所示的那样把目录添加到PATH中,或在命令行中指定完整的路径名。

表1-6 只用一步就可完成hello.cpp编译和链接的命令
[ascii]工具集 命令行
GCC g++ -o hello hello.cpp
Visual C++ cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp
Intel (Windows) icl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp
Intel (Linux) icpc -o hello hello.cpp
Metrowerks mwcc -wchar_t on -cwd include -o hello hello.cpp
Comeau como -o hello hello.cpp
Borland bcc32 -q -ehello hello.cpp
Digital Mars dmc -Ae -Ar -I<dmcroot>/stlport/stlport -o hello hello.cpp[/ascii]
表1-7 把目录添加到PATH环境变量
[ascii]外壳程序 命令行
bash, sh, ksh (Unix) export PATH=<directory>:$PATH
csh, tsch (Unix) setenv PATH <directory>:$PATH
cmd.exe (Windows) set PATH=<directory>;%PATH%[/ascii]
例如,如果你使用的是Microsoft Visual Studio .NET 2003,且是安装在C盘上的标准目录下,那么就可以切换到含有hello.cpp的目录下,输入如下命令:
> "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\ vcvars32.bat" Setting environment for using Microsoft Visual Studio .NET 2003 tools. (If you have another version of Visual Studio or Visual C++ installed and wish to use its tools from the command line, run vcvars32.bat for that version.) > cl -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fehello hello.cpp hello
现在可以运行程序:
> hello Hello, World!
同样,如果你使用的是Intel 9.0 for Linux,且如果是安装在标准位置/opt/intel/cc/9.0下面,那么就可以打开bash外壳程序,切换到含有hello.cpp的目录,并输入如下命令:
$ . /opt/intel/cc/9.0/bin/iccvars.sh $ icpc -o hello hello.cpp $ ./hello Hello, World!
===============================
这里省略一大波关于环境变量的说明
===============================

表1-7中的大多数命令行选项属于第四类:通常的配置信息。这些选项不是用于特定的文件,而是启用或禁用某些编译器特征。
? 选项 -nologo (Visual C++和Intel for Windows) 和 -q (Borland) 告诉编译器不显示其名字和版本号。这使得编译器的输出更易读。

? 选项 -EHsc (Visual C++和Intel for windows) 和 -Ae (Digital Mars) 告诉编译器启用C++异常处理。

? 选项 -GR (Visual C++和Intel for windows) 和 -Ar (Digital Mars) 告诉编译器启用运行时类信息 (RTTI)。

? 选项 -Zc:wchar_t (Visual C++和Intel for windows) 和 -wchar_t on (Metrowerks)告诉编译器把 wchar_t 识别为内置类型

? 选项 -Zc:forScope (Visual C++和Intel for windows) 告诉编译器要严格遵守 for-scoping 规则。

? 选项 -cwd include (Metrowerks) 告诉编译器,在含有 include 指令的源文件目录中开始查找一个包含的头文件
接下来,让我们来看看原始问题的另一种解决方案。这种解决方案不是用单个命令来编译和链接,而是把第2步分割成一下两部分:

2a. 输入下一个指令,告诉编译器把程序编译成目标文件,但不进行编译。

2b. 输入下一个命令,告诉链接器把上一步中创建的目标文件生成可运行程序。

在前面的简单示例中,没有必要分别编译和链接。但是,分别编译和链接经常是需要的。因此,了解如何做是很重要的。例如,要创建一个静态库,就必须只编译而不链接,然后把结果目标文件传给归档器。

(以下纠正了翻译错误)
分两步编译和链接的命令见表 1-8 和 1-9 所示。某些示例中使用o[bj]标记扩展名,这表明该命令在Windows和Unix下使用方法相同,但扩展名不同。
表 1-8 用于编译但不链接 hello.cpp 的命令
[ascii]工具集 命令行
GCC g++ -c -o hello.o hello.cpp
Visual C++ cl -c -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fohello hello.cpp
Intel (Windows) icl -c -nologo -EHsc -GR -Zc:forScope -Zc:wchar_t -Fohello hello.cpp
Intel (Linux) icpc -c -o hello.o hello.cpp
Metrowerks mwcc -c -wchar_t on -cwd include -o hello.o[bj]hello.cpp
Comeau como -c -o hello.o[bj] hello.cpp
Borland bcc32 -c -q -o hello.obj hello.cpp
Digital Mars dmc -c -Ae -Ar -I<dmcroot>/stlport/stlport -o hello.obj hello.cpp[/ascii]
表 1-9 用于链接 hello.exe 或 hello 的命令
[ascii]工具集 命令行
GCC g++ -o hello hello.o
Visual C++ link -nologo -out:hello.exe hello.obj
Intel (Windows) xilink -nologo -out:hello.exe hello.obj
Intel (Linux) icpc -o hello hello.o
Metrowerks mwld -o hello hello.o[bj]
Comeau como no_prelink_verbose -o hello hello.o[bj]
Borland bcc32 -q -ehello hello.cpp
Digital Mars link -noi hello.obj, hello.exe,NUL,user32.lib kernel32.lib,,[/ascii]
例如,要用GCC工具集来构建可执行程序hello,先切换到含有hello.cpp 文件的目录下,然后输入命令:
$ g++ -c -o hello.o hello.cpp $ g++ -o hello hello.o
现在可以运行该程序:
$ ./hello Hello, World!
表1-9 与表1-6几乎相同,只有两处不同。首先,选项-c 用于告诉编译器只编译而不链接。其次,指定输出文件为目标文件hello.obj或hello.o,而不是可执行程序。大多数编译器使用-o <file>来指定输出文件,但Visual C++和Intel for Windows 使用选项 -Fo<file>。而且,除Visual C++ 和 Intel for Windows 外,所有编译器都要求指定目标文件的扩展名。

到现在为止,表1-9 中的大多数命令行都应该很容易理解,因此这里只给出两个说明:

· Digital Mars 链接器的命令行语法与众不同,它由6个逗号分隔的字段组成,用于指定输入文件的不同类型。现在,你只需要知道第一个字段用于目标文件,第二个用于输出文件就可以了。选项 -noi 告诉链接器执行区分大小写的链接,这对于C++程序是需要的。

· Borland 链接器 ilink32.exe 使用与 Digital Mars 类似的语法。为了简化命令行,我使用的是bcc32.exe 编译器来执行链接步骤。bcc32.exe会在后端调用ilink32.exe。

参见 1.7 节和 1.15 节
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

编译多个源文件的示例

帖子 vicyang »

编译多个源文件的示例 - John, Paul, George and Ringo

自从 Brain Kernighan 和 Deinnis Ritche 于 1978 年出版了 The C Programming Language 以来,通常都是通过编写、编译和运行一个往控制台显示 “Hello, World!” 的程序来开始一种新语言的学习。由于本章要介绍静态库和动态库,以及可执行程序,因此我将介绍一个稍微复杂一点的示例。

示例 1-1 ~ 示例 1-3 显示了 hellobeatles 应用程序的源代码,该程序将往控制台显示:
John, Paul, George and Ringo
该程序本来可以写成一个源文件,但我把它分成了三个模块:静态库 libjohnpaul、动态库libgeorgeringo,以及可执行程序hellobeatles。而且,尽管每个库都可以很容易地实现为单个的头文件和.cpp文件,但我把实现分成了许多个源文件,用于演示如何编译和链接含有多个源文件的项目。
注意: 在开始本章的技巧之前,请先创建四个目录johnpaul、georgeringo、hellobeatles 和 binaries。前三个目录用于存放示例1-1~示例1-3的源文件。第四个用于存放由IDE生成的二进制文件。
Libjohnpaul的源代码如示例1-1所示。Libjohnpaul的公用接口由单个函数johnpaul()组成,它声明在头文件johnpaul.hpp中。函数johnpaul()负责向控制台显示:
John, Paul,
johnpaul()的实现又分成两个源文件john.cpp和paul.cpp,每个源文件都只负责显示单个人名。

示例 1-1 libjohnpaul 的源文件 johnpaul/john.hpp
  • #ifndef JOHN_HPP_INCLUDED
    #define JOHN_HPP_INCLUDED

    void john( ); // Prints "John, "

    #endif // JOHN_HPP_INCLUDED

    johnpaul/john.cpp

    #include <iostream>
    #include "john.hpp"

    void john( )
    {
    std::cout << "John, ";
    }

    johnpaul/paul.hpp

    #ifndef PAUL_HPP_INCLUDED
    #define PAUL_HPP_INCLUDED

    void paul( ); // Prints " Paul, "

    #endif // PAUL_HPP_INCLUDED

    johnpaul/paul.cpp

    #include <iostream>
    #include "paul.hpp"

    void paul( )
    {
    std::cout << "Paul, ";
    }

    johnpaul/johnpaul.hpp

    #ifndef JOHNPAUL_HPP_INCLUDED
    #define JOHNPAUL_HPP_INCLUDED

    void johnpaul( ); // Prints "John, Paul, "

    #endif // JOHNPAUL_HPP_INCLUDED

    johnpaul/johnpaul.cpp

    #include "john.hpp"
    #include "paul.hpp"
    #include "johnpaul.hpp"

    void johnpaul( )
    {
    john( );
    paul( );
    }
libgeorgeringo 的源代码如示例 1-2 所示。Libgeorgeringo 的公用接口由单个函数georgeringo()组成,它生命在georgeringo.hpp头文件中。正如你能猜到的那样,georgeringo()函数只负责向控制台显示:
George, and Ringo
同样,georgeringo()的实现也分成了两个源文件 george.cpp 和 ringo.cpp。

示例 1-2 libgeorgeringo 的源文件 georgeringo/george.hpp
  • #ifndef GEORGE_HPP_INCLUDED
    #define GEORGE_HPP_INCLUDED

    void george( ); // Prints "George, "

    #endif // GEORGE_HPP_INCLUDED

    georgeringo/george.cpp

    #include <iostream>
    #include "george.hpp"

    void george( )
    {
    std::cout << "George, ";
    }

    georgeringo/ringo.hpp

    #ifndef RINGO_HPP_INCLUDED
    #define RINGO_HPP_INCLUDED

    void ringo( ); // Prints "and Ringo\n"

    #endif // RINGO_HPP_INCLUDED

    georgeringo/ringo.cpp

    #include <iostream>
    #include "ringo.hpp"

    void ringo( )
    {
    std::cout << "and Ringo\n";
    }

    georgeringo/georgeringo.hpp

    #ifndef GEORGERINGO_HPP_INCLUDED
    #define GEORGERINGO_HPP_INCLUDED

    // define GEORGERINGO_DLL when building libgerogreringo.dll
    # if defined(_WIN32) && !defined(__GNUC__)
    # ifdef GEORGERINGO_DLL
    # define GEORGERINGO_DECL _ _declspec(dllexport)
    # else
    # define GEORGERINGO_DECL _ _declspec(dllimport)
    # endif
    # endif // WIN32

    #ifndef GEORGERINGO_DECL
    # define GEORGERINGO_DECL
    #endif

    // Prints "George, and Ringo\n"
    #ifdef __MWERKS__
    # pragma export on
    #endif

    GEORGERINGO_DECL void georgeringo( );
    #ifdef __MWERKS__
    # pragma export off
    #endif

    #endif // GEORGERINGO_HPP_INCLUDED

    georgeringo/ georgeringo.cpp

    #include "george.hpp"
    #include "ringo.hpp"
    #include "georgeringo.hpp"

    void georgeringo( )
    {
    george( );
    ringo( );
    }
头文件georgeringo.hpp 包含了一些复杂的预处理器指令。如果你现在不理解,没关系,1.4节将解释它们。

最后,可执行程序hellobeatles的源代码如示例1-3所示。它由单个源文件hellobeatles.cpp组成,该源文件包含了头文件johnpaul.hpp和georgeringo.hpp,调用了函数johnpaul()和georgeringo()。

示例1-3 hellobeatles 的源代码 hellobeatles/ hellobeatles.cpp
  • #include "johnpaul/johnpaul.hpp"
    #include " georgeringo/ georgeringo.hpp"

    int main( )
    {
    // Prints "John, Paul, George, and Ringo\n"
    johnpaul( );
    georgeringo( );
    }
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

从命令行创建静态库

帖子 vicyang »

1.3 从命令行创建静态库

问题
如何用命令行工具把C++源文件(如示例1-1所示的文件)创建成静态库?

解决方案
首先,用编译器把源文件编译成目标文件。如果源文件包含了位于其他目录的头文件,则需要用-I选项来告诉编译器到哪里去查找这些头文件,详细信息参见1.5节。其次,用归档器把目标文件组合成静态库。

要编译示例1-1中的三个源文件,可以用表1-8中的命令行,并按需要修改输入和输出文件名。要把编译好的目标文件组合成静态库,可以使用1-10中的命令。

表1-10 用于创建 libjohnpaul.lib 或 libjohnpaul.a 的命令
工具集 命令行 GCC (Unix)Intel (Linux)Comeau (Unix) ar ru libjohnpaul.a john.o paul.o johnpaul.o ranlib libjohnpaul.a GCC (Windows) ar ru libjohnpaul.a john.o paul.o johnpaul.o Visual C++Comeau (with Visual C++) lib -nologo -out:libjohnpaul.lib john.obj paul.obj johnpaul.obj Intel (Windows) xilib -nologo /out:libjohnpaul.lib john.obj paul.obj johnpaul.obj Metrowerks (Windows) mwld -library -o libjohnpaul.lib john.obj paul.obj johnpaul.obj Metrowerks (Mac OS X) mwld -library -o libjohnpaul.a john.o paul.o johnpaul.o Borland tlib libjohnpaul.lib /u /a /C +john +paul +johnpaul Digital Mars lib -c -n libjohnpaul.lib john.obj paul.obj johnpaul.obj
例如,要用GCC把 john.cpp、paul.cpp 和 johnpaul.cpp 编译成目标文件,切换到 johnpaul 目录,输入如下命令来产生 john.o、paul.o 和 johnpaul.o 目标文件:
$ g++ -c -o john.o john.cpp $ g++ -c -o paul.o paul.cpp $ g++ -c -o johnpaul.o johnpaul.cpp
现在就可以把这些目标文件链接成一个静态库了:
$ ar ru libjohnpaul.a john.o paul.o johnpaul.o $ ranlib libjohnpaul.a
讨论
在Unix系统中,使用 GCC 时,要用两个单独的命令来生成一个静态库。首先,运行归档器 ar,然后运行名为 ranlib 的工具。Ru 选项告诉 ar,如果没有相同名字的归档文件,就把给定的目标文件添加到指定的归档器中,但只有当给定的目标文件比已有的文件更加新时,才会更新到已有的归档文件。以前,在创建或更新归档后,就用 ranlib 工具来创建更新归档符号表(就是出现在不同目标文件中的符号索引)。现在,在很多系统中,归档器自己负责创建或更新符号表,因此没有必要再运行 ranlib 了。GNU版本的 ar 就是如此。但是,在一些系统中,GCC 编译器可能会与非 GNU 版本的 ar 一起使用,鉴于此原因,最好是运行 ranlib 以防万一。

从表 1-10 可以看出,Borland 归档器 tlib 使用的语法稍有不同,目标文件前面的加号用于把这些目标文件添加到库中。对其他命令行应该很容易理解。
注意:对于其他工具集,通过传递恰当的命令行选项,链接器可以用作归档器。而对其他的工具集,则必须使用单独的归档器。
参见1.8节、1.11节和1.16节
头像
vicyang
版主
版主
帖子: 56
注册时间: 2016年07月21日 20:35
联系:

从命令行创建动态库

帖子 vicyang »

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节
回复

在线用户

正浏览此版面之用户: 没有注册用户 和 0 访客