怎样建一个库
几个文件
在堆代码的过程中,最常用到的文件有:头文件(.h)、源文件(.c/.cpp)、目标文件(.o/.obj)、库文件(.a/.lib和.so/.dll)和可执行文件(.out/.exe)。其中,头文件和源文件是用ACSII字符码出来的,处理器是读不懂这些字符的,所以需要编译器把它们翻译成二进制;目标文件、库文件和可执行文件都是经编译器处理之后的二进制文件,这是用户不可读的,所以打开这些文件也只是乱码。
由于库文件和可执行文件是由目标文件进一步生成的,因而它们存储的内容并无太大差别,即代码和数据。在linux中,三者都是以ELF格式(Executable Linkable Format)存储的,可以用file命令来查看它们的文件信息:
ELF格式如下:
由图可知,ELF文件的开头是一个“文件头”,它描述了整个文件的文件属性。比如文件是可执行、静态链接还是动态链接等,以及入口地址(如果是可执行文件)、目标硬件、目标操作系统等信息; 文件头还包括一个段表(Section Table),段表其实是一个描述文件中各个段的数组。段表描述了文件中各个段在文件中的偏移位置及段的属性等,从段表里面可以得到每个段的所有信息。
文件头后面就是各个段的内容,比如代码段保存的就是程序的指令,数据段保存的就是程序的静态变量等。如果是可执行文件,在它运行成为进程时,这些段中的内容会加载到进程空间的相应位置。
关于库文件
有这样一种思想,把多个编译好的目标代码文件打包到一个容器里面,这种思想的目的就是提供API接口,
避免重新去造轮子。而库文件,就是这个容器。库文件有两类:
1、静态库(.a): 用来链接的目标代码库,会成为应用程序的一部分。
2、动态库,也称共享库(.so):有两种使用方法。
A: 在运行过程中动态链接。 这些库在编译或链接阶段必须可以访问,这些共享的对象不会成
为可执行文件的组件但是会参与其执行过程。
B: 在运行过程中动态地加载和卸载,比如浏览器的插件。
在linux中对库文件命名时,需要添加前缀”lib”和后缀“.a/.so”,即”libname.a”或者“libname.so”。但在链接时,
命令行中要链接的库不能包含前缀和后缀,即:gcc filename.c -lm -lpthread.
注:这个命令用到的库是math库和thread库,分别在:/usr/lib/libm.a和/usr/lib/libpthread.a
如何构建一个库
→ 静态库(.a)
静态库的建立和使用是非常简单的:
1. 编译源文件:gcc -Wall -c test1.c test2.c
2. 生成库文件:ar -cvq libtest.a
3. 链接库文件:gcc -o exename source.c libtest.a 或者
gcc -o exename source.c -L/path/to/lib-directory -ltest
在做毕设时,需要一个从配置文件中提取参数值。在整个系统中,它的作用很重要,单独作成一个模块会更好,以方便地和其他模块交互。
头文件config.h
/* * config/config.h * * Copyright (C) 2013, Chen Wu * * get parameter's value from the configuration file */ #ifndef _CONFIG_H #define _CONFIG_H #include <stdio.h> #include <string.h> #include <stdlib.h> #define LINE_LEN 80 /* line buffer's max length */ #define FILE_DIR "/home/chenwu/config/config" extern FILE *open_config(); extern void c_pvalue_sp(char *pname,char *pvalue); extern void c_pvalue_nsp(char *pname,char *pvalue); #endif /* _CONFIG_H */
config.c主要定义了一些内部函数,以实现对头文件中函数接口的封装,所以文件有点大,在此不给出。
配置文件<~/config/config>的内容:
e_entity e_id=123 456; e_name= 温 度小于 10度 ; e_gid = 1 2 56 ; e_pname =te mp; e_ptype =in t; e_pvalue =10; e_tvalue =1 0; e_derection =< ; e_dis =温度小于10°; e_private = nu ll; e_wight=1 ; e_cflag=RTC; e_stat=0; e_repflag= 0; e_end
测试文件test.c内容为:
/* 从配置文件提取e_gid的参数,不允许Space/Tab */ #include "config.h" int main() { char *value = malloc(60); FILE *f_open; f_open = open_config(); c_pvalue_nsp("e_gid",value); puts(value); free(value); fclose(f_open); return 0; }
现在,新建libconfig.a库,再通过测试文件使用这个库中的接口:
→ 动态库(.so)
动态库的建立步骤、链接方法和静态库基本一致,如下:
gcc -Wall -fPIC -c *.c
gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o
mv libctest.so.1.0 /opt/lib
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so.1
ln -sf /opt/lib/libctest.so.1.0 /opt/lib/libctest.so
链接命令:gcc -Wall -I/path/to/include-files -L/path/to/libraries prog.c -lctest -o prog
注:这里的libname.so.x.x是版本化库,x代表版本号。另外,作为插件形式使用的共享不做介绍。
库文件的路径:
在程序运行期间,为了使库文件能够被程序链接到并使用,用户必须配置系统环境,以使得这些库文件能够被搜索到。可以使用以下任何一种方法:
1、把库文件添加到/etc/ld.so.conf中:
/usr/X11R6/lib
/usr/lib
/usr/lib/sane
/usr/lib/mysql
/opt/lib
添加之后需要以root权限执行ldconfig命令,以使ld.so可以搜索到。
2、添加特定路径到库缓存中:(root权限)
ldconfig -n /opt/lib </opt/lib中有libctest.so>
这种方式的系统配置在系统重启后不会再起作用。
3、配置环境变量LD_LIBRARY_PATH:
命令:export LD_LIBRARY_PATH=/lib/directory:$LD_LIBRARY_PATH
库文件路径的配置,就是为了是库文件加载器ld.so在运行时能够找到共享库文件。
在建库之前
由前面的介绍可以看到,新建一个自己的库是很容易的。在我看来,建立一个库的难点在于生成库文件的lib.c和lib.h的编写。对于一个用来提供接口的库来说,源文件应该主要用作内部函数的实现和接口的封装,而头文件最好只是起到一个声明接口函数的作用。下面是关于如何写头文件的一些tips:
→ 为系统的每一个模块建立一个头文件。 每个模块可能由多个编译单元组成,如.c或.asm文件。但是一个模块往往只会实现系统的某一个方面。在嵌入式系统中,比较明显的模块有设备驱动(A/Der)和通信协议(FTP)等。
→ 头文件应该包含这个模块描述的接口的函数原型。 对A/D的头文件adc.h来说,应该包含的函数原型有:adc_init(),adc_select_input(),和adc_read()。
→ 不要包含这个模块的源文件中使用的函数和宏定义。在源文件中定义自己的接口时,很有可能会定义一些其它的函数,用来实现接口的封装。这样的函数应该,从而在模块A调用模块B时,只能通过使用定义在moduleB.h中的接口。
→ 头文件里不能有变量声明和可执行代码。 内联函数(inline function)可以是个例外。
→ 不要在头文件中暴露任何变量。这是因为在头文件中,extern是经常使用的,而一个好的封装,就必须隐藏一些数据。在源文件中定义的变量,只要有可能,就应该用static关键字限制在这个文件里头。
→ 不要暴露这个模块中特定的数据结构。 也就是说在头文件中不应该出现结构定义,如“struct { … }foo;”。如果你有数据类型要从模块B传进传出,那么模块A可以声明一个它的实例。应该怎样做呢?在模块B的源文件中声明这个结构体,然后在头文件中定义这个结构体的类型——”typedef struct foo modulB_type“。