linux应用编程——静态库和动态库

4.动态库和静态库

如果编写了一个很有价值的算法,想让别人使用,但又不想公开代码。该怎么办?

在C/C++中,使用库的技术,将编译好的代码提供给他人使用。库分为动态库和静态库,在windows系统下,动态库扩展名为dll,静态库扩展名为lib;在linux系统下,动态库扩展名为so,静态库扩展名为a。

4.1动态库

4.1.1动态库的生成

Linux下动态库的规范命名:libxxx.so。前缀lib,库名称xxx,扩展名so。

在/home/lzb/linux_projects/project4/lib目录下,有other.cpp、other.h,内容如下:

//other.cpp

#include <stdio.h>

#include "other.h"

void fun(void)

{

printf("hello linux!\n");

}

//other.h

void fun(void);

方法1:命令行编译

cd /home/lzb/linux_projects/project4/lib

编译:加-fPIC选项

g++ -c -fPIC other.cpp -o other.o

链接:加-shared选项

g++ -shared other.o -o libother.so

方法2:使用Makefile

在/home/lzb/linux_projects/project4目录下,创建文件Makefile,内容如下:

#Makefile:shared library

EXE=libother.so

SUBDIR=lib

 

CXX_SOURCES = $(foreach dir, $(SUBDIR), $(wildcard $(dir)/*.cpp))

CXX_OBJECTS = $(patsubst %.cpp, %.o, $(CXX_SOURCES))

DEP_FILES = $(patsubst %.o, %.d, $(CXX_OBJECTS))

 

$(EXE):$(CXX_OBJECTS)

g++ -shared $(CXX_OBJECTS) -o $(EXE)

 

%.o:%.cpp

g++ -c -fPIC -MMD $< -o $@

 

-include $(DEP_FILES)

 

clean:

rm -rf $(CXX_OBJECTS) $(DEP_FILES) $(EXE)

提交:other.h、libother.so、使用平台。

4.1.2动态库的使用—自动加载

在/home/lzb/linux_projects/project5/lib目录下,有libother.so;在/home/lzb/linux_projects/project5/include目录下,有other.h;在/home/lzb/linux_projects/project5目录下,有hello.cpp,其内容如下:

#include "./include/other.h"

int main()

{

fun();

return 0;

}

方法1:命令行编译

cd /home/lzb/linux_projects/project5

(1)编译:

g++ -c hello.cpp -o hello.o

(2)链接:-L指定库文件的位置,-lother指定使用libother.so库文件

g++ hello.o -o hello -L./lib -lother

(3)运行:

./hello

./hello: error while loading shared libraries: libother.so: cannot open shared object file: No such file or directory

为什么操作系统找不到libother.so?

操作系统寻找库文件时,会从标准位置开始寻找。对于Linux系统,寻找目录为/lib、/usr/lib、/usr/local/lib。若在这些目录没有找到对应的库文件,则从LD_LIBRARY_PATH环境变量里寻找。

在Linux中,库文件要么放在标准位置,要么放在LD_LIBRARY_PATH指定的位置。常采用第二种方法。

(4)设置环境变量LD_LIBRARY_PATH:

export LD_LIBRARY_PATH=./lib

(5)查看环境变量LD_LIBRARY_PATH:

echo $LD_LIBRARY_PATH

./lib

(6)运行:

./hello

hello linux!

(7)查看hello依赖的库:

readelf -d hello

基本库:libc.so标准C库、libstdc++.so标准C++库,这2个库不需要在命令行中指定,g++会默认链接。

方法2:Makefile

通常使用的第三库目录结构,一般是libxxx/lib存放库文件、libxxx/include存放头文件。因此在/home/lzb/linux_projects/project5/lib目录下,有libother.so;在/home/lzb/linux_projects/project5/include目录下,有other.h;在/home/lzb/linux_projects/project5/src目录下,有hello.cpp,在/home/lzb/linux_projects/project5目录下,有Makefile。

#include “other.h”表示在当前目录下搜索头文件other.h,若不存在则去系统的头文件目录下查找。Linux系统头文件目录为:/usr/include、 /usr/local/include。

#include <other.h>表示在系统的头文件目录下查看other.h,g++编译器可以使用-l选项来指定自定义的头文件查找目录。

//hello.cpp

#include <other.h>

int main()

{

fun();

return 0;

}

#Makefile

EXE=hello

SUBDIR=src

 

#CXXFLAGS:编译选项

CXXFLAGS += -I/home/lzb/linux_projects/project5/include

#LDFLAGS:链接选项

LDFLAGS += -L/home/lzb/linux_projects/project5/lib -lother

#LDFLAGS += -L/home/lzb/linux_projects/project6/lib -lother

 

CXX_SOURCES = $(foreach dir, $(SUBDIR), $(wildcard $(dir)/*.cpp))

CXX_OBJECTS = $(patsubst %.cpp, %.o, $(CXX_SOURCES))

DEP_FILES = $(patsubst %.o, %.d, $(CXX_OBJECTS))

 

$(EXE):$(CXX_OBJECTS)

g++ $(CXX_OBJECTS) -o $(EXE) $(LDFLAGS)

 

%.o:%.cpp

g++ -c $(CXXFLAGS) -MMD $< -o $@

 

-include $(DEP_FILES)

 

clean:

rm -rf $(CXX_OBJECTS) $(DEP_FILES) $(EXE)

命令行编译运行:

cd /home/lzb/linux_projects/project5

make

export LD_LIBRARY_PATH=./lib

./hello

上面Makefile主要是使用命令行:

g++ -c -I/home/lzb/linux_projects/project5/include -MMD src/hello.cpp -o src/hello.o

g++  src/hello.o -o hello -L/home/lzb/linux_projects/project5/lib -lother

上述的-L和-I中,可以使用绝对路径,也可以使用相对路径,如:

g++ -c -I./include -MMD src/hello.cpp -o src/hello.o

g++  src/hello.o -o hello -L./lib -lother

注:-I/home/lzb/linux_projects/project5/include中的-I是include的首字符i的大写;-L/home/lzb/linux_projects/project5/lib -lother中的-l是libxxx中的l,也是L的小写。

4.1.3动态库的使用—手工加载

在windows平台下,动态库分为隐式调用和显式调用。同理,在Linux平台下,动态库分为自动加载和手工加载。在前面4.1.2中讲的是动态库的自动记载,在4.1.3会讲解动态库的手工加载。

在windows平台下,在应用程序中使用函数来调用。(1)LoadLibrary函数是加载DLL且返回句柄;(2) GetProcAddress是根据句柄找到动态库中的函数,并返回此函数的指针;(3)使用此函数指针来调用动态库中的某个函数;(4)FreeLibrary释放动态库。

在Linux平台下,也是在应用程序中使用函数来调用。(1)dlopen函数用来加载动态库;(2)dlsym根据句柄找到动态库中的函数,并返回此函数的指针;(3)使用此函数指针来调用动态库中的某个函数;(4)dlclose卸载动态库。注:手工加载的方式只适合于调用C库。

使用这3个函数,需要添加头文件#include <unistd.h>、#include <dlfcn.h>;在链接时需要添加链接选项-ldl;库文件libother.so是使用other.c和other.h来编译;库文件libother.so是使用other.cpp和other.h来编译,但other.h的函数声明前加extern “C”。

//hello.cpp

#include <stdio.h>

#include <unistd.h>

#include <dlfcn.h>

 

int main()

{

//步骤1:加载库返回一个句柄(handle),指向加载到内存里的库

void * libother = dlopen("lib/libother.so", RTLD_NOW);

if(!libother)

{

printf("Load Library Failed...\n");

return -1;

}

 

//步骤2:找到函数符号

typedef void (*SO_FUNCTION) (void);

SO_FUNCTION fun = (SO_FUNCTION) dlsym(libother, "fun");

if(!fun)

{

printf("Can Not Find Symbol...\n");

return -1;

}

 

//步骤3:调用函数

fun(1, 2);

 

//步骤4:卸载动态库

dlclose(libother);

return 0;

}

g++ hello.cpp -o hello -ldl

export LD_LIBRARY_PATH=./lib

./hello

注意:

(1)手工加载不需要提供头文件,如other.h。

(2)手工加载实际上是搜索动态库中的函数符号(未修饰,即函数名)。若是C++库,其函数会在编译动态库时进行名称修饰;C库函数名称不变。解决方法:在进行C++库编译时,在头文件的函数声明前,加上extern “C”,表示此函数按照C语言的方式来编译。

4.2静态库

4.2.1静态库的生成和使用

静态库的命名方式:libxxx.a。

(1)编译:得到*.o文件;

(2)打包:将多个*.o文件打包,并不是链接

ar -rcs   libxxx.a  file1.o  file2.o ... fileN.o

如:

g++ -c other.cpp -o other.o

ar -rcs libother.a other.o

提交静态库:other.h、libother.a、使用平台。

在/home/lzb/linux_projects/project6/lib目录下,有libother.a;在/home/lzb/linux_projects/project6/include目录下,有other.h;在/home/lzb/linux_projects/project6/src目录下,有hello.cpp。

方法1:由于静态库本质上是*.o文件的打包,因此可以像*.o一样使用libxxx.a。

cd /home/lzb/linux_projects/project6/src

g++ -I../include hello.cpp ../lib/libother.a -o hello

./hello

其中,-I../include中的-I是include首字符的大写;执行hello不需要再设置环境变量LD_LIBRARY_PATH,因为静态库编译到可执行文件中去了,在执行hello时不需要静态库的支持。

方法2:和动态库使用方法一样

g++ -I../include hello.cpp -o hello -L../lib -lother

./hello

其中,-L../lib -lother中的-l是L的小写。

通过-L和-l的方法来指定静态库,和使用动态库的方法一样。

4.2.2动态库和静态库的区别

使用静态库,生成可执行程序hello。hello会包含静态库libother.a的代码,因此在执行时不需要静态库的支持,但也导致可执行程序过大。

使用动态库,生成的可执行程序中不会包含libother.so的代码,因此在执行时需要动态库的支持,但可执行程序小。

问题1:在前面执行链接过程时,通过-L../lib -lother来指定库,但是这个库可以是libother.a,也可以是libother.so。当lib目录下同时有libother.a和libother.so时,它到底会使用哪个库?                           

答案:优先选择动态库libother.so。

问题2:若要指定使用静态库libother.a,该怎么办?

由于静态库可以像*.o一样使用,因此使用全路径的方式来使用静态库,如:

g++ -I../include hello.cpp ../lib/libother.a -o hello

4.3使用C库

在C语言中,用函数名来标识一个函数,因此函数不可重载。

在C++语言中,用函数名+参数类型+名称修饰来标识一个函数,因此可以函数重载。

在windows平台下,生成动态库时,其头文件中的函数声明需要加上extern “C”。extern “C”标识编译器将这个函数按照C语言的方式来编译,因此动态库无法使用函数重载。由于编译器的不同,导致C++函数的名称修饰规则不同,因此若动态库中的函数按照C++方式来编译,其他编译器的代码会无法调用此动态库。

而在Linux平台下,其编译器统一使用GCC,导致其名称修饰规则一样,因此C++方式编译的动态库,可以在任何Linux平台下使用。总结:C++代码可以调用C++库。

问题:Linux平台下,C++代码如何调用C库呢?

C++代码调用C++库,通过修饰过的函数符号来寻找库中对应的函数;若调用C库,通过修饰过的函数符号肯定找不到对应的函数,因此C库中的函数符号就是函数名,没有修饰过。

解决方法:修改C库的头文件,在函数声明前加上extern “C”。表示这个库按照C语言方式来编译;并且调用C库时,按照函数名的方式来搜索对应函数。

extern “C”的3种使用方式:

(1)声明一个函数:

extern "C" void fun(void);

(2)声明多个函数:

extern "C"

{

void fun1(void);

void fun2(void);

}

(3)声明整个头文件

extern "C"

{

#include “other.h”

}

一般情况下,C库的提供者会提供一个兼容C/C++的头文件:

//other.h

#ifdef __cplusplus

extern "C" {

#endif

void fun(void);

#ifdef __cplusplus

}

#endif

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值