Introduction to C/C++ Binary Library
2005-11-11 17:52
519 查看
Part 1. 需求分析:为什么要使用二进制库?
答案在于两个方面:1) 隐藏源代码的需要。
放眼现在市面上各个中间件软件提供商,所提供的产品绝大多数都是以二进制库的形式发布的。流行的比如Linux下的Qt图形库,Windows下微软的DirectX图形接口、.NET基类,以及嵌入式平台上的MiniGUI等等。在Linux下静态库和动态库文件的扩展名通常为.lib和.so(Shared Object),而Windows下动态库也称作动态链接库,扩展名为.dll(Dynamic Link Library)。
2) 模块化、层次化设计的需要。
一旦某个软件模块设计完成,可以将其打包成一个独立的库文件。这样系统其他部分就可以按照其提供的接口进行调用,而无须考虑代码层次的兼容性。其次,库文件是已经编译好的二进制文件,被调用的库只需要静态或动态地链接到主调代码中即可,而无需再次编译库的源代码,大大提高了编译的速度和效率。
下面在Linux环境中使用流行的gcc编译器来演示这两种库的建立和使用。
Part 2. 案例1:C与动态共享库
假设有一个简单的任务(经典的helloworld例子)有一个sayHello的方法在sayhello.c中定义:
//file: sayhello.c#include<stdio.h>
void sayHello()
{
printf("hello!/n");
}
头文件sayhello.h声明函数sayHello()的形式:
//file: sayhello.hvoid sayHello();
现在我们希望发布这个sayHello函数的实现,但是不希望别人知道源代码。于是我们可以将sayHello()函数打包成二进制库的形式发布,同时提供一个头文件声明其调用格式。主程序hello.c调用sayHello(),如下:
//file: hello.c#include"hello.h"
int main()
{
sayHello();
return 0;
}
生成动态库的过程如下:[sonic@Rex dynamic-lib-gcc]$ gcc -c -fpic sayhello.c
这样就生成了一个sayhello.o的目标文件。
参数-c: 编译或汇编源文件,但是不作连接.编译器输出对应于源文件的目标文件.
参数-fpic: Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine.
[sonic@Rex dynamic-lib-gcc]$ gcc -shared sayhello.o -o libHello.so
共享库libHello.so就生成好了。
现在假设第三方客户需要使用我们提供的共享库,只需要提供libHello.so和hello.h就行了。(hello.h包含于主程序hello.c中)
[sonic@Rex dynamic-lib-gcc]$ gcc hello.c sayhello.so -o hello
把主程序文件和打包好的共享库文件一起编译,最后得到的文件是
[sonic@Rex dynamic-lib-gcc]$ lshello hello.c libHello.so sayhello.h
但是这样还不能运行:
[sonic@Rex dynamic-lib-gcc]$ ./hello ./hello: error while loading shared libraries: libHello.so: cannot open shared object file: No such file or directory
这是因为编译hello时用到了共享库,而运行的时候系统只在几个固定的目录进行查找:/lib、/usr/lib,其余的在配置文件/etc/ld.so.conf中指定。
这里有两种选择,一是把libHello.so拷入系统指定的目录,一是自己建一个专门放置共享库的目录。
比如/lib/local/shared-lib就是一个不错的选择。
修改/etc/ld.so.conf,将目录加入其中。
把libHello.so复制到/lib/local/shared-lib,然后以root账户运行/sbin/ldconfig更新系统缓存。
Ok,现在运行:
[sonic@Rex dynamic-lib-gcc]$ ./hello hello!
Bingo!试验成功!用ldd检查一下编译完成的hello可执行文件:
[sonic@Rex dynamic-lib-gcc]$ /usr/bin/ldd[1] hello libHello.so=> /usr/local/shared-lib/libHello.so (0x00a26000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
可以发现红色标记的那一行是我们提供的共享库。
Part 3. 案例2:C++与库
这是一个稍微复杂一点的例子。比如我们要发布一个类,提供这样的头文件声明:
//file: SaySomething.hclass SaySomething
{
public:
void sayhello();
void sayhi();
};
成员函数的定义由两个文件承担:
//file: SayHello.cpp#include "SaySomething.h"
#include <iostream>
using namespace std;
void SaySomething::sayhello()
{
cout<<"hello"<<endl;
}
//file: SayHi.cpp#include "SaySomething.h"
#include <iostream>
using namespace std;
void SaySomething::sayhi()
{
cout<<"hi"<<endl;
}
基于同样的理由,内部成员函数的具体实现是不能公开的,头文件是公开对外发布的唯一途径。
先以动态库形式编译:
[sonic@Rex dynamic-lib-g++]$ g++ -c -fpic SayHello.cpp SayHi.cpp
生成了两个目标文件:SayHello.o和SayHi.o。
参数-c和-fpic与上例含义一致。
[sonic@Rex dynamic-lib-g++]$g++ -shared SayHello.o SayHi.o -o libSay.so
库文件libSay.so已经生成。
使用:
//file: Main.cpp#include "SaySomething.h"
int main()
{
SaySomething* sayHello=new SaySomething();
sayHello->sayhello();
sayHello->sayhi();
return 0;
}
编译:[sonic@Rex dynamic-lib-g++]$ g++ Main.cpp libSay.so -o hello2
同样,将libSay.so放入之前建立的共享库目录/lib/local/shared-lib,运行:
[sonic@Rex dynamic-lib-g++]$ ./hello2 hello
hi
用ldd查看hello2:
[sonic@Rex dynamic-lib-g++]$ /usr/bin/ldd hello2 libSay.so => /usr/local/shared-lib/libSay.so (0x0032b000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)
libm.so.6 => /lib/tls/libm.so.6 (0x00301000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
结论是一致的。
还有一种方案是采用静态库的形式发布,当然,效果是一样的,达到了隐藏代码的目的。
[sonic@Rex static-lib-g++]$ g++ -c SayHello.cpp SayHi.cpp
这里没有使用-fpic选项,得到了两个同前缀的.o目标文件。
[sonic@Rex static-lib-g++]$ ar -r libSay.a SayHello.o SayHi.o
使用ar工具打包成.a文件(archive),编译:
[sonic@Rex static-lib-g++]$ g++ Main.cpp libSay.a -o hello3
这次直接运行即可:
[sonic@Rex static-lib-g++]$ ./hello3 hello
hi
使用ldd查看的结果中也没有libSay的项了:
[sonic@Rex static-lib-g++]$ /usr/bin/ldd hello3 libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)
libm.so.6 => /lib/tls/libm.so.6 (0x00301000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
Part 4. 比较与分析
现在比较一下静态链接和动态链接。静态链接下的各中间文件的最终可执行文件:
[sonic@Rex static-lib-g++]$ ll-rw------- 1 sonic sonic 153 Oct 3 13:50 Main.cpp-rw------- 1 sonic sonic 133 Oct 3 13:50 SayHello.cpp-rw-rw-r-- 1 sonic sonic 2648 Oct 3 21:43 SayHello.o-rw------- 1 sonic sonic 128 Oct 3 13:50 SayHi.cpp-rw-rw-r-- 1 sonic sonic 2636 Oct 3 21:43 SayHi.o
-rw------- 1 sonic sonic 71 Oct 3 13:50 SaySomething.h-rwxrwxr-x 1 sonic sonic 8230 Oct 3 21:47 hello3
-rw-rw-r-- 1 sonic sonic 5592 Oct 3 21:45 libSay.a
动态链接下的各中间文件的最终可执行文件:
[sonic@Rex dynamic-lib-g++]$ ll-rw------- 1 sonic sonic 153 Oct 3 13:57 Main.cpp-rw------- 1 sonic sonic 133 Oct 3 13:57 SayHello.cpp-rw-rw-r-- 1 sonic sonic 3008 Oct 3 21:32 SayHello.o-rw------- 1 sonic sonic 128 Oct 3 13:57 SayHi.cpp-rw-rw-r-- 1 sonic sonic 3000 Oct 3 21:32 SayHi.o
-rw------- 1 sonic sonic 71 Oct 3 13:57 SaySomething.h-rwxrwxr-x 1 sonic sonic 5669 Oct 3 21:37 hello2
-rwxrwxr-x 1 sonic sonic 7941 Oct 3 21:34 libSay.so
比较对应的文件发现,中间目标文件动态链接比静态略大,库文件动态链接明显大于静态,原因可能跟动态链接较静态链接更为复杂的机制有关。最终可执行文件静态链接明显大于动态链接,原因很明显,动态链接顾名思义就是用到的时候才进行加载,因此主程序本身并不包含库。而静态链接就是把库在编译的时候就加载到可执行文件中,因此在运行时也不像同态链接那样需要库的支持。
这样一来,C/C++中头文件和库文件的关系就非常清楚了。
如下表所示。
一点疑问
动态链接产生的.so文件在和主程序文件一起编译的时候不能使用-static选项。如果强制使用的话,虽说可以通过链接,但是执行的时候会发生这样的错误:[sonic@Rex dynamic-lib-g++]$ g++ -static Main.cpp libSay.so
[sonic@Rex dynamic-lib-g++]$ ./a.out
-bash: ./a.out: /usr/lib/libc.so.1: bad ELF interpreter: No such file or directory
而静态链接产生的.a文件时可以和主程序文件一起用-static编译的。
[sonic@Rex static-lib-g++]$ g++ -static Main.cpp libHello.a
[sonic@Rex static-lib-g++]$ ./a.out
hello
hi
[1]如果安装过Intel的arm-linux交叉工具链,默认的ldd可能会定位到arm-linux目录下,所以为避免不必要的错误加上了绝对路径
答案在于两个方面:1) 隐藏源代码的需要。
放眼现在市面上各个中间件软件提供商,所提供的产品绝大多数都是以二进制库的形式发布的。流行的比如Linux下的Qt图形库,Windows下微软的DirectX图形接口、.NET基类,以及嵌入式平台上的MiniGUI等等。在Linux下静态库和动态库文件的扩展名通常为.lib和.so(Shared Object),而Windows下动态库也称作动态链接库,扩展名为.dll(Dynamic Link Library)。
2) 模块化、层次化设计的需要。
一旦某个软件模块设计完成,可以将其打包成一个独立的库文件。这样系统其他部分就可以按照其提供的接口进行调用,而无须考虑代码层次的兼容性。其次,库文件是已经编译好的二进制文件,被调用的库只需要静态或动态地链接到主调代码中即可,而无需再次编译库的源代码,大大提高了编译的速度和效率。
下面在Linux环境中使用流行的gcc编译器来演示这两种库的建立和使用。
Part 2. 案例1:C与动态共享库
假设有一个简单的任务(经典的helloworld例子)有一个sayHello的方法在sayhello.c中定义:
//file: sayhello.c#include<stdio.h>
void sayHello()
{
printf("hello!/n");
}
头文件sayhello.h声明函数sayHello()的形式:
//file: sayhello.hvoid sayHello();
现在我们希望发布这个sayHello函数的实现,但是不希望别人知道源代码。于是我们可以将sayHello()函数打包成二进制库的形式发布,同时提供一个头文件声明其调用格式。主程序hello.c调用sayHello(),如下:
//file: hello.c#include"hello.h"
int main()
{
sayHello();
return 0;
}
生成动态库的过程如下:[sonic@Rex dynamic-lib-gcc]$ gcc -c -fpic sayhello.c
这样就生成了一个sayhello.o的目标文件。
参数-c: 编译或汇编源文件,但是不作连接.编译器输出对应于源文件的目标文件.
参数-fpic: Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine.
[sonic@Rex dynamic-lib-gcc]$ gcc -shared sayhello.o -o libHello.so
共享库libHello.so就生成好了。
现在假设第三方客户需要使用我们提供的共享库,只需要提供libHello.so和hello.h就行了。(hello.h包含于主程序hello.c中)
[sonic@Rex dynamic-lib-gcc]$ gcc hello.c sayhello.so -o hello
把主程序文件和打包好的共享库文件一起编译,最后得到的文件是
[sonic@Rex dynamic-lib-gcc]$ lshello hello.c libHello.so sayhello.h
但是这样还不能运行:
[sonic@Rex dynamic-lib-gcc]$ ./hello ./hello: error while loading shared libraries: libHello.so: cannot open shared object file: No such file or directory
这是因为编译hello时用到了共享库,而运行的时候系统只在几个固定的目录进行查找:/lib、/usr/lib,其余的在配置文件/etc/ld.so.conf中指定。
这里有两种选择,一是把libHello.so拷入系统指定的目录,一是自己建一个专门放置共享库的目录。
比如/lib/local/shared-lib就是一个不错的选择。
修改/etc/ld.so.conf,将目录加入其中。
把libHello.so复制到/lib/local/shared-lib,然后以root账户运行/sbin/ldconfig更新系统缓存。
Ok,现在运行:
[sonic@Rex dynamic-lib-gcc]$ ./hello hello!
Bingo!试验成功!用ldd检查一下编译完成的hello可执行文件:
[sonic@Rex dynamic-lib-gcc]$ /usr/bin/ldd[1] hello libHello.so=> /usr/local/shared-lib/libHello.so (0x00a26000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
可以发现红色标记的那一行是我们提供的共享库。
Part 3. 案例2:C++与库
这是一个稍微复杂一点的例子。比如我们要发布一个类,提供这样的头文件声明:
//file: SaySomething.hclass SaySomething
{
public:
void sayhello();
void sayhi();
};
成员函数的定义由两个文件承担:
//file: SayHello.cpp#include "SaySomething.h"
#include <iostream>
using namespace std;
void SaySomething::sayhello()
{
cout<<"hello"<<endl;
}
//file: SayHi.cpp#include "SaySomething.h"
#include <iostream>
using namespace std;
void SaySomething::sayhi()
{
cout<<"hi"<<endl;
}
基于同样的理由,内部成员函数的具体实现是不能公开的,头文件是公开对外发布的唯一途径。
先以动态库形式编译:
[sonic@Rex dynamic-lib-g++]$ g++ -c -fpic SayHello.cpp SayHi.cpp
生成了两个目标文件:SayHello.o和SayHi.o。
参数-c和-fpic与上例含义一致。
[sonic@Rex dynamic-lib-g++]$g++ -shared SayHello.o SayHi.o -o libSay.so
库文件libSay.so已经生成。
使用:
//file: Main.cpp#include "SaySomething.h"
int main()
{
SaySomething* sayHello=new SaySomething();
sayHello->sayhello();
sayHello->sayhi();
return 0;
}
编译:[sonic@Rex dynamic-lib-g++]$ g++ Main.cpp libSay.so -o hello2
同样,将libSay.so放入之前建立的共享库目录/lib/local/shared-lib,运行:
[sonic@Rex dynamic-lib-g++]$ ./hello2 hello
hi
用ldd查看hello2:
[sonic@Rex dynamic-lib-g++]$ /usr/bin/ldd hello2 libSay.so => /usr/local/shared-lib/libSay.so (0x0032b000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)
libm.so.6 => /lib/tls/libm.so.6 (0x00301000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
结论是一致的。
还有一种方案是采用静态库的形式发布,当然,效果是一样的,达到了隐藏代码的目的。
[sonic@Rex static-lib-g++]$ g++ -c SayHello.cpp SayHi.cpp
这里没有使用-fpic选项,得到了两个同前缀的.o目标文件。
[sonic@Rex static-lib-g++]$ ar -r libSay.a SayHello.o SayHi.o
使用ar工具打包成.a文件(archive),编译:
[sonic@Rex static-lib-g++]$ g++ Main.cpp libSay.a -o hello3
这次直接运行即可:
[sonic@Rex static-lib-g++]$ ./hello3 hello
hi
使用ldd查看的结果中也没有libSay的项了:
[sonic@Rex static-lib-g++]$ /usr/bin/ldd hello3 libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00a39000)
libm.so.6 => /lib/tls/libm.so.6 (0x00301000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x0090f000)
libc.so.6 => /lib/tls/libc.so.6 (0x001d0000)
/lib/ld-linux.so.2 (0x001b7000)
Part 4. 比较与分析
现在比较一下静态链接和动态链接。静态链接下的各中间文件的最终可执行文件:
[sonic@Rex static-lib-g++]$ ll-rw------- 1 sonic sonic 153 Oct 3 13:50 Main.cpp-rw------- 1 sonic sonic 133 Oct 3 13:50 SayHello.cpp-rw-rw-r-- 1 sonic sonic 2648 Oct 3 21:43 SayHello.o-rw------- 1 sonic sonic 128 Oct 3 13:50 SayHi.cpp-rw-rw-r-- 1 sonic sonic 2636 Oct 3 21:43 SayHi.o
-rw------- 1 sonic sonic 71 Oct 3 13:50 SaySomething.h-rwxrwxr-x 1 sonic sonic 8230 Oct 3 21:47 hello3
-rw-rw-r-- 1 sonic sonic 5592 Oct 3 21:45 libSay.a
动态链接下的各中间文件的最终可执行文件:
[sonic@Rex dynamic-lib-g++]$ ll-rw------- 1 sonic sonic 153 Oct 3 13:57 Main.cpp-rw------- 1 sonic sonic 133 Oct 3 13:57 SayHello.cpp-rw-rw-r-- 1 sonic sonic 3008 Oct 3 21:32 SayHello.o-rw------- 1 sonic sonic 128 Oct 3 13:57 SayHi.cpp-rw-rw-r-- 1 sonic sonic 3000 Oct 3 21:32 SayHi.o
-rw------- 1 sonic sonic 71 Oct 3 13:57 SaySomething.h-rwxrwxr-x 1 sonic sonic 5669 Oct 3 21:37 hello2
-rwxrwxr-x 1 sonic sonic 7941 Oct 3 21:34 libSay.so
| 中间文件 | 库文件 | 最终可执行文件 |
静态链接 | 2648+2636 | 5592 | 8230 |
动态链接 | 3008+3000 | 7941 | 5569 |
这样一来,C/C++中头文件和库文件的关系就非常清楚了。
如下表所示。
| 头文件 | 静态库 | 动态库 |
内容 | 只提供函数、类以及全局变量、宏的申明,而不提供具体的实现(即源代码) | 函数、类的具体实现的二进制目标代码 | 函数、类的具体实现的二进制目标代码 |
调用方式 | 头文件被预处理器(cpp)以直接插入代码的方式被包含进主调源代码中(include) | 由链接器(ld)在编译期(静态)链接到主调二进制代码中 | 由链接器(ld)在运行期(动态)链接到主调二进制代码中 |
使用 | 源代码中#include,gcc参数-include headerfile | gcc参数-l library_name | 放入系统定义的共享库搜索目录中 |
一点疑问
动态链接产生的.so文件在和主程序文件一起编译的时候不能使用-static选项。如果强制使用的话,虽说可以通过链接,但是执行的时候会发生这样的错误:[sonic@Rex dynamic-lib-g++]$ g++ -static Main.cpp libSay.so
[sonic@Rex dynamic-lib-g++]$ ./a.out
-bash: ./a.out: /usr/lib/libc.so.1: bad ELF interpreter: No such file or directory
而静态链接产生的.a文件时可以和主程序文件一起用-static编译的。
[sonic@Rex static-lib-g++]$ g++ -static Main.cpp libHello.a
[sonic@Rex static-lib-g++]$ ./a.out
hello
hi
[1]如果安装过Intel的arm-linux交叉工具链,默认的ldd可能会定位到arm-linux目录下,所以为避免不必要的错误加上了绝对路径
相关文章推荐
- Beyond the C++ Standard Library: An Introduction to Boost by Bjцrn Karlsson
- boost::bind实践2——来自《Beyond the C++ Standard Library ( An Introduction to Boost )》
- Beyond the C++ Standard Library: An Introduction to Boost -- Library 5.2 Usage
- Beyond the C++ Standard Library: An Introduction to Boost
- boost::function实践——来自《Beyond the C++ Standard Library ( An Introduction to Boost )》
- Beyond the C++ Standard Library: An Introduction to Boost: Björn Karlsson: 9780321133540: Amazon.com: Books
- Introduction to Programming with c++ 13-7 BinaryIO
- Programming Tips: 2) An introduction to C++ Traits
- 【UE4官方文档翻译】Introduction to C++ Programming in UE4 (介绍UE4中的C++编程)
- 【C++】C++0x :: Introduction to some amazing features
- C&C++ library introduction(3)
- Introduction to Algorithms Chapter 12: Binary Search Tree
- [LeetCode] Flatten Binary Tree to Linked List 将二叉树展开成链表 C++
- Introduction to Glide, Image Loader Library for Android, recommended by Google
- Introduction to Glide, Image Loader Library for Android, recommended by Google
- Introduction to the Standard Template Library
- How to compile C++ boost library with Intel C++ compiler
- Introduction to JSP Standard Tag Library (JSTL Basics)
- An Introduction to XML Data Binding in C++
- 【LeetCode 114】Flatten Binary Tree to Linked List (C++)