• c++加C#实现android开发


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


    前言

    因为一丢丢的小兴趣,前段时间尝试了一下移动设备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

    方案实现步骤:

    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++跨平台工具包。勾选这里,实际上要完成这套开发其他几个也要勾的。
    在这里插入图片描述

    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);
        };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    源文件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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    将类封装出导出接口。
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    选择Release,ARM64编译生成就得到了*.so组件,如果手机是32位的,就选ARM.

    二、实现C#动态库

    1.添加共享项目

    添加共享项目的目的是为了不同平台可以共享同一套代码,比如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);
    
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    用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;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    对外提供的类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;
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2.添加平台项目(Android)

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


    三、将动态库打包成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],通过拖拽完成界面,略丑。
    在这里插入图片描述

    2、引用自定义Nuget包MathSharpLib

    在这里插入图片描述

    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);
    
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

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

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

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

    右键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位。

  • 相关阅读:
    Linux下用C语言实现<<图书管理系统>>
    【计算机毕业设计】50.课程设计管理系统
    Linux 文件权限详细教程
    springcloud nacos和eureka
    Qt 线程串口
    KubeClipper——轻量便捷的 Kubernetes 多集群全生命周期管理工具
    Linux圈子里的“鲁大师“dmidecode-尚文网络xUP楠哥
    Redis的缓存雪崩、缓存穿透、缓存击穿
    文心一言 VS 讯飞星火 VS chatgpt (119)-- 算法导论10.3 4题
    Spring之BeanFactory
  • 原文地址:https://blog.csdn.net/lifei_0001/article/details/125925013