so 与 so 是不同的。
Linux 与 Android 只是系统环境,不影响编译出的 so 。影响 so 能否使用是看用什么编译器,gun 编译出的与 ndk 编译出的 SO是不同的。
本文章只是实验 如何 Android 使用 JNI 封装的第三方 c 语言 so 库。
目前取得了第三方linux so 库。想在手机上使用此 so 库,必须使用 NDK 的 JNI 来封装此第三方 so 库。封装教程网上比比皆是,但是封装却始终提示:
一直认为名字都为 so ,不成功是步骤出了问题。试验了无数次我才幡然大悟,开始质疑 so 是否有差别的。这次实验就充分说明了不同编译器编译的 so 是不同的。要想实现我的目标,必须需要 NDK 来编译源码生成 so ,才能继续使用 ndk 的 jni 封装出Android 能用的 so 库。
直接上简单代码,c 与 cpp 是为了生成 c 的 so 和 c++ 的 so,他们共用一个头文件 h:
Atest.h
//本文件名
#ifndef ATEST_H
#define ATEST_H
int MyFuck(int a);
#endif
Atest.cpp
//本文件名
#include"Atest.h"
int MyFuck(int a)
{
return a+a;
}
Atest.c
//本文件名
#include"Atest.h"
int MyFuck(int a)
{
return a+a;
}
一、linux下gun编译so:
Linux 下 gun 编译:g++ -fpic -shared -o libAtestcpp.so Atest.cpp 即可得到 c++ 的 libAtestcpp.so。
Linux 下 gun 编译:gcc -c -fPIC -o Atest.o Atest.c
gcc -shared -o libAtestc.so Atest.o 即可得到 c 的 libAtestc.so。
现在我们看下这两个 so 有什么不同,linux 下用 file 名字.so 即可看到文件信息:
C++ 的:
c 的:
都是 elf 文件,64位。c 和 c++ 基本一样。
现在看看他们里面的函数是否一样,linux 下 nm -D 名字.so 即可看到里面的函数:
c++ 的:
c 的:
很清楚的看见同样函数 c 和 c++ 的 so 中动态库函数符号是明显不一样的。
因为用 gun 编译 so 肯定是不能用于NDK、不能用于jni、不能用于Android,所以接下的测试我已经完成但不再展示这种错误的做法:
在使用 NDK 的 jni 封装这个 libAtest.so 时,肯定是会报错的,因为 NDK 是识别不了 GUN 编译的 SO(大概吧)。
在封装 jni 时,报错界面:
实际错误为找不到 so 中我们自己写的函数
二、Windows下NDK编译so:
把上面三个源码用 NDK 编译,额外需要在源码目录下创建 jni 文件夹,创建 Android.mk 文件,内容为:
LOCAL_PATH := $(call my-dir)/.. #路径目录定为当前文件夹上层路径
LOCAL_LIB_PATH := $(call my-dir) #lib路径为当前路径
include $(CLEAR_VARS)
LOCAL_MODULE := Atest #生成的so名字
LOCAL_SRC_FILES := Atest.c #从这个源码编成so,cpp就改成cpp
include $(BUILD_SHARED_LIBRARY) #命令为建成 动态库
在 Android.mk 目录下运行 cmd,会自动在上一层目录生成 so 的文件夹:
obj 文件夹是中间生成物,libs 文件夹里面的是真正需要的 so 文件。图中 c 和 cpp 都生成了一下,我们只使用 c 的 so。cpp 的so 大家自己尝试吧。
c++ 的 so:
最后一行为 c++ 动态库函数符号
c 的 so:
再来比较两个 so 的文件信息:
可以看出差别不大,但是比我们用 gun 编译出的 so 多出了 ARM 架构。
接下来的步骤网上都是:如何 Android 使用 jni 封装的第三方 c 语言 so 库
我们需要刚才的生成的 c 的 so 库和我们的头文件 Atest.h :
com_sdk_HCNetSDK.h
//本文件名
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_sdk_HCNetSDK */
#ifndef _Included_com_sdk_HCNetSDK
#define _Included_com_sdk_HCNetSDK
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_sdk_HCNetSDK
* Method: NET_DVR_Cleanup
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_sdk_HCNetSDK_NET_1DVR_1Cleanup
(JNIEnv *, jobject);
/*
* Class: com_sdk_HCNetSDK
* Method: NET_DVR_Init
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_sdk_HCNetSDK_NET_1DVR_1Init
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
HCNetSDK.cpp
//本文件名
#include"com_sdk_HCNetSDK.h"
extern "C" {
#include"Atest.h"
JNIEXPORT jboolean JNICALL Java_com_sdk_HCNetSDK_NET_1DVR_1Init
(JNIEnv *, jobject)
{
int a=999;
int b=a;
a=MyFuck(a);
if(a==b)
{return false;}
else
{return true;}
}
JNIEXPORT jboolean JNICALL Java_com_sdk_HCNetSDK_NET_1DVR_1Cleanup
(JNIEnv *, jobject)
{
return true;
}
}
这里需要注意一下,要把我们的 Atest.h 用 extern "C"
修饰,变量和函数是按照 C 语言方式编译和链接的。
创建jni文件夹,里面新建 Android.mk 和 Application.mk 两个文件,内容为:
Android.mk
//本文件名
LOCAL_PATH := $(call my-dir)/..
LOCAL_LIB_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := $(LOCAL_LIB_PATH)/libAtest.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := NetDEVSDK_JNI
LOCAL_C_INCLUDES = -I $(LOCAL_PATH)/Atest.h com_sdk_HCNetSDK.h
#源文件
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/HCNetSDK.cpp)
LOCAL_LDLIBS := -fPIC -shared -lc -lz
LOCAL_ALLOW_UNDEFINED_SYMBOLS:=true #你还是乖乖加上这个吧
LOCAL_SHARED_LIBRARIES += test
include $(BUILD_SHARED_LIBRARY)
Application.mk
//本文件名
APP_ABI := armeabi armeabi-v7a x86 mips mips64 x86_64 arm64-v8a
APP_CPPFLAGS += -frtti
APP_PLATFORM := android-9
APP_STL := gnustl_static
在 jni 文件夹里运行 cmd,执行 ndk-build 命令。即可生成我们需要的 libNetDEVSDK_JNI.so 文件。
这里的报错是因为架构不支持,我们最少获得了 libs 文件夹中的 armeabi 和 armeabi-v7a 架构的so。
正常使用时需要我们生成的两个so文件,Android 调用 libNetDEVSDK_JNI.so 内函数,libNetDEVSDK_JNI.so 调用 libAtest.so 文件内函数。