怎样建一个库

几个文件

在堆代码的过程中,最常用到的文件有:头文件(.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

View Code
/* 
 * 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“。

posted @ 2013-01-22 12:57  陈小硕  阅读(2190)  评论(1编辑  收藏  举报