终极篇 C++算法到安卓的移植——AS调用VS的so库

2023-11-11

目的:windows平台下的c++算法,需要移植到安卓系统上。平时用惯了Visual Studio,再在其他软件上重新写算法,调试算法,实在头疼。所以我用VS的c++移动开发功能创建动态共享库,将算法内容放入,并实现JNI和JAVA接口部分;最后用Android Studio调用成功。

 

吐槽微软的仿真器以及VS自带的google emulator for android,搞了很久,还是有问题,不能直接用(本着放在一起调试方便,竟然没搞出来。如果有朋友在这一块调试好了,记得发文章,还是很期待的),索性重点不在这里,干脆放弃,使用Android Studio做测试。(本来算法功能测试已经在windows平台测试的差不多了)

 

吐槽开始。。。

三周了,从未接触java,android,对于一个C++死忠粉各种没信心,只能各种查资料,找度娘,找论坛,都以为没戏了,终于给我搞成功了!!

在此特别感谢CSDN的Mr_L_Y,他对我的帮助无以言谢。这位大神贡献的资料可以查看:

VS2019 C++的跨平台开发——Android .so开发

https://blog.csdn.net/luoyu510183/article/details/94590497

VS2019 OpenCV的Windows工程到安卓的移植

https://blog.csdn.net/luoyu510183/article/details/102710080

 

其实有这两篇文章足以移植VS中创建的C++移动开发的SO库,但是想想这么久的辛苦,还是记录一下自己的成果。

(这里插一句,如果是整个大项目的移植,比如团队项目,直接参考Mr_L_Y的移植方法;如果是自己写的,源码结构比较简单的直接参考我这里的方法会更方便一点。)

 

本文使用的软件版本如下:

  • VisualStudio 2019 (创建c++移动开发的动态库)
  • AndroidStudio 3.5 (调用VS创建的动态库,并在模拟器中显示结果)
  • NDK-r16B
  • Android SDK 21
  • Opencv 4.1.1(android)

 

 

第一部分:创建c++算法的so库,供安卓调用

1. 软件准备:安装Visual Studio中的“使用C++的移动开发”,不需要在可选项中选择模拟器

2. 打开VS,新建项目,选择”动态共享库(Android)“,命名为SharedObject

3. 配置opencv

由于算法中使用了opencv,具体配置可以参考文章

Visual Studio + android + opencv 跨平台生成动态库文件https://blog.csdn.net/Merria28/article/details/102517646

在这里特别讲一下配置的问题,java不需要区分debug和release,所以在附加依赖项或者库依赖项中的所有配置是一样的。需要注意的是,opencv的第三方依赖库x86_64和x86中比arm64和arm的库文件少一个libtegra_hal.a,配置的时候不要添加就可以了。

附加库目录需要指定到配置文件夹:

OpenCV-android-sdk\sdk\native\3rdparty\libs\armeabi-v7a

参考MR_L_Y的文章,使用了$(PlatformShortName)代替了具体的每种配置,但是我的编译不过,就自己手动改成具体的配置内容了。Visual Studio中的ARM(对应安卓下的armeabi-v7a文件夹下的lib),ARM64(对应arm64-v8a),x86(对应x86),x64(对应x86_64)。

 

4. 添加自己的任意算法库头文件和源文件到项目中

我这里的头文件OpenCVFunc.h内容如下:

#pragma once
float TestOpencv(float* buf, int len);
float TestMath();

源文件内容如下:

#include "OpenCVFunc.h"

#include <math.h>
#include <opencv2/opencv.hpp>

float TestOpencv(float* buf, int len)
{
	cv::Mat mat = cv::Mat(len, 1, CV_32FC1, buf);
	auto sum = cv::sum(mat);
	return sum.val[0];
}

float TestMath()
{
	return sqrt(2.0f);
}

 

5. 导出上面头文件中的函数

在项目默认生成中的SharedObject19.cpp文件中添加,完整代码如下:

#include "SharedObject19.h"


#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "SharedObject19", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "SharedObject19", __VA_ARGS__))

extern float TestOpencv(float* buf, int len);
extern float TestMath();

extern "C" {
	
	float ExternTestOpencv(float* buf, int len) //这个用来导出给Android JNI使用
	{
		return TestOpencv(buf, len);
	}

	float ExternTestMath()//这个用来导出给Android JNI使用
	{
		return TestMath();
	}

	//C++导出给Java类使用的命名规范
	//Java_packagename_classname_functionname
	//第一个传参总是JNIEnv* env
	//第二个传参 如果是static成员函数就是jclass type,
	//		    如果是非static成员函数就是jobject thiz,
	//第三个传参才是真正的参数
	JNIEXPORT jfloat JNICALL
		Java_com_jniexample_JNIInterface_CVTestSum(JNIEnv* env, jclass type, jfloatArray buf) //这个用来导出给Java使用
	{
		auto len = env->GetArrayLength(buf);
		jboolean notcopy = JNI_FALSE;
		float* fptr = env->GetFloatArrayElements(buf, &notcopy);//从Java内存转换到native指针
		return TestOpencv(fptr, len);
	}
	JNIEXPORT jfloat JNICALL
		Java_com_jniexample_JNIInterface_TestSum(JNIEnv* env, jclass type, jfloatArray buf)//这个用来导出给Java使用
	{
		auto len = env->GetArrayLength(buf);
		jboolean notcopy = JNI_FALSE;
		float* fptr = env->GetFloatArrayElements(buf, &notcopy);
		float sum = 0;
		for (size_t i = 0; i < len; i++)
		{
			sum += fptr[i];
		}
		return sum;
	}
	JNIEXPORT jfloat JNICALL
		Java_com_jniexample_JNIInterface_TestMath(JNIEnv* env, jclass type)//这个用来导出给Java使用
	{
		return TestMath();
	}


	//此简单函数返回平台 ABI,此动态本地库为此平台 ABI 进行编译。
	const char * SharedObject19::getPlatformABI()
	{
	#if defined(__arm__)
	#if defined(__ARM_ARCH_7A__)
	#if defined(__ARM_NEON__)
		#define ABI "armeabi-v7a/NEON"
	#else
		#define ABI "armeabi-v7a"
	#endif
	#else
		#define ABI "armeabi"
	#endif
	#elif defined(__i386__)
		#define ABI "x86"
	#else
		#define ABI "unknown"
	#endif
		LOGI("This dynamic shared library is compiled with ABI: %s", ABI);
		return "This native library is compiled with ABI: %s" ABI ".";
	}

	void SharedObject19()
	{

	}

	SharedObject19::SharedObject19()
	{
	}

	SharedObject19::~SharedObject19()
	{
	}


}

需要提示的是,我们算法库只有两个算子TestOpencv和TestMath,但是在导出部分我却编写了4个算子用于外部导出,他们分别是:

float ExternTestOpencv(float* buf, int len)

float ExternTestMath()

JNIEXPORT jfloat JNICALL

Java_com_jniexample_JNIInterface_CVTestSum(JNIEnv* env, jclass type, jfloatArray buf)

JNIEXPORT jfloat JNICALL

Java_com_jniexample_JNIInterface_TestMath(JNIEnv* env, jclass type)

其中有个函数:

JNIEXPORT jfloat JNICALL

Java_com_jniexample_JNIInterface_TestSum(JNIEnv* env, jclass type, jfloatArray buf)

这个函数是用来检测Java_com_jniexample_JNIInterface_CVTestSum结果是否正常的,实现方式不一样而已。正常情况下是不需要的。

上面这四个函数需要特别说明一下,前两个是用来导出给Android JNI使用的,后两个是用来导出给Java使用的。具体使用的位置,在第二大部分会详细介绍。

 

6. 编译生成so文件

安卓在调用的时候最好提供全部配置的库文件。Visual Studio中的ARM(对应安卓下的armeabi-v7a文件夹下的lib),ARM64(对应arm64-v8a),x86(对应x86),x64(对应x86_64)。

这里我使用了x86进行测试,其他配置的库文件先不管。

还有一个小问题,Mr_L_Y大牛在他的文章“”中最后提示部分的第四点提到 “在使用Opencv4.1.1的安卓native sdk后,如果项目属性里选择的是 llvm-libc++ static,那么会出现编译错误,undefined reference to `strtof_l'. 具体原因我也不清楚,但是由于Opencv使用libc++_shared,所以使用static本身也不合理。” 我这里发现ARM64和x64下使用llvm-libc++ static编译通不过,都会提示undefined reference to `strtof_l'这个错误,我在具体应用的时候改成了libc++_shared,就会编译通过。

到这里,so文件生成就结束了。

 

第二部分,使用Android Studio调用VS2019生成的动态共享库

开始之前,先放一下需要修改的文件,内容不多,需要注意细节:

1. 打开android studio 创建新项目,选择Native C++,语言选择Java,其他随意。我这里创建的项目名称为NativeCplusplus

2. 将算法so库导入到安卓项目中——libSharedObject19.so放入当前项目

放入位置app/libs/x86/libSharedObject19.so 以及app/libs/x86/libopencv_java4.so

由于安卓模拟器默认用的是x86的,所以使用x86库文件;其他配置的库文件如果要放,每个配置文件夹下都必须有着两个so文件,否则会编译报错。可以选择放几个配置文件夹:armeabi-v7a, arm64-v8a, x86, x86_64app/build.gradle文件

3. 修改app/build.gradle文件

 

4. 创建java类,这里面用到的函数对应第一部分中SharedObject.cpp中的类似如下形式的函数:

JNIEXPORT jfloat JNICALL

Java_com_jniexample_JNIInterface_CVTestSum(JNIEnv* env, jclass type, jfloatArray buf)

添加的java类函数直接可以在MainActivity.java中调用并显示结果。

先将Android改为Project,在app/src/java文件夹上右击,NEW-JavaClass

然后实现JNIInterface.java的内容:

5. 修改CmakeLists.txt

这部分修改的内容主要针对的是导出的供JNI使用的函数,对应第一部分中SharedObject.cpp中的类似如下形式的函数:float ExternTestOpencv(float* buf, int len)。修改cmake文件后,这部分函数就可以在native-lib.cpp中使用了。然后才能在MainActivity.java中使用并显示结果。

6. 修改native-lib.cpp文件

可以使用so中导出的供JNI使用的函数,即第5步讲到的float ExternTestOpencv(float* buf, int len)这种函数。

 

7. 在显示结果之前,需要添加显示的方式和位置。

我们通过文本和按钮的方式在文件app\src\main\res\layout\activity_main.xml中实现。双击打开该文件,添加文本和按钮。

 

8. 在MainActivity.java中调用java函数,即调用native-lib.cpp和JNIInterface.java中的函数。这部分内容是通过上一步创建的UI界面显示的。

至此,代码和显示设计都完成了。下一步编译运行。

 

 

9. 编译apk:Build-->Build Bundle(s) / APK(s)-->Build APK(s)

10. 分析apk:Build-->Analyze APK...

 

11. 点击运行按钮,在模拟器上运行。(也可以选择在安卓设备上运行)

这里我没有设备,只能在模拟器上运行,第一次使用需要创建一个模拟器,点击菜单栏上的AVD Manager图标,如下图所示。选择左下角的Create Virtual Device。一切按默认或者推荐选择设置即可。需要注意的是,x86的模拟器比arm的模拟器快很多,尽量选x86的。(所以我想用vs_emulator.exe,据说该模拟器sudo更快。调试更方便。)

设置好虚拟设备之后,可以点击右侧的绿色按钮运行一下效果。体验之后你就懂了。。。

这时就可以点击菜单栏上的运行按钮,查看自己的运行效果了。

这是我的效果:

 

最后,我要引用Mr_L_Y的警示,因为不注意就会入坑:

1.AndroidStudio中的虚拟机默认是使用的x86的安卓系统,所以应该用x86编译下的.so文件。

 

2.apk安装后一运行就提示xxx已停止工作,就是安卓里面的崩溃,一般情况下是.so找不到,需要使用logcat自己排查问题。

 

3.apk点击那个按键后xxx已停止工作,崩溃在xxxxx函数没有实现,一般错误是那两个导出给安卓的函数名不正确,认真检查。

 

4.在使用Opencv4.1.1的安卓native sdk后,如果项目属性里选择的是 llvm-libc++ static,那么会出现编译错误,undefined reference to `strtof_l'. 具体原因我也不清楚,但是由于Opencv使用libc++_shared,所以使用static本身也不合理。

 

5.在进行大项目移植时,请先建立最小的opencv项目测试成功后再开始。

 

6.一定要会使用logcat

 

7.事已至此,请静下来学习一点Java和Android的开发知识,不要什么都直接去百度,最后拼凑出一个刚好能使用的项目。

 

安卓的官方文档:https://developer.android.com/studio/projects/add-native-code.html

https://developer.android.com/ndk/guides

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

终极篇 C++算法到安卓的移植——AS调用VS的so库 的相关文章

  • 关于android Sqlite在多进程情况下的安全性

    在我的应用程序中 存在多个进程 并且在每个进程中 我需要访问同一个SQLite数据库 当然 这意味着超过2个线程 所以我不仅担心SQLite的线程安全性 还担心SQLite的线程安全性 还有过程安全 这种情况的一种解决方案是使用内容提供者
  • 如何设置itemTouchHelper选择性滑动?

    所以我在 recyclerview rv 上实现了这个 itemtouchhelper simple 回调 现在 在这个 rv 中 我根据内容类型将 2 种布局设置为一行 因此 当我在 rv 上设置此 touchhelper 时 它正在这两
  • Android,无法从谷歌API获取天气

    以下代码之前运行良好 class RetreiveWeatherTask extends AsyncTask
  • 将文本视图包裹在图像视图周围

    晚上好 我正在尝试将文本包裹在图像视图中 就像这里提出的问题一样 Textview 环绕 View https stackoverflow com questions 3626750 textview wrap around view 在风
  • 尝试使用掩码裁剪位图会抛出 IllegalArgumentException:

    我正在使用以下代码 public void cropSelection Bitmap bitmap annotationBitmap copy annotationBitmap getConfig true Canvas canvas ne
  • 使用反向无限滚动添加到 ListView 时保持滚动位置

    我正在构建一个类似聊天的 Android 应用程序 类似于环聊 为此 我使用垂直 ListViewstackFromBottom true and transcriptMode normal 该列表按从较旧的消息 顶部 到较新的消息 底部
  • Droid 3 上的列表视图背景为灰色

    我有一个带有自定义背景的列表框 它在黑色背景的两侧显示一条细白线 在我所有的测试手机 Galaxy Captivate Vibrant Nexus 1 G Tablet Archos 32 Droid 上运行良好 我刚买了一台 Droid
  • 如何设置上拉刷新SwipeRefreshLayout

    我看到很多 ListView 下拉刷新的库 但是当从上到下拉动时它们是工作的 但是从下到上拉动时我如何刷新 我可以用这个做吗滑动刷新布局 http developer android com reference android suppor
  • 无法调整 Android React Native 模块中线性布局子项的大小

    完整代码在这里 https github com sbaar ResizableLLRN 这里有关于 java 中正确行为和 React Native 中错误行为的视频 https drive google com file d 0Bxl2
  • Android - 具有可序列化对象的 SharedPreferences

    我知道 SharedPreferences 有putString putFloat putLong putInt and putBoolean 但我需要存储一个类型的对象Serializable in SharedPreferences 我
  • Android 简单 TextView 动画

    我有一个 TextView 我想倒计时 3 2 1 发生了事情 为了使其更有趣 我希望每个数字都以完全不透明开始 然后淡出至透明 有没有一种简单的方法可以做到这一点 尝试这样的事情 private void countDown final
  • 当创建 Android Jetpack Compose AndroidView 的参数发生变化时,如何替换它?

    我有一个应用程序 显示封装在其中的几个不同视图AndroidView 在下面重现的简单示例中 这些只是TextView实例 问题是更改文本 在本例中循环显示三个不同的值 似乎不会更新应用程序显示的内容 sealed class AppVie
  • 日志记录在 Android 设备上实际上有什么作用?

    我一直在 Android 示例中看到这样的代码 try catch Exception e Log e Error e getMessage 什么是Log e实际上在物理设备上做什么 它进入系统日志 开发人员可以通过 SDK 工具访问该日志
  • Android EditText 不起作用,android:imeOptions="actionNext" android:inputType="phone"

    我已经尝试过了 只有删除android inputType phone 键盘回车可以跳转到下一个EditText 不知道大家之间是否有过一些冲突android imeOptions actionNext and android inputT
  • Android - 保持用户登录状态

    我正在尝试使用 PHP 和 MySQLi for Android 进行登录 我不明白的是如何保持用户登录状态 我看到一个简单的教程 其中有人使用 SQLite 来保护信息 但我不知道这是否真的安全 如何保存用户信息以保持用户登录状态 谢谢
  • Activity 上的 OnTouchListener 从不调用

    我使用了这段代码 但是当我在运行时单击活动时 它永远不会在 OnTouch 方法中命中 有人可以指导我我做错了什么吗 我需要设置此活动的内容视图吗 实际上我想要用户在执行过程中触摸的活动的坐标 public class TouchTestA
  • Android 可扩展 GridView 就像 Google 图像一样

    我想创建可扩展的 GridView 其行为类似于 Google 图像页面 我想知道如何实现这样的功能 当我单击其中一张图像时 页面会展开并显示图片的详细信息 Android 中是否可以做类似的事情 Images grid Expanded
  • 动画结束后更改视图位置

    我开发了一个基于ViewGroup我的问题是我需要在动画结束后保存项目的位置 我打了电话setFillAfter true 在我创建的动画对象中AnimationListener并在其中onAnimationEnd方法调用View layo
  • RecyclerView 适配器的 Kotlin 泛型

    我正在尝试编写一个通用的 recyclerview 适配器 我找到了几个例子 然而 仍然无法弄清楚如何实现通用适配器 我写的代码是 open abstract class BaseAdapter
  • 如何检测文本是否可读?

    我想知道是否有一种方法可以告诉给定的文本是人类可读的 我所说的人类可读的意思是 它有一些含义 格式就像某人写的文章 或者至少是由软件翻译器生成的供人类阅读的文章 这是背景故事 最近我正在制作一个应用程序 允许用户将短文本上传到数据库 在部署

随机推荐

  • QModbusServer

    目录 数据 1 setData 重载一 重载二 2 writeData 3 Data 重载一 重载二 4 readData 数据 1 setData 重载一 将数据写入Modbus服务器 Modbus服务器有四个表 A表 每个表有一个唯一的
  • 5G/NR 学习笔记: 波束赋形 / beam 管理

    What beam 是对于整列天线电磁波传播的一种样式 一个天线的时候 电磁波的辐射方向是360度传播的 但是一个天线阵列可以实现电磁波单方向传播 天线个数越多 电磁波传播方向越集中 https www rcrwireless com 20
  • 微服务:gateway的使用,和解决跨域问题,用户认证与网关整合

    1 网关介绍 API网关出现的原因是微服务架构的出现 不同的微服务一般会有不同的网络地址 而外部客户端可能需要调用多个服务的接口才能完成一个业务需求 如果让客户端直接与各个微服务通信 会有以下的问题 1 客户端会多次请求不同的微服务 增加了
  • JDK、JRE、JVM三者之间的关系

    jdk java development kit java开发工具包 jre java runtime environment java运行时环境 jvm java virtual machine java虚拟机 jdk环境配置 jdk环境
  • MAPREDUCE的JOB提交流程

    在hadoop1 0版本以前我们的Mapreduce是被当作资源调度和计算框架来使用的 成为了hadoop运行生态圈的瓶颈 所以在hadoop2 0版本以上引入了yarn的概念 使Mapreduce完全成为分布式计算框架 而Yarn成为了分
  • jquery ajax url中有中文,后端乱码的解决方法.txt

    参考 https zhidao baidu com question 2057437468670456147 html url类似 http localhost 8080 floorsNodes 风管 json 方法 在jquery aja
  • HTML标签的分类

    HTML的标签 一 分类 1 排版标签 2 标题标签 3 段落标签 4 水平线标签 5 换行标签 6 文本格式标签 7 标签属性 8 图像标签 二 应用 1 排版标签 与css搭配使用 显示网页结构的标签 是网页布局中的常用标签 2 标题标
  • 验证码拦不住机器人了!谷歌AI已能精准识别模糊文字,GPT-4则装瞎求人帮忙

    丰色 发自 凹非寺量子位 公众号 QbitAI 最烦登网站时各种奇奇怪怪 甚至变态 的验证码了 现在 有一个好消息和一个坏消息 好消息就是 AI可以帮你代劳这件事了 不信你瞧 以下是三张识别难度依次递增的真实案例 而这些是一个名为 Pix2
  • 华退学博士王垠:离开是为了获得力量后再回来

    清华退学博士王垠 离开是为了获得力量后再回来 附万言退学书 只提供一种模具生产 标准化的人 而忽视 教育的多样性 的中国教育界是可悲的 异端王垠用现身说法打破了教育界 沉默的螺旋 文 张洋 王垠出名了 这是迟早的事 但这一次 他的出名更多是
  • MySQL LOAD DATA INFILE - 加载没有主键的文件实战

    首先告诉大家关于 MySQL LOAD DATA INFILE 加载没有主键的文件 是可以操作的 接下来就来实战一把 表imagecode结构可以看到有3列 其中id是自增列 drop table imagecode create tabl
  • ztree异步加载

    1 配置好参数 var setting ztreeSetting view showIcon false 前面文件夹显示状态 data key name MC 数据结构的属性由name改成MC async enable true url u
  • 【NVM】node多个版本管理工具安装步骤以及使用

    1 什么是nvm nvm是一个管理nodejs版本的工具 在实际的开发中 有些项目的开发依赖需要不同版本的nodejs运行环境 此时我们就需要使用nvm来管理nodejs版本 2 安装步骤 2 1 下载nvm https github co
  • linux文件赋予用户权限,Linux 给用户赋予操作权限

    chown Rkeesail keesail local赋予local目录给keesailchmod760 local赋予local目录读写权这个目chmod777文件夹名称 可以把文件夹设置成所有用户都有完全的权限 不过更改系统文件夹下的
  • WSL2的安装详细过程(转载)

    这部分记录了如何安装wsl 以及如何升级到wsl2的心酸历程 文章目录 版本要求 升级windows WSL的安装 升级到WSL2 安装linux分发版 排查安装问题 相关链接 记录所踩的坑 版本要求 安装wsl2对系统版本有硬性的要求 运
  • mysql的行转列和列转行

    一 行转列 即将原本同一列下多行的不同内容作为多个字段 输出对应内容 建表语句 DROP TABLE IF EXISTS tb score CREATE TABLE tb score id INT 11 NOT NULL auto incr
  • 语义分割常用数据集整理

    语义分割的数据集分为三类 2D图片 2 5D图片 RGB D 3D图片 每一个类别的数据集都提供了像素级的标签 可以用来评估模型性能 同时其中一部分工作用到了数据增强来增加标签样本的数量 一 2D数据 1 PASCAL Visual Obj
  • 更换 CentOS 7 的下载源为阿里云

    1 备份 mv etc yum repos d CentOS Base repo etc yum repos d CentOS Base repo backup 2 下载新的CentOS Base repo 到 etc yum repos
  • vue3 hooks的简单使用 组合式函数

    想当于vue2的mixins 我们可以使用hooks代替mixins 官方文档 组合式函数 hooks 特点 vue3 中的 hooks 函数相当于 vue2 里面的 mixin 混入 不同在于 hooks 是函数 vue3 中的 hook
  • 离线脱机局域网环境安装visual studio2019企业版

    我这个文章借鉴了网友的一些方法 自己改编的用于记录以后方便自己用 亲测2022社区版可能因为我电脑的win10系统不完善的原因 无法 安装 最后试了2019企业版成功安装 1 在官网下载安装包 点击跳转 2 点开的网页 我英语不好 我翻译了
  • 终极篇 C++算法到安卓的移植——AS调用VS的so库

    目的 windows平台下的c 算法 需要移植到安卓系统上 平时用惯了Visual Studio 再在其他软件上重新写算法 调试算法 实在头疼 所以我用VS的c 移动开发功能创建动态共享库 将算法内容放入 并实现JNI和JAVA接口部分 最