提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
因为一丢丢的小兴趣,前段时间尝试了一下移动设备APP的开发(主要是Android)。在Vs上使用了Xamarin模板,开发语言是C#。很早之前写过一个C++与C#的小工具,c++实现算法主要是Base64与Hash加密算法,winform实现界面。因为近期产品销售比较旺盛,工具使用的非常频繁,随时随地,这就带来了很大的麻烦(身为码农的我私人电脑已经坏了5、6年了,一直以来都是用的公司台式机),所以就萌生了把工具移植到手机上的想法。初期在怎么把C++动态库移植到手机上遇到了麻烦,然后就用C#重写了算法,至此工具移植完成。好巧不巧近期公司立项了一个APP的项目,unity平台加web,这再次激发了我解决掉(c++加C#提供动态库的跨平台方案)的想法。为什么非得搞C++?因为我是个C/C++程序员。因为在Android上验证的方案,所以在这里只讲安卓了。
方案实现步骤:
编译运行在arm平台的C++的动态库。
实现C#动态库,以作为C++动态库的壳。
将动态库打包成nupkg,以供项目通过NuGet引用。
在项目中使用nupkg,并发布APP。
思路来自于微软官网文档,有坑,说是提供的步骤特定于 Visual Studio for Mac,但该结构也适用于 Visual Studio 2017。但我vs2017安装不了Android SDK,用了vs2019,但好多步骤,操作都不对。官方链接
代码仓库链接
百度网盘:
链接: https://pan.baidu.com/s/1lI4wsOrdgLvf-exE-ki_MA
提取码: 99hm
首先搭建编译环境,在Visual studio Install里安装C++跨平台工具包。勾选这里,实际上要完成这套开发其他几个也要勾的。
创建库项目并编译。在【文件】【新建】【项目】中选【C++】【所有平台】【库】
添加代码文件。
头文件MyMathFuncs.h:
//MyMathFuncs.h
namespace MathFuncs
{
class MyMathFuncs
{
public:
double Add(double a, double b);
double Subtract(double a, double b);
double Multiply(double a, double b);
double Divide(double a, double b);
};
}
源文件MyMathFuncs.cpp:
#include "MyMathFuncs.h"
namespace MathFuncs
{
double MyMathFuncs::Add(double a, double b)
{
return a + b;
}
double MyMathFuncs::Subtract(double a, double b)
{
return a - b;
}
double MyMathFuncs::Multiply(double a, double b)
{
return a * b;
}
double MyMathFuncs::Divide(double a, double b)
{
return a / b;
}
}
将类封装出导出接口。
MyMathFuncsWrapper.h
#include "MyMathFuncs.h"
using namespace MathFuncs;
extern "C" {
MyMathFuncs* CreateMyMathFuncsClass();
void DisposeMyMathFuncsClass(MyMathFuncs* ptr);
double MyMathFuncsAdd(MyMathFuncs *ptr, double a, double b);
double MyMathFuncsSubtract(MyMathFuncs *ptr, double a, double b);
double MyMathFuncsMultiply(MyMathFuncs *ptr, double a, double b);
double MyMathFuncsDivide(MyMathFuncs *ptr, double a, double b);
}
MyMathFuncsWrapper.cpp:
#include "MyMathFuncsWrapper.h"
MyMathFuncs* CreateMyMathFuncsClass()
{
return new MyMathFuncs();
}
void DisposeMyMathFuncsClass(MyMathFuncs* ptr)
{
if (ptr != nullptr)
{
delete ptr;
ptr = nullptr;
}
}
double MyMathFuncsAdd(MyMathFuncs *ptr, double a, double b)
{
return ptr->Add(a, b);
}
double MyMathFuncsSubtract(MyMathFuncs *ptr, double a, double b)
{
return ptr->Subtract(a, b);
}
double MyMathFuncsMultiply(MyMathFuncs *ptr, double a, double b)
{
return ptr->Multiply(a, b);
}
double MyMathFuncsDivide(MyMathFuncs *ptr, double a, double b)
{
return ptr->Divide(a, b);
}
选择Release,ARM64编译生成就得到了*.so组件,如果手机是32位的,就选ARM.
添加共享项目的目的是为了不同平台可以共享同一套代码,比如Android,IOS,win加载C++接口用的都是同一套代码。
在解决方案中,【添加】【新建项目】,设置【C#】【所有平台】【所有类型】,选择【共享项目】模板。项目名称SharedPro。
加载C++库,封装C#类。使用 MyMathFuncsSafeHandle 类替代标准 IntPtr。 在封送过程中,IntPtr 会自动映射到 SafeHandle。MyMathFuncsWrapper.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace MathFuncs
{
internal static class MyMathFuncsWrapper
{
//#if Android
const string DllName = "libMathFuncs.so";
//#else
// const string DllName = "__Internal";
//#endif
[DllImport(DllName, EntryPoint = "CreateMyMathFuncsClass")]
internal static extern MyMathFuncsSafeHandle CreateMyMathFuncs();
[DllImport(DllName, EntryPoint = "DisposeMyMathFuncsClass")]
internal static extern void DisposeMyMathFuncs(MyMathFuncsSafeHandle ptr);
[DllImport(DllName, EntryPoint = "MyMathFuncsAdd")]
internal static extern double Add(MyMathFuncsSafeHandle ptr, double a, double b);
[DllImport(DllName, EntryPoint = "MyMathFuncsSubtract")]
internal static extern double Subtract(MyMathFuncsSafeHandle ptr, double a, double b);
[DllImport(DllName, EntryPoint = "MyMathFuncsMultiply")]
internal static extern double Multiply(MyMathFuncsSafeHandle ptr, double a, double b);
[DllImport(DllName, EntryPoint = "MyMathFuncsDivide")]
internal static extern double Divide(MyMathFuncsSafeHandle ptr, double a, double b);
}
}
用SafeHandle处理非托管内存。
MyMathFuncsSafeHandle .cs
using System;
using Microsoft.Win32.SafeHandles;
namespace MathFuncs
{
internal class MyMathFuncsSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public MyMathFuncsSafeHandle() : base(true) { }
public IntPtr Ptr => handle;
protected override bool ReleaseHandle()
{
// TODO: Release the handle here
MyMathFuncsWrapper.DisposeMyMathFuncs(this);
return true;
}
}
}
对外提供的类MyMathFuncs.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace MathFuncs
{
public class MyMathFuncs
{
readonly MyMathFuncsSafeHandle handle;
//MyMathFuncsSafeHandle handle;
const string DllName = "libMathFuncs.so";
public MyMathFuncs()
{
handle = MyMathFuncsWrapper.CreateMyMathFuncs();
}
public virtual void Dispose(bool disposing)
{
if (handle != null && !handle.IsInvalid)
handle.Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public string Test() {
string strRt = "";
string strPath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
if (File.Exists(DllName))
{
strRt = string.Format("{0} is Exist\r\n{1}", DllName, strPath);
}
else
{
strRt = string.Format("{0} is not Exist\r\n{1}", DllName, strPath);
}
return strRt;
}
public double Add(double a, double b)
{
return MyMathFuncsWrapper.Add(handle, a, b);
//return a + b;
}
public double Subtract(double a, double b)
{
return MyMathFuncsWrapper.Subtract(handle, a, b);
//return a - b;
}
public double Multiply(double a, double b)
{
return MyMathFuncsWrapper.Multiply(handle, a, b);
//return a * b;
}
public double Divide(double a, double b)
{
return MyMathFuncsWrapper.Divide(handle, a, b);
//return a / b;
}
}
}
添加一个平台项目,目的是编写特定于平台代码,最终打包到Nupkg中,不同的平台引用不同的部分。这里只加个安卓。
在解决方案中【添加】【新建项目】,选择【C#】【所有平台】【库】,选择【类库】,创建时目标框架选择.Net Standard 2.0。项目名称MathSharpLib。然后为MathSharpLib添加项目引用,引用SharedPro。
打nupkg包有两种方法,一种使用nuget手动打包。一种是使用vs打包,这种比较简单。
右键MathSharpLib,属性,打包,勾选在构建时生成Nuget包,在此界面还可以设置包版本等其他信息。
【工具】【Nuget包管理器】【程序包管理器设置】打开设置UI界面。在【Nuget包管理器】下的【程序包源】点添加,设置名字,已经源目录即可。包打出的包放在开目录下便能被项目看到。
然后就可以写个App项目来用Nuget包了。我这里用的是安卓应用(Xamarin),模板用的空白项,项目名App。
展开App项目下的【Resources】【layout】,双击[activity_main.xml],通过拖拽完成界面,略丑。
在MainActive.cs中完成代码。此时能够编译成功。
using Android.App;
using Android.OS;
using Android.Runtime;
using AndroidX.AppCompat.App;
using Android.Widget;
using SharePro;
using MathFuncs;
namespace App1
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
//public SharePro.Android.MathFunc _mathFunc;
public MathFuncs.MyMathFuncs _MyMath;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
//测试nuget包
EditText editA = FindViewById<EditText>(Resource.Id.editText1);
EditText editB = FindViewById<EditText>(Resource.Id.editText2);
EditText editRt = FindViewById<EditText>(Resource.Id.editText3);
Button b1 = FindViewById<Button>(Resource.Id.button1);
Button b2 = FindViewById<Button>(Resource.Id.button2);
Button b3 = FindViewById<Button>(Resource.Id.button3);
Button b4 = FindViewById<Button>(Resource.Id.button4);
//_mathFunc = new SharePro.Android.MathFunc();
_MyMath = new MyMathFuncs();
b1.Click += (sender, e) =>
{
double a = double.Parse(editA.Text);
double b = double.Parse(editB.Text);
//double c = _mathFunc.Add(a,b);
double c = _MyMath.Add(a,b);
editRt.Text = string.Format("{0} + {1} = {2}", a, b, c);
};
b2.Click += (sender, e) =>
{
double a = double.Parse(editA.Text);
double b = double.Parse(editB.Text);
//double c = _mathFunc.Multiply(a, b);
double c1 = _MyMath.Multiply(a, b);
editRt.Text = string.Format("_MyMath sharedc:{0} x {1} = {2}", a, b, c1);
};
b3.Click += (sender, e) =>
{
double a = double.Parse(editA.Text);
double b = double.Parse(editB.Text);
//double c = _mathFunc.Subtract(a, b);
double c = _MyMath.Subtract(a, b);
editRt.Text = string.Format("{0} - {1} = {2}", a, b, c);
};
b4.Click += (sender, e) =>
{
double a = double.Parse(editA.Text);
double b = double.Parse(editB.Text);
double c = _MyMath.Divide(a, b);
editRt.Text = string.Format("{0} / {1} = {2}", a, b, c);
//editRt.Text = _MyMath.Test();
};
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
按照微软的教学文档在编译MathSharpLib库时,c++的动态库应该在文件的生成操作,以“EmbeddedNativeLibrary”的形式嵌入进MathSharpLib库中。然而并不能找到这个选项。所以Nuget包里没有,再次添加到App里,生成apk时打包进去。
这时的目录结构很重要。名字不能错,因为编译器是根据目录名称来判断ABI的类型的。arm64-v8a放的是我们的C++动态库MathClib在ARM64下编译的,armeabi-v7a是ARM下编译的。添加好后再次编译。
右键App项目,选择【存档】【分发】【临时】,在UI创建一个新的密钥存储。然后另存为XXX.apk。
至此App就完成了。拷贝到手机上就能安装使用了。
App.apk解压之后我们能看到我们的C++的动态库libMathCLib.so。
和C#外壳动态库。
最终手机运行效果。
C++与C#实现跨平台方案,首先编译出Arm平台库、加C#外壳、生成nuget包以便灵活使用。需要注意的是C++的动态库一定要打到apk里。可以嵌入到C#的包里(我没实现),也可以加载到Apk里。一定要注意组件所在目录的名称,这里用以区分组件的ABI类型,也就是组件的平台架构,是arm还是x86,是64位还是32位。