C++ API 设计 18 附录

附录

A 库

库让你可以打包编译好的代码和数据,实现你的API,以便用户能够把它们嵌入到他们自己的程序中。库是模块化的集合工具[l1]。本附录将涵盖你可以使用的不同的库类型以及如何在不同的平台上创建它们。还涵盖了API设计的物理方面,也就是在库文件符号导出表中暴露的公共API符号。

库的特性、用法和支持的工具是平台特定的。在Windows系统上处理在Windows系统上的动态链接库(DLL)的方式和在UNIX系统上处理UNIX系统上的动态共享对象(Dynamic Shared ObjectDSO)的方式是不同的。因此,我在附录中是根据平台来组织大部分的内容,包括Windows、Linux和Mac OS X。这么做也有一个好处,就是你不必为你的当前项目不关心的特定平台的细节分心。

A.1 静态库对比动态库

你可以创建的库主要有两种形式。你所做的决定会对你的用户的最终用户程序产生重大的影响,实际的因素如:装载时间、可执行文件的大小和API的不同版本的健壮性。这两种基本类型是静态库和共享库。我会在下面的章节中描述每个细节。

A.1.1 静态库

静态库包含和一个最终用户程序链接的对象代码并且成为可执行文件的一部分。图A.1说明了这个概念。静态库有时也叫做“文档文件”(archive),因为本质上它只是一个已编译对象文件包。通常在UNIX和Mac OS X系统上,这些库的文件扩展名是.a;而Windows上是.lib,例如libjpeg.a或jpeg.lib。

把一个API实现分发发布为静态库会带来如下一些影响[l2]

q静态库只需要被链接到一个应用程序。它并不需要运行该程序,因为本质上库代码已经嵌入到程序中了。因此,用户可以分发他们程序的时候不会有任何额外的运行时依赖。

q如果用户希望把他们的库链接进多个可执行文件中,那么每个都会嵌入一份你的代码拷贝。如果你的库的大小是10MB的话,用户希望把它链接到5个独立的程序中,那么他们的产品大小会增加50MB。请注意,只有静态库中的有被实际使用到的对象文件才被拷贝到程序中。因此,实际中的每个程序的总大小会小于这种最坏的情况。

 

[图 P392 第一张]

图A.1

把一个静态库链接到程序中,这样库代码就会被嵌入到生成的可执行文件中。

 

q你的用户在分发他们的产品时,可以不用关心在最终用户的机子上是否有一个不兼容的库版本或者有一个来自其它厂商同名的却完全不同的库。

q然而,如果你的用户要给他们的程序打补丁[l3],也就是他们要更新他们的应用程序所使用的库版本,那么他们要实现这个就必须替换整个可执行文件。如果这是通过因特网完成更新的,那么最终用户可能就得下载一个大很多的更新,从而要完成更新就要等比较长的时间。

A.1.2动态库

动态库是在编译时被链接的文件,解析未定义的引用并接着随最终用户程序一起分发的,以便程序能够在运行时装载库代码(请参加图A.2)。这通常需要使用最终用户机子上的动态链接器来在运行时决定和装载所有的动态库依赖,执行需要的符号重定位(symbol relocation)。例如,Linux动态链接器叫做ld.so,在Mac上叫做dyld。动态链接器常常支持很多环境变量来修改或调式它的行为。例如,Mac上的“man dyld”。

动态库有时也叫做共享库,因为它们可以被多个程序所共享。在UNIX机子上,它们被叫做动态共享对象,而在Windows系统上,它们被称为动态链接库。在UNIX平台上,它们使用.so文件扩展名,而Windows上是.dll,Mac OS X上是.dylib。例如,libjpeg.so或jpeg.dll。

把一个API实现分发发布为动态库会带来如下一些影响:

q你的用户在分发你的动态库的时候必须带上他们的程序(也包括你的动态库所依赖的其它库),以便程序运行时可以找到动态库。

[图 P393 第一张]

图A.2

动态链接库是用来链接到一个程序,接着和程序一起分发,以便可以在运行时加载这个库。

q如果没有找到动态库的话,那么你的用户的程序将无法运行,比如库被删除或移动到动态库搜索路径之外的目录。还有,如果动态库更新到较新的版本或被旧的版本覆盖的话,那么程序也可能无法运行。

q就硬盘的使用空间而言,如果有多个程序需要使用库的话,那么使用动态库常常比使用静态库更有效率。这是因为库代码是存储在一个单独的共享文件中,并没有复制进每个可执行文件中。不过,要注意的是这不是一个硬性的规则[l4]。前面提到过,可执行文件只需要包含静态库中实际需要的对象代码。如果每个程序只使用整个静态库中的一小部分,那么磁盘空间的使用率仍然可以媲美一个完整的动态库。

q动态库也可能在内存方面更有效。绝大多数现代的操作系统在内存中只加载一次动态库代码并所有依赖的程序都可以共享。这样也可以更好地利用缓存。相比之下,每个程序都链接一个静态库会在内存中加载库代码的重复拷贝。

q如果你的用户希望通过共享库的新版本(向后兼容的)来给他们的程序打补丁,那么他们只要简单地替换库文件即可,他们的所有程序都可以使用这个新的库不需要重新编译或重新链接。

提示

你应该首选分发一个动态库,来给予用户更好的灵活性。如果库是足够小和稳定的,那么你可以考虑提供一个静态库版本。

理解下面的内容也是很重要的:动态库依赖于其它动态库的行为。如果你的库依赖于其它的动态库,那么你必须在你的SDK中也装载上那些库,除非你有理由确定最终用户的平台中已经预装有那些库。这是一种传递属性:你必须也装载你的依赖的所有依赖。本质上,动态库的整条连锁链对于程序在运行时必须是可用的,这样才能正常运行。当然,如果你的动态库链接到一个静态库,那么你就不需要为你的用户提供那个静态库,因为静态库的代码会直接被添加到你的动态库中。

[图 P394 第一张]

图A.3

插件库是一个可以从程序外被编译的动态库,会根据需要被程序所加载。

A.1.3 做为插件的动态库

动态库通常是被链接到一个程序并一起随程序一起分发,以便操作系统可以在程序启动时装载该库。不过,程序也可以根据需要来装载一个动态库,而不用程序被编译链接到该库。

这可以被用来创建插件接口,程序可以在运行时装载额外的代码,扩展程序的基本功能。例如,绝大多数Web浏览器支持插件处理具体的内容类型,如显示Adobe Flash动画或欣赏苹果的QuickTime电影。这种动态库的用法说明请参见图A.3。

就API开发而言,这可以让你创建可扩展的API,允许用户在API加载和执行后访问新的功能。Netscape(网景)插件API就是这样的一个这样的例子:这个API就是可供开发的,可以创建一个浏览器(如Firefox、Safari、Opera和Chrome)能够可以加载和运行的插件(如动态库)。我在第十二章已经讲述过使用插件来创建可扩展的API。

A.2 Windows系统上的库

在Windows系统上,使用.lib文件来表示静态库,而动态库是使用.dll文件扩展名。此外,随同每个.dll文件还需要一个导入库(import library),如.lib文件。导入库是被用来解析DLL中导出符号的引用。例如,Win32用户接口(User Interface)API是在user32.dll中实现的,附带有一个user32.lib。要注意的是当它们共享相同的.lib文件扩展时,静态库和导入库实际上是不同的文件类型。如果你计划同时分发API的静态和动态库版本,那么你需要避免文件名冲突,或者是把静态库命名成不同的,或者是把每个放在一个独立的目录中,例如:

[表格 P395 第一张]

在Windows系统上,几个其它的文件格式实际上是由DLL实现的。这些包括:

qActiveX控制文件(ActiveX Controls files, .ocx)

q设备驱动文件(Device Driver files,.drv)

q控制面板文件(Control Panel files,.cpl)

A.2.1导入和导出函数

在第六章中讨论过,如果在Windows系统上,你要一个函数从DLL处是可调用的[l5],那么你必须在它的声明处用下面的关键字显式标记出来:

[代码 P395 第一段]

例如:

[代码 P395 第二段]

相反地,如果你要在程序中使用一个导出DLL函数,那么你必须在函数原型前使用下面的关键字前缀:

[代码 P395 第三段]

因此,当构建API时,通常会采用预处理器宏来使用导出声明;在一个程序中使用相同的API时,是使用导入修饰。还有一个要点是因为这些__declspec修饰会在非Windows编译器上导致编译错误,所以你应该只在Windows系统编译时才使用它们。下面的预处理器代码为此提供了一个简单的演示。(更完整的跨平台的例子请参见第六章的6.9章节)

[代码 P395 第四段]

接着,你就可以声明所有的要从DLL中导出的符号,如下所示:

[代码 P396 第二段]

或者使用这些__declspec声明来修改源码,你可以创建一个模块定义.def文件来指定要导出的符号。一个最小化的DEF文件包括一个LIBRARY语句来指定相关的DLL文件的名称,而EXPORTS语句后面是要导出的符号列表。

[代码 P396 第三段]

DEF文件语法也支持更加强大的符号操作,如重命名符号或使用一个序号来做为导出名以帮助减少DLL的大小。序号表示的是DLL导出表中一个符号的地址指针的位置。为DLL符号使用序号可以生成更加轻量快速和更小的库。然而,从API稳定性的视角来看,这是有风险的,因为看似对DEF文件无害的修改都有可能更改更该API的导出符号。因此,当指定DLL导出时,我建议使用完全符号名,而不是序号。

A.2.2 DLL 入口点

当一个线程或进程装载DLL时或当卸载DLL清理内存时,DLL可以提供一个可选的入口点函数来初始化数据结构。这是由一个叫DllMain()的函数管理的,是在DLL中由你定义和导出的。如果入口点函数返回FALSE,那么这就被假定为是一个致命错误,程序就会启动失败。下面的代码提供了一个DLL入口点模板。

[代码 P396 第四段]

A.2.3 在Windows系统上创建库

下面的步骤描述了如何在Windows系统上创建一个静态库。这些步骤是微软的Visual Studio 2008 (9.0)的,不过其它版本的Visual Studio中的步骤也是类似的。

(1).选择菜单 File > New > Project

(2).选择Visual Cþþ > Win32选项和Win32项目图标

(3).会出现Win32程序向导

(4).在程序类型中选择静态库选项(请看图A.4)

[图 P397 第一张]

图A.4

在Visual Studio 2008中创建一个新的静态库或DLL

接着,你就可以在左边面板的源文件文件夹下添加新的或现有的源文件到你的项目中。当你生成项目时,结果会是一个静态库.lib文件。

创建一个DLL的步骤是非常相似的。接着,当你生成项目时,Visual Studio就会生成的.dll文件和相关的.lib导入文件。

A.2.4实用的Windows程序

很多程序可以帮你管理Windows系统上的DLL和侦测DLL的问题。这些很多都是可以从MS-DOS提示符运行的命令行工具。下面给出一些这样的DLL程序:

qtasklist.exe:这个程序用来查明一个运行中的Windows EXE文件所依赖的动态库,例如:tasklist /m /fi IMAGENAME eq APPNAME.EXE”

qdepends.exe:依赖助手程序可以递归扫描一个可执行文件来发现它所有依赖的DLL。[l6]它会检测缺失的DLL、无效的DLL和循环依赖,还包括错误条件。

qdlister.exe:这个程序提供一个所有安装在机子上的DLL的日志。这可以输出为一个文本文件或数据库文件。

qdcomp.exe:这是用来显示两个DLL之间由dlister.exe生成的列表差异。

A.2.5 在Windows系统上装载插件

在Windows平台上,可以使用LoadLibrary()或LoadLibraryEx()函数往一个进程中装载一个动态库,使用GetProcAddress()函数来获取DLL中一个导出符号的地址。要注意的是:采用这种方式装载一个动态库时不需要一个导入库.lib文件。为了演示这个,考虑下面这个用来创建一个plugin.dll库的简单的插件接口。

[代码 P398 第一段]

接着,下面的代码片段说明了如何根据需要加载这个DLL并从那个库中调用DoSomething()方法。

[代码 P399 第一段]

A.3 LINUX上的库

下面的章节概述了在Linux上创建和管理静态与共享库。这里强调的是基本的重要问题和技术。[l7]不过,如果要想了解更多的内容,我建议你阅读Ulrich Drepper的一篇很不错的文章:如何编写共享库,你可以在http://people.redhat.com/drepper/dsohowto.pdf找到。

A.3.1 在Linux上创建静态库

在Linux上,一个静态库只不过是一个对象(.o)文件存档。你可以使用Linux的ar命令来把编译多个对象编译成一个静态库。例如,下面的命令演示了如何使用GNU C++编译器把三个.cpp文件编译成.o文件,接着就可以从那些对象文件创建一个静态库。

[代码 P399 第二段]

g++的-c选项参数是通知编译器从输入的.cpp文件生成一个.o文件。ar选项是-c创建一个存档,-r把提供的.o文件插入到那个存档中,-s为存档创建了一个索引(相当于在生成的存档上运行ranlib的旧版本协议)。

接着,用户可以在ld或g++中使用-l选项来链接到你的库。这指定了要链接的库名称。-L链接器选项也可以被用来指定你的库所在的目录。例如:

[代码 P400 第一段]

在本例中,最终用户程序userapp是由编译sercode.cpp得到的并链接到同一目录的libmyapi.a静态库。

在这个命令行中的存档顺序是重要的。对于链接器在命令行上找到的每个存档,它会查看存档中是否定义了引用自命令行前面指定过的对象文件的任何符号。如果它确实定义了任何需要的符号,那么带有那些符号的对象文件会被拷贝到可执行文件中。因此,最好的做法是在命令行的末尾指定库(Mitchell等,2001)。

当我在讨论创建静态库时,是值得注意-static编译器选项的合适用法。这个标记被用来创建可执行文件,而不是库。因此,它对于你的API用户是可用的,但是不用来构建(生成)API本身。这个标记指示编译器首选把所有依赖库的静态版本链接到可执行文件中,以便在运行时它不依赖于任何的动态库。

A.3.23 在Linux上创建动态库

在Linux上创建动态库的过程和创建静态库的过程是很相似的。通过GNU C++编译器,你可以简单地使用-shared链接器选项来生成一个代替可执行文件的.so文件。

在非默认行为的平台上,你也应该指定-fpic或-fPIC命令行选项来指示编译器生成与位置无关的代码(position-independent code,PIC)。需要这个是因为对于不同的可执行文件,在共享库中的代码可能被不同的内存位置所加载。因此,为共享库生成PIC代码是重要的,以便用户代码不依赖于符号的绝对内存地址。下面的例子说明了如何把三个源文件编译成一个动态库。

[代码 P400 第二段]

接着,用户就可以使用先前的静态库例子中的命令行把你的动态库链接到他们的代码,也就是:

[代码 P400 第三段]

如果你同时有在一个目录中拥有相同名称的一个静态库和一个动态库,也就是:libmyapi.a和libmyapi.so,那么编译器会使用动态链接库(除非你使用-static库选项来只使用静态库)。为了更好地使用相同名称的静态库和动态库,你可以把静态库放到一个不同的目录内,确保提前在库搜索路径中设置好这个目录(使用-L链接器选项)。

要注意到在一个动态库中,本质上所有的代码都被压缩成一个对象文件。与之形成对比的是静态库,它被表示成一个对象文件的集合,可以根据需要被单独地拷贝到可执行文件中。(例如,在一个静态存档中不需要的对象文件不会被拷贝到可执行文件的映像中)。因此,装载一个动态库会包含所有定义在.so文件中的代码(Mitchell等,2001)。

在默认情况下,在DSO中的所有符号被公开导出(除非你指定了-fvisibility=hidden的编译器选项)。然而,GNU C++编译器支持导出映射的概念:显式定义一个动态库中的对用户程序可见的符号集。这是一个简单的ASCII格式,符号可以被单独列出或使用通配符式的表达式。例如,下面的映射文件:export.map,指定了只有DoSomething()应该被导出,而其它所有的符号应该被隐藏。

[代码 P401 第一段]

接着,当使用--version-script链接器选项构建这个动态库时,这个映射文件可以被传送给编译器,例子如下所示:

[代码 P401 第二段]

A.3.3 共享库入口点

当加载或卸载共享库时,可以定义一个会被自动调用的函数。这可以被用来执行库的初始化和清理操作,而不需要你的用户显式调用函数来执行这个。

实现这个的其中一个方法是使用静态构造函数和析构函数。这适用于任何编译器或平台,尽管你应该记住的是静态构造函数初始化的顺序是无法跨越翻译单元边界的[l8]。也就是说,你绝不应该依赖在其它.cpp文件中被初始化的静态变量。请铭记这一警告,你可以在其中的一个.cpp文件中创建一个静态库入口点,如下所示:

[代码 P401 第三段]

还有一种更加优雅的替代方法。不过,这是针对GNU编译器的。就是为函数使用构造函数和析构函数的__attribute__修饰符。例如,下面的代码演示了如何定义库的初始化和清理例程并在其中的一个.cpp文件中隐藏这些:

[代码 P402 第一段]

如果你使用这种方法,那么你应该注意到:在编译共享库时,不能带有GNU GCC参数-nostartfiles或-nostdlib。

A.3.4 实用的Linux程序

有几个标准的Linux程序可以帮助你处理静态和动态库。要特别注意的是:GNUlibtool的shell脚本。这个命令为不同的UNIX平台创建库提供了一致和可移植的接口。可以通过各种方式来使用libtool脚本,不过可以给予libtool对象文件列表的最简单的形式是指定-static或-dynamic选项,接着它就可以分别创建一个静态或动态库。例如:

[代码 P402 第二段]

如果你要在一系列UNIX平台上轻松地编译你的源码,而不用担心每个平台上创建库时所特有的行为,那么libtool脚本是很有用的。

另一个处理库时实用的命令是nm,可以被用来显示对象文件或库中的符号名。这是用来查找一个库是否定义或使用了给定的符号。例如,下面的命令行输出库中的所有的全局(外部)符号:

[代码 P402 第三段]

这个将生成类似下面的输出:

[代码 P402 第四段]

第二列字符指定了符号类型,“T”是指本库中定义的一个文本文字章节符号,而“U”是指库引用的一个符号,不过并非是它定义的。一个大写字母表示一个外部符号,而小写字母表示一个内部符号。第三列的字符串提供改编后的符号名。[l9]可以使用c++filt命令来输出改编之前的原始名称,例如:

[代码 P403 第一段]

另一个有用的命令是ldd。这可以被用来显示一个可执行文件依赖的动态库的列表。因为这会显示每个库使用的动态库的完整路径,你可以看到加载的库是哪个版本和操作系统是否无法找到某个库。例如,下面是一个简单的可执行文件在Linux上生成的输出。

[代码 P403 第二段]

使用-static选项链接的可执行文件不会依赖于任何动态库。在Linux上对一个可执行文件运行ldd,输出的结果如下所示:

[代码 P403 第三段]

最后,如果你有一个静态库,它可以有可能转换成一个动态库。回忆一下静态库(.a)只是一个对象文件(.o)的包装。因此,你可以使用ar命令提取单独的对象文件并重新链接成一个动态库。例如:

[代码 P403 第四段]

A.3.5 在Linux上装载插件

在Linux平台上,你可以使用dlopen()函数调用来把一个.so文件装载到当前进程中。接着你可以使用dlsym()函数来访问该库中的符号。这让你可以创建插件前面描述过的插件接口。例如,考虑下面这个很简单的插件接口:

[代码 P403 第五段]

你可以为这个API构建一个动态库,如libplugin.so。下面的代码演示了如何加载这个库和调用.so文件中的DoSomething()函数:

[代码 P404 第一段]

A.3.6 在运行时查找动态库

当你运行的一个依赖一个动态库的可执行文件,那么系统会在多个标准的位置查找这个库,通常是在/lib和/usr/lib。如果.so文件无法在所有的这些位置被找到,那么可执行文件将会启动失败。回想一下ldd命令可以被用来通知你系统是否有任何无法找到的动态库。这显然是创建一个需要依赖你的API的可执行文件程序所关心的。你的用户有三种主要的选择来确保他们利用你的API所创建的所有可执行文件可以在运行时找到你的库。

(1).API的用户要确保你的库已经安装在最终用户的机子的其中一个标准库目录中。例如,/usr/lib。这将需要最终用户执行一个安装过程并拥有root权限来拷贝文件到一个系统目录中。

(2).LD_LIBRARY_PATH环境变量可以用带有冒号分隔的目录列表来设置默认的库搜索路径。因此,你的用户能够分发一个shell脚本来运行他们的程序,脚本把LD_LIBRARY_PATH变量设置成可以找到你的库的恰当的目录。

(3).你的用户可以使用rpath(运行路径)链接器选项来设置查询动态库的首选路径并链接入可执行文件中。例如,下面的编译行将生成让系统为所有的动态库搜索/usr/local/lib的可执行文件。

[代码 P405 第一段]

A.4 MAC OS X上的库

Mac OS X 操作系统是构建于一个叫做Darwin的BSD UNIX系统的一个版本之上的。因此,先前Linux系统中提到过的很多细节也同样适用于Mac。然而,在Darwin和其它UNIX平台(如Linux)之间还是存在着一些值得关注的不同点。

A.4.1 在Mac OS X上创建静态库

在Mac OS X系统上创建静态库的方法和Linux上是一样的。也就是,使用ar或libtool。然而,当链接一个静态库到一个程序时,就存在一些不同的行为。

特别地,苹果不鼓励使用静态编译器选项来生成使用静态链接所有库依赖的可执行文件。这是因为苹果要确保程序总是引用他们分发的最新系统库。[l10]实际上,gcc的操作手册说明了-static无法工作在MAC OS X系统上,除非所有的库(包括libgcc.a)也是由-static编译的。因为既没有提供一个libSystem.dylib的静态版本,也没有提供crt0.o,所以这个选项对于绝大多数人是没有用处的。

本质上,Mac上的-static选项是为构建内核预留的,或者是留给那些敢于使用的开发人员。[l11]

和这种情况相关的,默认情况下的Mac链接器为了查找一个动态库会扫描库搜索路径中的所有路径。如果它失败了,接着它会为一个静态库再次扫描路径。这意味着你就无法使用把需要的静态库放置在前面提到过的库搜索路径的目录中的技巧。然而,有一个链接器选项叫做-search-paths-first可以让链接器为查找一个动态库而访问每个搜索路径。接着,如果没有找到的话,就在相同的目录寻找一个静态库。在这个方面,这个选项使Mac的链接器行为更像Linux中的链接器。不过要注意的是:在Mac上,当在相同的目录中同时有静态库和动态库时,没有方法可以优先选择链接静态库。

A.4.2在Mac OS X上创建动态库

在Mac OS X创建动态库的方式和前面提到过的Linux指令是非常相似的。有一个重要的区别是:在Mac上,你应该在g++中使用-dynamiclib选项来代替-shared创建动态库。

[代码 P405 第二段]

还有,注意一下-headerpad_max_install_names选项的用法。当在Mac上构建动态库时,这个标记是备受推荐的,其中的原因我会立刻解释的。

还要注意到的是ldd命令在Mac OS X上是不可用的。你可以使用otool命令目录来代替,用来罗列出一个可执行文件所依赖的动态库集。也就是:

[代码 P406 第二段]

A.4.3 Mac OS X上的框架

Mac OS X上也引入了一个框架的概念,就是把所有的需要编译和链接到一个API的文件全部分发到单个包中。一个框架仅仅是一个带有.framework扩展名的目录,包含各种资源,如动态库、头文件和参考文档。从NSBundle的概念上看,它本质上就是一个包(bundle)。这把所有需要的开发文件都打包到单个包中,这样可以方便安装和卸载一个库。还有,一个框架可以在同一个包中包含多个版本的库,这样对于较旧的程序可以更容易地维持向后兼容性。

下面的目录列出了一个框架包布局的例子[l12],->符号表示一个符号链接。

[代码 P406 第三段]

苹果的绝大多数API都是分发成框架的,包括Cocoa、Foundation和Core Service。假设你已经安装了Mac OS X开发环境,你就可以在/Developer/SDKs目录找到它们。因此,在Mac上你会希望把你的API分发成一个框架,这样看起来更像Mac上的原生库。

你可以使用苹果的Xcode开发环境把你的API构建成一个框架。要完成这个可以先选择File > New Project菜单,接着选择左边面板的Framework(请见图A.5)。在默认情况下,你可以为你的项目选择使用Carbon或Cocoa框架。如果你不需要这两者(例如:你要编写一个纯C++的库),你可以在Xcode为你创建项目后删除这些框架。

[图 P407 第一张]

图A.5

使用苹果XCode IDE创建一个框架。

用户可以为g++或ld提供一个-framework选项来链接到你的框架。他们也可以通过-F选项来指定查找你的框架包的目录。

A.4.4 在运行时查找动态库

Linux下在运行时查找动态库相同的原理也适用于Mac OS X下运行的应用程序,不过有一些细微的差异。第一个就是用于指定库搜索路径的环境变量叫做DYLD_LIBRARY_PATH(不过现在最新版本的Mac OS X也支持LD_LIBRARY_PATH)。

还有,Mac OS X不支持Linux的-rpath链接器选项。它提供了一个替代的安装名称install name的概念。一个安装名称是一个被写入Mach-O二进制的路径,用来指定搜索依赖动态库的路径。[l13]指定的这个路径可以是相对于可执行文件的程序,只要安装名称开头使用一个特殊的字符串@executable_path。

当你构建你的动态库时,可以指定一个安装名称,但是你的用户可以使用install_name_tool程序来修改这个路径。不过,他们无法指定比.dylib中原始路径更长的路径。这就是为什么总是建议你在Mac上使用-headerpad_max_install_names选项来构建你的动态库:这样可以让你的用户可以按照他们的期望灵活地修改库的安装名称。

下面的命令演示了一个用户如何修改你的库的安装名称并为他们的可执行文件修改安装名称:

[代码 P408 第一段]

Power by  YOZOSOFT

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
水资源是人类社会的宝贵财富,在生活、工农业生产中是不可缺少的。随着世界人口的增长及工农业生产的发展,需水量也在日益增长,水已经变得比以往任何时候都要珍贵。但是,由于人类的生产和生活,导致水体的污染,水质恶化,使有限的水资源更加紧张。长期以来,油类物质(石油类物质和动植物油)一直是水和土壤中的重要污染源。它不仅对人的身体健康带来极大危害,而且使水质恶化,严重破坏水体生态平衡。因此各国都加强了油类物质对水体和土壤的污染的治理。对于水中油含量的检测,我国处于落后阶段,与国际先进水平存在差距,所以难以满足当今技术水平的要求。为了取得具有代表性的正确数据,使分析数据具有与现代测试技术水平相应的准确性和先进性,不断提高分析成果的可比性和应用效果,检测的方法和仪器是非常重要的。只有保证了这两方面才能保证快速和准确地测量出水中油类污染物含量,以达到保护和治理水污染的目的。开展水中油污染检测方法、技术和检测设备的研究,是提高水污染检测的一条重要措施。通过本课题的研究,探索出一套适合我国国情的水质污染现场检测技术和检测设备,具有广泛的应用前景和科学研究价值。 本课题针对我国水体的油污染,探索一套检测油污染的可行方案和方法,利用非分散红外光度法技术,开发研制具有自主知识产权的适合国情的适于野外便携式的测油仪。利用此仪器,可以检测出被测水样中亚甲基、甲基物质和动植物油脂的污染物含量,为我国众多的环境检测站点监测水体的油污染状况提供依据。
### 内容概要 《计算机试卷1》是一份综合性的计算机基础和应用测试卷,涵盖了计算机硬件、软件、操作系统、网络、多媒体技术等多个领域的知识点。试卷包括单选题和操作应用两大类,单选题部分测试学生对计算机基础知识的掌握,操作应用部分则评估学生对计算机应用软件的实际操作能力。 ### 适用人群 本试卷适用于: - 计算机专业或信息技术相关专业的学生,用于课程学习或考试复习。 - 准备计算机等级考试或职业资格认证的人士,作为实战演练材料。 - 对计算机操作有兴趣的自学者,用于提升个人计算机应用技能。 - 计算机基础教育工作者,作为教学资源或出题参考。 ### 使用场景及目标 1. **学习评估**:作为学校或教育机构对学生计算机基础知识和应用技能的评估工具。 2. **自学测试**:供个人自学者检验自己对计算机知识的掌握程度和操作熟练度。 3. **职业发展**:帮助职场人士通过实际操作练习,提升计算机应用能力,增强工作竞争力。 4. **教学资源**:教师可以用于课堂教学,作为教学内容的补充或学生的课后练习。 5. **竞赛准备**:适合准备计算机相关竞赛的学生,作为强化训练和技能检测的材料。 试卷的目标是通过系统性的题目设计,帮助学生全面复习和巩固计算机基础知识,同时通过实际操作题目,提高学生解决实际问题的能力。通过本试卷的学习与练习,学生将能够更加深入地理解计算机的工作原理,掌握常用软件的使用方法,为未来的学术或职业生涯打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值