Cmakelists 与gcc 调用so库文件,几个名词解释

文章目录

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)

上一篇:CMake


下一篇:add_subdirectory命令