背景
要给甲方爸爸A演示一下公司的产品,但是我们公司的产品,最终都是要跟爸爸们的产品结合在一起的,等于说要把甲方爸爸的代码抽出来编译成一个so,供我们的产品的java代码来调用——这本来是个很常见的场景——但是问题在于,甲方爸爸的这些代码,是在它的另一个乙方儿子上的so的基础上完成的,那么场景就变成了如下:
之前没有自己搞过这样的逻辑,趁此机会,尝试一下,以此为记;
尝试
PS:因为自己一直做的是C(也会有C++)的开发,所以当前的demo演示里,我们来调用甲方爸爸的so时,用C的程序来代替Java的程序(这里后面另外有一部分工作要做,看看要不要写在本篇里);
g++生成so
test.cpp代码:
#include <stdio.h>
int test_func(int a, int b)
{
printf("test_func ==> a = %d, b = %d\n", a, b);
return (a+b);
}
——我有罪,用c风格的代码来伪装C++
test.h:
#ifndef TEST_H_
#define TEST_H_
int test_func(int a, int b);
#endif
编译命令:
g++ test.cpp -fPIC -shared -o libtest.so
注意:
(1)编译生成的so的命名,必须是lib开始的;
(2)编译选项说明:
A、-shared:
该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接,相当于一个可执行文件;
B、-fPIC:
表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的;
g++生成模拟调用的可执行程序
main.cpp代码:
#include <iostream>
#include "test.h"
int main()
{
test_func(1, 2);
std::cout<< " after test_func" << std::endl;
return 1;
}
编译命令:
g++ main.cpp -L. -ltest -o main
注意:
(1)编译选项说明:
A、-L.:
表示要连接的库在当前目录中;
B、-ltest:
编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称;-ltest就等于查找libtest.so的so;
模拟调用的可执行程序调用so
直接执行 ./main,出错:
出错信息显示没有找到要加载的libtest.so,这里是一个坑;简单来说,就是有的时候,如果通过 -L.指定了从当前执行文件的目录来寻找要加载的so路径时,执行文件有时可以成功加载so,但有的时候就不可以,这种情况下,需要
手动设置LD_LIBRARY_PATH:
export LD_LIBRARY_PATH=/home/admin/cus:$LD_LIBRARY_PATH
手动设置后,成功执行:
通过ldd来查看生成的main,也可以看到libtest.so已经能够找到了;
至此,第一阶段结束;
在so中链接外部so进行编译
下面在上面的尝试的基础上进行更进一步的测试,在test.cpp里添加对另一个so:libt2sdk.so 里的定义的类的调用;
对应的头文件:t2sdk_interface.h
对应的so:libt2sdk.so
为方便调试,把头文件和so都放在main.cpp的同级目录下;
编译生成新的so
test.cpp代码:
代码量比较大,就不贴代码了,这里include了t2sdk_interface.h,下面是新添加的调用乙方二儿子的so里的接口函数:
test.h:
#ifndef TEST_H_
#define TEST_H_
int test_func(int a, int b);
int test_func_new();
#endif
so的编译命令:
g++ test.cpp -fPIC -shared -o libtest.so -L. -lt2sdk
——与没有链接so时相比,在编译新的so时,就需要指定要链接的libt2sdk.so及其路径;
main.cpp:
#include <iostream>
#include "test.h"
int main()
{
test_func(1, 2);
test_func_new();
std::cout<< " after test_func" << std::endl;
return 1;
}
main的编译命令不变:
g++ main.cpp -L. -ltest -o main
执行成功:
总结
1.编译选项的意义(-fPIC、-shared、-L. 。。。。。)
2.注意
A、如果so和最后的可执行文件都是用g++编译的,那么在so所对应的头文件里,不要用 extern “C” 来解释目标函数,因为这是为了把g++编译的so给gcc编译的可执行文件调用,而如果so和可执行文件都是用g++编译的话,两者的编译和命名规则都是统一的,就不必另外进行设置了;
B、哪怕在编译可执行文件时,通过 -L. 说明了链接的so在当前的编译路径下,也会出现编译成功,但是执行时找不到so的问题,需要通过设置LD_LIBRARY_PATH来保证能够找到so:
C、当编译的so(A)里,链接别的so(B)时,在编译A时,就需要通过编译命令来制定B的路径及名称;