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

头像
vicyang
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

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

帖子 #1 vicyang » 2016年10月18日 22:08

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

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

略过的部分用类似如下的边界线作为标识
========
此处略过
========

最后,码字上来只是为了交流学习,如果涉及到版权问题,请告知。

随书代码

头像
vicyang
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

1.0 概述

帖子 #2 vicyang » 2016年10月18日 22:14

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

基本术语
用于构建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
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

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

帖子 #3 vicyang » 2016年10月18日 23:44

从命令行创建 "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 命令行工具需要的用于设置环境变量的脚本
工具集 脚本
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

编译和链接hello.cpp的命令如表1-6所示。要使这些命令正确运行,应使当前目录是含有hello.cpp的目录,且包含命令行编译器的目录应出现在PATH环境变量中。如果已在第1步中运行了脚本程序,那么后面的条件将自动满足。也有可能在你安装工具集时,安装程序就把包含命令行工具的目录添加到PATH中了。如果不是这样,你就得如表1-7所示的那样把目录添加到PATH中,或在命令行中指定完整的路径名。

表1-6 只用一步就可完成hello.cpp编译和链接的命令
工具集 命令行
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

表1-7 把目录添加到PATH环境变量
外壳程序 命令行
bash, sh, ksh (Unix) export PATH=<directory>:$PATH
csh, tsch (Unix) setenv PATH <directory>:$PATH
cmd.exe (Windows) set PATH=<directory>;%PATH%

例如,如果你使用的是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 的命令
工具集 命令行
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


表 1-9 用于链接 hello.exe 或 hello 的命令
工具集 命令行
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,,

例如,要用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
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

编译多个源文件的示例

帖子 #4 vicyang » 2016年10月19日 09:59

编译多个源文件的示例 - 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
    1. #ifndef JOHN_HPP_INCLUDED
    2. #define JOHN_HPP_INCLUDED
    3.  
    4. void john( ); // Prints "John, "
    5.  
    6. #endif // JOHN_HPP_INCLUDED
    7.  
    8. johnpaul/john.cpp
    9.  
    10. #include <iostream>
    11. #include "john.hpp"
    12.  
    13. void john( )
    14. {
    15.     std::cout << "John, ";
    16. }
    17.  
    18. johnpaul/paul.hpp
    19.  
    20. #ifndef PAUL_HPP_INCLUDED
    21. #define PAUL_HPP_INCLUDED
    22.  
    23. void paul( ); // Prints " Paul, "
    24.  
    25. #endif // PAUL_HPP_INCLUDED
    26.  
    27. johnpaul/paul.cpp
    28.  
    29. #include <iostream>
    30. #include "paul.hpp"
    31.  
    32. void paul( )
    33. {
    34.     std::cout << "Paul, ";
    35. }
    36.  
    37. johnpaul/johnpaul.hpp
    38.  
    39. #ifndef JOHNPAUL_HPP_INCLUDED
    40. #define JOHNPAUL_HPP_INCLUDED
    41.  
    42. void johnpaul( ); // Prints "John, Paul, "
    43.  
    44. #endif // JOHNPAUL_HPP_INCLUDED
    45.  
    46. johnpaul/johnpaul.cpp
    47.  
    48. #include "john.hpp"
    49. #include "paul.hpp"
    50. #include "johnpaul.hpp"
    51.  
    52. void johnpaul( )
    53. {
    54.     john( );
    55.     paul( );
    56. }
libgeorgeringo 的源代码如示例 1-2 所示。Libgeorgeringo 的公用接口由单个函数georgeringo()组成,它生命在georgeringo.hpp头文件中。正如你能猜到的那样,georgeringo()函数只负责向控制台显示:

George, and Ringo

同样,georgeringo()的实现也分成了两个源文件 george.cpp 和 ringo.cpp。

示例 1-2 libgeorgeringo 的源文件 georgeringo/george.hpp
    Code: [全选] [展开/收缩] [Download] (george.hpp)
    1. #ifndef GEORGE_HPP_INCLUDED
    2. #define GEORGE_HPP_INCLUDED
    3.  
    4. void george( ); // Prints "George, "
    5.  
    6. #endif // GEORGE_HPP_INCLUDED
    7.  
    8. georgeringo/george.cpp
    9.  
    10. #include <iostream>
    11. #include "george.hpp"
    12.  
    13. void george( )
    14. {
    15.     std::cout << "George, ";
    16. }
    17.  
    18. georgeringo/ringo.hpp
    19.  
    20. #ifndef RINGO_HPP_INCLUDED
    21. #define RINGO_HPP_INCLUDED
    22.  
    23. void ringo( ); // Prints "and Ringo\n"
    24.  
    25. #endif // RINGO_HPP_INCLUDED
    26.  
    27. georgeringo/ringo.cpp
    28.  
    29. #include <iostream>
    30. #include "ringo.hpp"
    31.  
    32. void ringo( )
    33. {
    34.     std::cout << "and Ringo\n";
    35. }
    36.  
    37. georgeringo/georgeringo.hpp
    38.  
    39. #ifndef GEORGERINGO_HPP_INCLUDED
    40. #define GEORGERINGO_HPP_INCLUDED
    41.  
    42. // define GEORGERINGO_DLL when building libgerogreringo.dll
    43. # if defined(_WIN32) && !defined(__GNUC__)
    44. #  ifdef GEORGERINGO_DLL
    45. #   define GEORGERINGO_DECL _  _declspec(dllexport)
    46. #  else
    47. #   define GEORGERINGO_DECL _  _declspec(dllimport)
    48. #  endif
    49. # endif // WIN32
    50.  
    51. #ifndef GEORGERINGO_DECL
    52. # define GEORGERINGO_DECL
    53. #endif
    54.  
    55. // Prints "George, and Ringo\n"
    56. #ifdef __MWERKS__
    57. # pragma export on
    58. #endif
    59.  
    60. GEORGERINGO_DECL void georgeringo( );
    61. #ifdef __MWERKS__
    62. # pragma export off
    63. #endif
    64.  
    65. #endif // GEORGERINGO_HPP_INCLUDED
    66.  
    67. georgeringo/ georgeringo.cpp
    68.  
    69. #include "george.hpp"
    70. #include "ringo.hpp"
    71. #include "georgeringo.hpp"
    72.  
    73. void georgeringo( )
    74. {
    75.     george( );
    76.     ringo( );
    77. }

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

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

示例1-3 hellobeatles 的源代码 hellobeatles/ hellobeatles.cpp
    Code: [全选] [展开/收缩] [Download] (hellobeatles.cpp)
    1. #include "johnpaul/johnpaul.hpp"
    2. #include " georgeringo/ georgeringo.hpp"
    3.  
    4. int main( )
    5. {
    6.     // Prints "John, Paul, George, and Ringo\n"
    7.     johnpaul( );
    8.     georgeringo( );
    9. }

头像
vicyang
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

从命令行创建静态库

帖子 #5 vicyang » 2016年10月19日 19:45

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
版主
版主
帖子: 47
注册时间: 2016年07月21日 20:35
拥有现金: 锁定
储蓄: 锁定
Has thanked: 7 times
联系:

从命令行创建动态库

帖子 #6 vicyang » 2016年10月19日 20:06

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 的命令
工具集 命令行
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

注意:到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 的模块定义文件
LIBRARY LIBGEORGERINGO.DLL

EXPORTS
Georgeringo @1


从DLL中输出符号
从Windows的DLL中输出符号有两种标准的方法:
· 使用DLL文件的__declspec(dllexport)属性,当链接使用DLL的代码时,创建一个输入库。
__declspec(dllexport)属性应插在输入函数或数据声明的开头,如示例1-6所示,__declspec(dllexport)不是C++语言的一部分,它是被大多数Windows编译器所实现的语言扩展。
·创建一个用于描述被动态库输出的函数或数据的.def文件

    示例1-6 __declspec(dllexport)属性

    Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
    1. __declpec(dllexport) int m = 3;     // Exported data definition
    2. extern __declpec(dllexport) int n;  // Exported data declaration
    3. __declpec(dllexport) void f( );         // Exported function declaration
    4. class __declpec(dllexport) c {      // Exported class definition
    5.    /* ... */
    6. };

使用.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

    1. extern __attribute__((visibility("default"))) int m;      // exported
    2. extern int n;                                            // not exported
    3.  
    4. __attribute__((visibility("default"))) void f( );        // exported
    5. void g( );                                                       // not exported
    6.  
    7. struct __attribute__((visibility("default"))) S {  };        // exported
    8. 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

    Code: [全选] [展开/收缩] [Download] (georgeringo.hpp)
    1. #ifndef GEORGERINGO_HPP_INCLUDED
    2. #define GEORGERINGO_HPP_INCLUDED
    3.  
    4. // define GEORGERINGO_DLL when building libgerogreringo
    5. # if defined(_WIN32) && !defined(__GNUC__)
    6. #  ifdef GEORGERINGO_DLL
    7. #   define GEORGERINGO_DECL _  _declspec(dllexport)
    8. #  else
    9. #   define GEORGERINGO_DECL _  _declspec(dllimport)
    10. #  endif
    11. # else // Unix
    12. #  if defined(GEORGERINGO_DLL) && defined(HAS_GCC_VISIBILITY)
    13. #   define GEORGERINGO_DECL _  _attribute_  _((visibility("default")))
    14. #  else
    15. #   define GEORGERINGO_DECL
    16. #  endif
    17. # endif
    18.  
    19. // Prints "George, and Ringo\n"
    20. GEORGERINGO_DECL void georgeringo( );
    21.  
    22. #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节


回到 “资料、教程”

在线用户

用户浏览此论坛: 没有注册用户 和 1 访客