文章目录
GCC 版本
1. c代码中使用dlopen/dlsum动态加载动态库(不使用头文件)
制作so文件
首先先制作制作so文件:libadd_c.so
[ add.c]
int add(int a, int b) {
return a + b;
}
编译:
gcc -shared -fpic -o libadd_c.so add.c
-shared 生成共享目标文件,通常用在建立共享库时
-fpic 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。
这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
参考:gcc-4.9.4命令文档
执行编译任务,输出相应的libadd_c.so文件。
编写测试函数
[ test.cpp]
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <iostream>
using namespace std;
int main()
{
int a = 0;
void *handle = dlopen("./libadd_c.so", RTLD_LAZY);
if(!handle) {
printf("open lib error\n");
cout<<dlerror()<<endl;
return -1;
}
typedef int (*add_t)(int a, int b);
add_t add = (add_t) dlsym(handle, "add");
if(!add) {
cout<<dlerror()<<endl;
dlclose(handle);
return -1;
}
a = add(4, 4);
printf("a = %d\n",a);
dlclose(handle);
return 0;
}
编译:
g++ test.cpp -ldl -o test
运行
./test
输出为:8
注意:typedef int (*add_t)(int a, int b);声明一个函数指针。
首先你要明白函数指针的概念
int *p(int ,int );//声明一个函数
int (*p)(int ,int);//声明一个函数指针
typedef int(*add_t)(int, int);
就是把这个类型的函数指针的声明变为add_t
在使用动态链接库libadd_c.so文件的时候,主要使用到了四个函数:
(1) dlopen()
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
a.根据环境变量LD_LIBRARY_PATH查找
b.根据/etc/ld.so.cache查找
c.查找依次在/lib和/usr/lib目录查找。
flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。
(2) dlerror()
函数原型:char *dlerror(void);
功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。
(3) dlsym()
函数原型:void *dlsym(void *handle, const char *symbol);
功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数。
注意:个人感觉从dlsym()寻找到内存中指针位置这个操作还是很麻烦的。
(4) dlclose
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。
链接:https://www.jianshu.com/p/f055b347f338
2. c代码中使用类似静态库的调用(使用头文件)
这种方式生成的程序会在启动时候就加载so动态库。
方式1:类似静态库的调用(使用头文件)
这种方式生成的程序会在启动时候就加载so动态库。
add.h
int add(int x, int y);
add.c
#include "add.h"
int add(int x, int y) {
return (x + y);
}
main.c
#include <stdio.h>
#include "add.h"
int main()
{
int sum = add(7, 8);
printf("7+8 = %d\n", sum);
return 0;
}
编译so,生成libadd.so。
gcc -shared -o libadd.so add.c
编译main,使用-L./指定add库在当前目录。
gcc -o main main.c -L./ -ladd
链接:https://blog.csdn.net/shaosunrise/article/details/81161064
3. so动态库中调用so动态库
add.h
int add(int x, int y);
add.c
#include "add.h"
int add(int x, int y) {
return (x + y);
}
sum.h
void printsum(int a, int b);
sum.c
#include "sum.h"
#include <stdio.h>
#include "add.h"
void printsum(int a, int b){
int sum = add(a, b);
printf("%d+%d = %d\n", a, b, sum);
}
main.c
#include "sum.h"
int main()
{
printsum(1, 3);
return 0;
}
编译libadd.so
gcc -shared -o libadd.so add.c
编译libsum.so,需要指定libadd.so信息
gcc -shared -o libsum.so sum.c -L. -ladd
编译main,仅需要指定libsum.so
gcc -o main main.c -L. -lsum
main运行的时候同时需要libsum.so 和 linadd.so。
链接:https://blog.csdn.net/shaosunrise/article/details/81161064
Cmakelists
关于Cmakelists的用法,唯一的建议是,访问官方网站:https://cmake.org/cmake/help/v3.0
1. 以下介绍一种导入动态库的方法:
引入so文件CmakeList 配置
1.导入so文件
将so文件拷贝到项目中,路径自己定吧,只要配置的时候不出错就行,我是这样的拷贝到jniLibs文件夹中的。jniLibs下的子文件夹表示的是cpu类型,你可以少建些,但不可以不建,so文件就放在这些文件夹下,每个cpu类型都放。
(注:导入的so文件需要在库名前添加“lib”,即lib库名.so,比如我要导入库名为test的so文件,那么你得将so文件名改为libtest.so再拷贝进项目)
2.配置
add_library( ): .c或者.cpp文件要假如里面;
include_directories( ) :第三库使用到的头文件;
(1)在CMakeLists.txt中添加:
- add_library()
- set_target_properties()
- 通常情况下so文件还会附带.h头文件,这时候需要再加上include_directories()语句来定位头文件的位置
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#将指定的源文件生成链接文件,然后添加到工程中去
add_library(
#设置lib的名字
native-lib
#设置lib的类型 共享型 还有STATIC、和MODULE
SHARED
#源码的相对路径
src/main/cpp/native-lib.cpp
)
#这个地方是 系统已经集成了很多lib,然后需要find以下 作为声明,表示你要用这些lib
find_library(
#自己起一个 lib的名称
log-lib
#lib的文件名
log )
#将目标文件与库文件进行链接
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
add_library(test-lib SHARED IMPORTED)
set_target_properties(test-lib
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest-lib.so)
set(distribution_DIR ../../../../libs)
#添加lib,SHARED类型,是IMPORTED 引入的库
add_library( libyuv
SHARED
IMPORTED)
#设置 库的属性 里面是名称 ,属性:引入地址把我们的真实地址填写进去
set_target_properties( libyuv
PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/armeabi-v7a/libyuv.so)
target_link_libraries(
native-lib
#指定链接库
libyuv
# Links the target library to the log library
# included in the NDK.
${log-lib} )
STATIC:表示静态的.a的库。
SHARED:表示.so的库。
${CMAKE_SOURCE_DIR}:表示CMake.txt的当前文件夹路径。
${ANDROID_ABI}:编译时会自动根据CPU架构去选择相应的库。
3.调用
导入so文件相当于你“实现”了方法,还需要“声明”。
链接:https://blog.csdn.net/carryworld/article/details/75026171
2.实践:
2.1、创建我的共享库:MySharedLib
CMakeLists.txt
library.h
library.cpp
在CMakeLists.txt所在的目录中:
cmake ./
make
2.2.so 共享库的使用(被可执行项目调用)
创建一个名为test的可执行项目
CMakeLists.txt
按照这个顺序来导入so
1、include_directories( )
2、add_library( )
3、set_target_properties( )
4、add_executable(hello main.cpp)
5、target_link_libraries( )
也可以按照这个顺序来导入so
1、include_directories( )
2、link_libraries()
3、add_executable(hello main.cpp)
4、target_link_libraries( )
代码参考我的仓库例子:
https://github.com/HiYx/study-repository-myself-cmake-practice-moment-V1-cmakelist/tree/main/code/ch04_solib
3. 几个关键名词的区别
这里介绍常用的指令
- include_directories
- add_library + set_target_properties
- target_link_libraries
- link_directories
- link_libraries (已经废弃)
该笔记主要参考了cmake官网给的教程,如有需要请访问以下网址:
https://cmake.org/cmake/help/v3.1/
1、include_directories
只是告诉CMake这个位置可能有 .h 头文件,输入的是path
2、link_directories (不一定需要)
只是告诉CMake这个位置可能有lib,输入的是path。
该指令的作用主要是指定要链接的库文件的路径,该指令有时候不一定需要。因为find_package和find_library指令可以得到库文件的绝对路径。不过你自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。
例子如下:
link_directories(
lib
)
3、 add_library + set_target_properties
该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
[source1] [source2] [...])
-
其中表示库文件的名字,该库文件会根据命令里列出的源文件来创建。而STATIC、SHARED和MODULE的作用是指定生成的库文件的类型。STATIC库是目标文件的归档文件,在链接其它目标的时候使用。SHARED库会被动态链接(动态链接库),在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用。
-
而语法中的source1 source2分别表示各个源文件。
如果add_library 只写了属性,则通过 set_target_properties 设置 库的属性 里面是名称 ,属性:引入地址把我们的真实地址填写进去
3、target_link_libraries
要CMake去生成target,并链接对应的库。输入的是二进制文件名或着已经生成但未link的target。target_link_libraries需要指定target并且调用linker
即按照header file + .lib + .dll方式隐式调用
通过add_executable()和add_library()指令生成已经创建的目标文件。
4、link_libraries
已经废弃:https://cmake.org/cmake/help/v3.0/command/link_libraries.html
target_link_libraries 与link_libraries 也就是这个两个函数的作用应该差不多
一个区别是:
target_link_libraries 要在 add_executable 之后
link_libraries 要在 add_executable 之前
附,三个有效的cmakelist.txt的例子
target_link_libraries 版
cmake_minimum_required(VERSION 3.12)
include_directories("/usr/local/Cellar/vips/8.6.0/include")
include_directories("/usr/local/Cellar/glib/2.54.2/lib/glib-2.0/include")
include_directories("/usr/local/Cellar/glib/2.54.2/include/glib-2.0")
link_directories("/usr/local/Cellar/glib/2.54.2/lib")
link_directories("/usr/local/opt/gettext/lib")
link_directories("/usr/local/Cellar/vips/8.6.0/lib")
add_executable(vipc main.c)
target_link_libraries(vipc vips)
target_link_libraries(vipc glib-2.0)
target_link_libraries(vipc gobject-2.0)
link_libraries版:
cmake_minimum_required(VERSION 3.12)
include_directories("/usr/local/Cellar/vips/8.6.0/include")
include_directories("/usr/local/Cellar/glib/2.54.2/lib/glib-2.0/include")
include_directories("/usr/local/Cellar/glib/2.54.2/include/glib-2.0")
link_directories("/usr/local/Cellar/glib/2.54.2/lib")
link_directories("/usr/local/opt/gettext/lib")
link_directories("/usr/local/Cellar/vips/8.6.0/lib")
link_libraries(vips)
link_libraries(glib-2.0)
link_libraries(gobject-2.0)
add_executable(vipc main.c)