Linux环境编译静态库动态库说明

来源:互联网 发布:数据库原理中的设计题 编辑:程序博客网 时间:2024/06/03 14:32

目 录
1. 引言
1.1 编写目的
1.2 环境
1.3 参考资料
1.4 背景知识
2. 动态库
2.1 C调用
2.1.1 纯源码
2.1.2 源码+静态库
2.2 JNI调用
2.2.1 纯源码
2.2.2 源码+静态库
3. 静态库
3.1 纯源码
3.2 源码+静态库
4. 命令参数说明

**

1.引言

**

1.1编写目的

梳理Linux下静态库和动态库的编译使用,作为之后工作的指导。

1.2环境

Linux64位系统

1.3参考资料

参考网址如下:
http://www.ibm.com/developerworks/cn/linux/l-cn-linklib/index.html
http://www.cnblogs.com/wblyuyang/archive/2011/12/26/2301744.html
http://www.oschina.net/question/54100_32476
http://blog.csdn.net/xiaojianpitt/article/details/5652223

1.4背景知识

库是一种软件组件技术,库里面封装了数据和函数。库的使用可以使程序模块化。
Windows系统包括静态链接库(.lib文件)和动态链接库(.dll文件)。Linux通常把库文件存放在/usr/lib或/lib目录下。Linux库文件名由:前缀lib、库名和后缀3部分组成,其中动态链接库以.so最为后缀,静态链接库通常以.a作为后缀。
在程序中使用使用静态库和动态库时,他们载入的顺序是不同的。静态库的代码在编译时就拷贝的应用程序中,这样的优点是节省编译时间。动态链接库时程序在开始运行后调用库函数时才被载入。

2.动态库

  1. 动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。库中函数和变量的地址是相对地址,不是绝对地址,其真实地址在调用动态库的程序加载时形成。
  2. 动态链接库的名称有别名(soname), 真名(realname)和链接名(linker name)。别名由一个前缀lib,然后是库的名字,再加上一个后缀“.so”构成。真名是动态链接库真实名称,一般总是在别名的基础加上一个小版本号,发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。
    3.在动态链接库安装的时候,总是复制文件到某个目录下,然后用一个软连接生成别名,在库文件进行更新的时候,仅仅更新软链接即可。

2.1C调用

2.1.1纯源码
下面通过一个实例来了解如何自己生成动态库和使用动态库
2.1.1.1创建程序文件
1>在test文件夹下创建四个文件: test.c ,add.c,sub.c,math.h
2> test.c文件中的内容:

#include <stdio.h>#include "math.h"int  main(void){              printf("sum =%d\n",add(5,3));              printf("sub= %d\n",sub(5,3));              return 0;     }

3>math.h文件中的内容:

#ifndef __MATH__#define __MATH__int add(int  a,int  b);int sub(int  a,int  b);#endif

4>add.c文件中的内容:

#include "math.h"int  add(int a, int b){      return a +b;}

5>sub.c文件中的内容:

#include "math.h"int  sub(int a, int b){       return  a - b;}

2.1.1.2动态库的生成和使用
1.生成动态链接库
1>首先生成目标文件,但是此时要加编译器选项-fpic和链接器选项-shared,
gcc -fpic -c add.c
gcc -fpic -c sub.c
生成中间文件add.o和sub.o
2>其次生成动态库
gcc -shared –o libmymath.so add.o sub.o
生成动态库libmymath.so,libmymath.so就是我们生成的目标动态库。我们以后使用动态库和test.c程序生成可执行程序
说明:
以上两部也可以合成一步搞定:
gcc -fpic -shared add.c sub.c -o libmymath.so
2.使用动态链接库
在编译程序时,使用动态链接库和静态库是一致的,使用”-l库名”的方式,在生成可执行文件的时候会链接库文件。
1>使用命令:
gcc -o test test.c -L ./ -lmymath
2>-L指定动态链接库的路劲,-lmymath链接库函数mymath。-lmymath是动态库的调用规则。Linux系统下的动态库命名方式是lib*.so,而在链接时表示位-l*,*是自己命名的库名。
3>但是程序会提示如下错误
error while loading shared libraries: libmymath.so: cannot open shared object file: No such file or direct
这是因为程序运行时没有找到动态链接库造成的。程序编译时链接动态库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。
4>使用以下方法可以解决此问题
a. 在linux下最方便的解决方案是拷贝libmymath.so到绝对目录 /usr/lib 下(但是,要是root才可以,因此要使用sudo)。就可以生成可执行程序了
b.第二种方法是:将动态链接库的目录放到程序搜索路径中,可以将库的路径加到环境变量LD_LIBRARY_PATH中实现:
export LD_LIBRARY_PATH=pwd:$LD_LIBRARY_PATH
5>运行可执行程序test: ./test
运行结果如下:
这里写图片描述
2.1.2源码+静态库
1>在test文件夹下创建三个文件: add.c,sub.c,math.h
2>math.h文件中的内容:

#ifndef __MATH__#define __MATH__int add(int  a,int  b);int sub(int  a,int  b);#endif

3>add.c文件中的内容:

#include "math.h"#include "mylib.h"int  add(int a, int b){    welcome();    outString("it's add\n");      return a +b;}

4>sub.c文件中的内容:

#include "math.h"#include "mylib.h"int  sub(int a, int b){    welcome();    outString("it's sub\n");      return  a - b;}   

其中,add.c和sub.c都加载了libmylib.a库(在3.1中定义)的头文件mylib.h,调用了里面的函数,mylib.h如下:

#ifndef _MYLIB_H_#define _MYLIB_H_void weclome(void);void outString(const char *str);#endif

5>生成动态库
将使用到的静态库libmylib.a放到编译动态库的目录下,运行
gcc -fpic -shared add.o sub.o -o libmymath.so -I./ -L. -lmylib
其中将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名。
6>将动态库copy到Linux的库目录(/usr/lib或者/lib)下:
cp libmymath.so /usr/lib

编写调用库函数的测试程序test.c:

#include <stdio.h> #include "math.h"int  main(void){              printf("sum =%d\n",add(5,3));              printf("sub= %d\n",sub(5,3));              return 0;     }

7>使用动态库编译:gcc -o test test.c -L ./ -lmymath
8>调用test:./test
结果如下:
这里写图片描述

2.2JNI调用

2.2.1纯源码
2.2.1.1创建程序文件
1.创建C层文件
1>首先在test文件夹下创建三个文件: add.c,sub.c,math.h
2>math.h文件中的内容:

#ifndef __MATH__#define __MATH__int add(int  a,int  b);int sub(int  a,int  b);#endif

3>add.c文件中的内容:

#include "math.h"int  add(int a, int b){      return a +b;}

4>sub.c文件中的内容:

#include "math.h"int  sub(int a, int b){       return  a - b;}

2.创建JNI层文件
1>首先,创建JAVA层的接口文件TestMethods .java,如下所示:

package com.test.example;public class TestMethods {    private static TestMethods jni = new TestMethods();    private TestMethods() {        System.loadLibrary("testso");    }    public static TestMethods getInstance() {        return jni;    }    public native int testadd(int numA,int numB);    public native int testsub(int numA,int numB);}

2>在com文件夹上一层使用javah命令生成JNI层头文件,运行javah -classpath . -jni com.test.example.TestMethods,生成com_test_example_TestMethods.h,如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_test_example_TestMethods */#ifndef _Included_com_test_example_TestMethods#define _Included_com_test_example_TestMethods#ifdef __cplusplusextern "C" {#endif/* * Class:     com_test_example_TestMethods * Method:    testadd * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testadd  (JNIEnv *, jobject, jint, jint);/* * Class:     com_test_example_TestMethods * Method:    testsub * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testsub  (JNIEnv *, jobject, jint, jint);#ifdef __cplusplus}#endif#endif

3>之后把com_test_example_TestMethods.h放到c文件夹目录下,并创建JNI层实现文件testjni.c,要遵从JNI的编写规范。

#include <stdio.h>#include "math.h"#include "com_test_example_TestMethods.h"JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testadd  (JNIEnv *jEnv, jobject jObj, jint jnumA, jint jnumB)  {    printf("testadd start!\n");    int numC = add(jnumA,jnumB);    printf("sum =%d\n",numC);    printf("testadd end!\n");    return numC;  }JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testsub  (JNIEnv *jEnv, jobject jObj, jint jnumA, jint jnumB)  {    printf("testsub start!\n");    int numC = sub(jnumA,jnumB);    printf("sub =%d\n",numC);    printf("testsub end!\n");    return numC;  }

3.创建Java层调用文件
1>在上面创建TestMethods .java的目录创建test.java

package com.test.example;public class test {    public static void main(String[] args) {        System.out.println("hello");        int ret = 0;            int numA = 100;        int numB = 50;        TestMethods instance = TestMethods.getInstance();        //byte [] keyFile;         int numC = instance.testadd(numA,numB);         System.out.println( "sum number numC:" + numC);         numC = instance.testsub(numA,numB);         System.out.println( "sum number numC:" + numC);    }}

2.2.1.2动态库的生成和使用
1.生成动态链接库
gcc -I./ -I/home/hello/Desktop/jdk1.8.0/include -I/home/hello/Desktop/jdk1.8.0/include/linux -fpic -shared add.c sub.c testjni.c -o libtestso.so
其中-fpic是编译器选项,-shared是链接器选项,-I/home/hello/Desktop/jdk1.8.0/include -I/home/hello/Desktop/jdk1.8.0/include/linux是jdk目录下jni.h和jni_md.h的地址,在jni层会用到。
运行之后会生成动态库libtestso.so,使用cp libtestso.so /usr/lib将动态库放入可调用的路径,同时也可以在java接口文件中使用System.load来指定动态库路径。
2.使用动态链接库
首先编译使用的java文件,可以在src目录运行javac com/test/example/test.java 生成clas文件,之后运行java com/test/example/test,运行结果如下:
这里写图片描述

2.2.2源码+静态库
2.2.2.1创建程序文件
1.创建C层文件
1>首先在test文件夹下创建三个文件: add.c,sub.c,math.h
2>math.h文件中的内容:

#ifndef __MATH__#define __MATH__int add(int  a,int  b);int sub(int  a,int  b);#endif

3>add.c文件中的内容:

#include "math.h"#include "mylib.h"int  add(int a, int b){    welcome();    outString("it's add\n");      return a +b;}

4>sub.c文件中的内容:

#include "math.h"#include "mylib.h"int  sub(int a, int b){    welcome();    outString("it's sub\n");      return  a - b;}   

其中,add.c和sub.c都加载了libmylib.a库(在3.1中定义)的头文件mylib.h,调用了里面的函数,mylib.h如下:

#ifndef _MYLIB_H_#define _MYLIB_H_void weclome(void);void outString(const char *str);#endif

2.创建JNI层文件
1>首先,创建JAVA层的接口文件TestMethods .java,如下所示:

package com.test.example;public class TestMethods {    private static TestMethods jni = new TestMethods();    private TestMethods() {        System.loadLibrary("testso");    }    public static TestMethods getInstance() {        return jni;    }    public native int testadd(int numA,int numB);    public native int testsub(int numA,int numB);}

2>在com文件夹上一层使用javah命令生成JNI层头文件,运行javah -classpath . -jni com.test.example.TestMethods,生成com_test_example_TestMethods.h,如下:

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_test_example_TestMethods */#ifndef _Included_com_test_example_TestMethods#define _Included_com_test_example_TestMethods#ifdef __cplusplusextern "C" {#endif/* * Class:     com_test_example_TestMethods * Method:    testadd * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testadd  (JNIEnv *, jobject, jint, jint);/* * Class:     com_test_example_TestMethods * Method:    testsub * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testsub  (JNIEnv *, jobject, jint, jint);#ifdef __cplusplus}#endif#endif

3>之后把com_test_example_TestMethods.h放到c文件夹目录下,并创建JNI层实现文件testjni.c,要遵从JNI的编写规范。

#include <stdio.h>#include "math.h"#include "com_test_example_TestMethods.h"JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testadd  (JNIEnv *jEnv, jobject jObj, jint jnumA, jint jnumB)  {    printf("testadd start!\n");    int numC = add(jnumA,jnumB);    printf("sum =%d\n",numC);    printf("testadd end!\n");    return numC;  }JNIEXPORT jint JNICALL Java_com_test_example_TestMethods_testsub  (JNIEnv *jEnv, jobject jObj, jint jnumA, jint jnumB)  {    printf("testsub start!\n");    int numC = sub(jnumA,jnumB);    printf("sub =%d\n",numC);    printf("testsub end!\n");    return numC;  }

4.创建Java层调用文件
1>在上面创建TestMethods .java的目录创建test.java

package com.test.example;public class test {    public static void main(String[] args) {        System.out.println("hello");        int ret = 0;            int numA = 100;        int numB = 50;        TestMethods instance = TestMethods.getInstance();        //byte [] keyFile;         int numC = instance.testadd(numA,numB);         System.out.println( "sum number numC:" + numC);         numC = instance.testsub(numA,numB);         System.out.println( "sum number numC:" + numC);    }}

2.2.2.2动态库的生成和使用
1.生成动态链接库
gcc -I./ -I/home/hello/Desktop/jdk1.8.0/include -I/home/hello/Desktop/jdk1.8.0/include/linux -fpic -shared add.c sub.c testjni.c -o libtestso.so -I./ -L. -lmylib
其中-fpic是编译器选项,-shared是链接器选项,-I/home/hello/Desktop/jdk1.8.0/include -I/home/hello/Desktop/jdk1.8.0/include/linux是jdk目录下jni.h和jni_md.h的地址,在jni层会用到。
需要注意的是需要加载的静态库-lmylib在编译的时候需要添加-fPIC选项,否则会导致动态库生成失败。
运行之后会生成动态库libtestso.so,使用cp libtestso.so /usr/lib将动态库放入可调用的路径,同时也可以在java接口文件中使用System.load来指定动态库路径。
3.使用动态链接库
首先编译使用的java文件,可以在src目录运行javac com/test/example/test.java 生成clas文件,之后运行java com/test/example/test,运行结果如下:

这里写图片描述

3.静态库

静态库的创建和使用:
1、在一个头文件种声明静态库所导出的函数。
2、在一个源文件种实现静态库所导出的函数。
3、编译源文件,生成可执行代码。
4、将可执行代码所在的目标文件加入到某个静态库中,并将静态库拷贝到系统默认的存放库文件的目录下。

3.1纯源码

下面通过一个例子来说明:mylib.h种存放的是静态库提供给用户使用的函数的声明,mylib.c实现了mylib.h种声明的函数。
头文件:mylib.h

#ifndef _MYLIB_H_#define _MYLIB_H_void weclome(void);void outString(const char *str);#endif

源文件:mylib.c

#include <stdio.h>#include "mylib.h"void welcome(void){    printf("welcome to libmylib\n");}void outString(const char *str){    if(str != NULL)        printf("%s\n", str);}

1>编译mylib.c生成目标文件:gcc -o mylib.o -c mylib.c
2>将目标文件加入到静态库中:ar rcs libmylib.a mylib.o(该命令的说明在第4部分)
3>将静态库copy到Linux的库目录(/usr/lib或者/lib)下:
cp libmylib.a /usr/lib/ (此处需要root权限)

编写调用库函数的测试程序test.c:

#include <stdio.h>#include "mylib.h" //需加载静态库头文件int main(void){    printf("create and use library:\n");    welcome();    outString("it's successful\n");    return 0;}

4>使用静态库编译:gcc -o test test.c -lmylib
这里注意,编译时无需带上前缀和后缀。
5>运行可执行程序test: ./test
运行结果如下:
这里写图片描述

3.2源码+静态库

下面通过一个例子来说明:myupperlib.h种存放的是静态库提供给用户使用的函数的声明,myupperlib.c实现了myupperlib.h种声明的函数。
头文件:myupperlib.h

#ifndef _MYUPPERLIB_H_#define _MYUPPERLIB_H_int testadd(int numA,int numB);#endif

源文件:mylib.c

#include <stdio.h>#include "mylib.h"#include "myupperlib.h"int testadd(int numA,int numB){    printf("create and use myupperlib:\n");    welcome();    outString("it's myupperlib\n");    int numC = numA + numB;    outString("testadd end\n");    return numC;}

其中mylib.h为所需调用的另一个静态库libmylib.a的头文件:如下

#ifndef _MYLIB_H_#define _MYLIB_H_void weclome(void);void outString(const char *str);#endif

1>编译mylib.c生成目标文件:gcc -o myupperlib.o -c myupperlib.c
2>将目标文件加入到静态库中:ar rcs libmyupperlib.a myupperlib.o(该命令的说明在第4部分)
3>将静态库copy到Linux的库目录(/usr/lib或者/lib)下:
cp libmyupperlib.a /usr/lib
静态库调用的库也需要放到库目录
cp libmylib.a /usr/lib/ (此处需要root权限)

编写调用库函数的测试程序testupper.c:

#include <stdio.h>#include "myupperlib.h"int main(void){    printf("main start\n");    int mum = testadd(4,19);    printf("mum:%d\n",mum);    printf("main end\n");    return 0;}

4>使用静态库编译:gcc -o testupper testupper.c -lmyupperlib -lmylib //两个静态库都需要链接
这里注意,编译时无需带上前缀和后缀。
5>运行可执行程序testupper : ./testupper
运行结果如下:

这里写图片描述

4.命令参数说明

编译动态库所用命令形式如下:
gcc (-fpic) -shared -o libmyfunction.so(目标库名称) myfunction.c(源文件)
— fpic 使输出的对象模块是按照可重定位地址方式生成的。

— shared指定把对应的源文件生成对应的动态链接库文件。

ar 命令的命令行格式如下:
ar [-]{dmpqrtx}[abcfilNoPsSuvV] [membername] [count] archive files…
参数archive定义库的名称, files是库文件中包含的目标文件的清单, 用空格分隔每个文件
比如创建一个静态库文件的命令如下::
ar r libapue.a error.o errorlog.o lockreg.o

常用参数
  格式:ar rcs libxxx.a xx1.o xx2.o
  参数r:在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
  参数c:创建一个库。不管库是否存在,都将创建。
  参数s:创建目标文件索引,这在创建较大的库时能加快时间。(补充:如果不需要创建索引,可改成大写S参数;如果。a文件缺少索引,可以使用ranlib命令添加)