c++加C#实现android开发

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、编译运行在arm平台的C++的动态库
    • 1.搭建C++移动开发环境
    • 2.创建项目并生成C++链接库
  • 二、实现C#动态库
    • 1.添加共享项目
    • 2.添加平台项目(Android)
  • 三、将动态库打包成nupkg
    • 1、Nuget打包
    • 2、Nuget配置本地包
  • 四、在APP项目中使用nupkg
    • 1.界面布局
    • 2、引用自定义Nuget包MathSharpLib
    • 3.为按钮添加事件响应
    • 4.添加C++动态库.so
    • 5.生成APP的安装包apk文件。
  • 总结


前言

因为一丢丢的小兴趣,前段时间尝试了一下移动设备APP的开发(主要是Android)。在Vs上使用了Xamarin模板,开发语言是C#。很早之前写过一个C++与C#的小工具,c++实现算法主要是Base64与Hash加密算法,winform实现界面。因为近期产品销售比较旺盛,工具使用的非常频繁,随时随地,这就带来了很大的麻烦(身为码农的我私人电脑已经坏了5、6年了,一直以来都是用的公司台式机),所以就萌生了把工具移植到手机上的想法。初期在怎么把C++动态库移植到手机上遇到了麻烦,然后就用C#重写了算法,至此工具移植完成。好巧不巧近期公司立项了一个APP的项目,unity平台加web,这再次激发了我解决掉(c++加C#提供动态库的跨平台方案)的想法。为什么非得搞C++?因为我是个C/C++程序员。因为在Android上验证的方案,所以在这里只讲安卓了。

方案实现步骤:

  1. 编译运行在arm平台的C++的动态库。

  2. 实现C#动态库,以作为C++动态库的壳。

  3. 将动态库打包成nupkg,以供项目通过NuGet引用。

  4. 在项目中使用nupkg,并发布APP。

    思路来自于微软官网文档,有坑,说是提供的步骤特定于 Visual Studio for Mac,但该结构也适用于 Visual Studio 2017。但我vs2017安装不了Android SDK,用了vs2019,但好多步骤,操作都不对。官方链接

代码仓库链接
百度网盘:
链接: https://pan.baidu.com/s/1lI4wsOrdgLvf-exE-ki_MA
提取码: 99hm


一、编译运行在arm平台的C++的动态库

1.搭建C++移动开发环境

首先搭建编译环境,在Visual studio Install里安装C++跨平台工具包。勾选这里,实际上要完成这套开发其他几个也要勾的。
c++加C#实现android开发_第1张图片

2.创建项目并生成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.

二、实现C#动态库

1.添加共享项目

添加共享项目的目的是为了不同平台可以共享同一套代码,比如Android,IOS,win加载C++接口用的都是同一套代码。
在解决方案中,【添加】【新建项目】,设置【C#】【所有平台】【所有类型】,选择【共享项目】模板。项目名称SharedPro。
c++加C#实现android开发_第2张图片

加载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;
        }
    }
}

2.添加平台项目(Android)

添加一个平台项目,目的是编写特定于平台代码,最终打包到Nupkg中,不同的平台引用不同的部分。这里只加个安卓。
在解决方案中【添加】【新建项目】,选择【C#】【所有平台】【库】,选择【类库】,创建时目标框架选择.Net Standard 2.0。项目名称MathSharpLib。然后为MathSharpLib添加项目引用,引用SharedPro。
c++加C#实现android开发_第3张图片


三、将动态库打包成nupkg

1、Nuget打包

打nupkg包有两种方法,一种使用nuget手动打包。一种是使用vs打包,这种比较简单。
右键MathSharpLib,属性,打包,勾选在构建时生成Nuget包,在此界面还可以设置包版本等其他信息。

2、Nuget配置本地包

【工具】【Nuget包管理器】【程序包管理器设置】打开设置UI界面。在【Nuget包管理器】下的【程序包源】点添加,设置名字,已经源目录即可。包打出的包放在开目录下便能被项目看到。

四、在APP项目中使用nupkg

然后就可以写个App项目来用Nuget包了。我这里用的是安卓应用(Xamarin),模板用的空白项,项目名App。

1.界面布局

展开App项目下的【Resources】【layout】,双击[activity_main.xml],通过拖拽完成界面,略丑。
c++加C#实现android开发_第4张图片

2、引用自定义Nuget包MathSharpLib

c++加C#实现android开发_第5张图片

3.为按钮添加事件响应

在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);

        }
    }
}

4.添加C++动态库.so

按照微软的教学文档在编译MathSharpLib库时,c++的动态库应该在文件的生成操作,以“EmbeddedNativeLibrary”的形式嵌入进MathSharpLib库中。然而并不能找到这个选项。所以Nuget包里没有,再次添加到App里,生成apk时打包进去。
这时的目录结构很重要。名字不能错,因为编译器是根据目录名称来判断ABI的类型的。arm64-v8a放的是我们的C++动态库MathClib在ARM64下编译的,armeabi-v7a是ARM下编译的。添加好后再次编译。
c++加C#实现android开发_第6张图片

5.生成APP的安装包apk文件。

右键App项目,选择【存档】【分发】【临时】,在UI创建一个新的密钥存储。然后另存为XXX.apk。
至此App就完成了。拷贝到手机上就能安装使用了。
App.apk解压之后我们能看到我们的C++的动态库libMathCLib.so。
c++加C#实现android开发_第7张图片

和C#外壳动态库。
c++加C#实现android开发_第8张图片

最终手机运行效果。
c++加C#实现android开发_第9张图片

总结

C++与C#实现跨平台方案,首先编译出Arm平台库、加C#外壳、生成nuget包以便灵活使用。需要注意的是C++的动态库一定要打到apk里。可以嵌入到C#的包里(我没实现),也可以加载到Apk里。一定要注意组件所在目录的名称,这里用以区分组件的ABI类型,也就是组件的平台架构,是arm还是x86,是64位还是32位。

你可能感兴趣的:(跨平台,c++,c#,android)