项目背景
最近开始研究做移动端项目, 但是本人基本是做了五六年的 c++ 的底层研发, 对 C++ 的研发可以说是驾轻就熟了, 但是对于 android 还是属于刚入门阶段, 虽然断断续续做移动端也做了一年, 但是没有一个系统的去研究过 android, 怎么才能结合两者的优势呢. 我决定用 C++ 写底层.
ANDROID 是运行 JAVA 虚拟机层, 编译的是字节码, 效率不会太高, 特别是密集型 CPU 计算.
希望用更快速, 更简单的方法调用已有的技术, 而不用重新来一套.
防止核心代码被别人反编译破解.
所以我想到了使用 SWIG 封装技术.
使用 SWIG 介绍
SWIG 是个帮助使用 C 或者 C++ 编写的软件能与其它各种高级编程语言进行嵌入联接的开发工具. SWIG 能应用于各种不同类型的语言包括常用脚本编译语言例如 Perl, PHP, Python, Tcl, Ruby and PHP. 支持语言列表 http://www.swig.org/translations/chinese/compat.html#SupportedLanguages 中也包括非脚本编译语言, 例如 C#, Common Lisp (CLISP, Allegro CL, CFFI, UFFI), Java, Modula-3, OCAML 以及 R, 甚至是编译器或者汇编的计划应用 (Guile, MzScheme, Chicken).SWIG 普遍应用于创建高级语言解析或汇编程序环境, 用户接口, 作为一种用来测试 C/C++ 或进行原型设计的工具. SWIG 还能够导出 XML 或 Lisp s-expressions 格式的解析树. SWIG 可以被自由使用, 发布, 修改用于商业或非商业中.
SWIG 从 C++ 到 Android 开发的五个步骤
SWIG 是一个开源的 C++ 到其他语言包装技术, 你可以下载它的源码自己编译, 也可以直接下载编译好的二进制包, 如果没有啥特殊的需求, 使用编译好的 EXE 就可以了. 不用自己花心思去处理. 从网上下载 SWIG http://www.swig.org/index.php 并下载一份官方文档 http://www.swig.org/Doc3.0/index.html , 解压并将其目录加到 PATH 的环境目录中.
编写 C++ 代码
与平常写 C++ 代码没有区别, 但是为了方便, 一般情况下我们会写一个 C++ 的接口文件, 之后写一个实现类来集成那个接口
c++ 的接口文件 (ishapeprovider.h)
- // 这是一个读写 SHAPE 数据的接口类; 文件名小写测
- class IShapeProvider
- {
- public:
- // 打开一个文件或者一个数据库
- virtual void openFile(const std::string& file_name) = 0;
- // 添加一个记录到文件中, 其中 IFeature 是一个自定义接口类;
- virtual void addFeatrue(IFeature* feature) = 0;
- // 设置一个属性;
- virtual void setProperty(const std::string& key, const Variant& variant) = 0;
- // 打开一个图层, ITable 也是一个自定义接口类
- #ifndef SWIG // 这个宏是 SWIG 的特有的宏, 告诉 SWIG 在处理的时候不要将此函数暴露给 JAVA, 对 C++ 代码不受影响
- virtual ITable* table(const std::string& table_name) = 0;
- #endif
- // 获取一个图层的外包范围
- #ifdef SWIG // 对于传指针的处理方式
- %apply void &OUTPUT{ (int* x,int* y,int*with,int*height};
- #endif
- virtual void extent(int* x,int* y,int*with,int*height)=0;
- // 获取类的信息, 由于 toString 方法在 Java 中的方法冲突, 所以我们需要给该函数改一个名字
- #ifdef SWIG
- %rename (toJavaString) toString;//() 里面是修改之后的函数名字, toString 是在 C++ 的函数名字;
- #endif
- virtual std::string toString()=0;
- };
C++ 的实现文件 (shapeProvider.h)
- class ShapeProvider:public IShapeProvider
- {
- public:
- // 打开一个文件或者一个数据库
- virtual void openFile(const std::string& file_name)
- {
- // 实现省略
- }
- // 添加一个记录到文件中, 其中 IFeature 是一个自定义接口类;
- virtual void addFeatrue(IFeature* feature)
- {
- // 实现省略
- }
- // 设置一个属性;
- virtual void setProperty(const std::string& key, const Variant& variant)
- {
- // 实现省略
- }
- // 打开一个图层, ITable 也是一个自定义接口类
- virtual ITable* table(const std::string& table_name)
- {
- // 实现省略
- }
- // 其他实现....
- private:
- void initTableInformaction() {}
- private:
- QFile _file;//QT 类
- OGRDriver* _driver;//GDAL 类
- };
类实例化封装类 (classfactory.h)
- class ClassFactory
- {
- public:
- // 打开一个文件或者一个数据库
- virtual IShapeProvider* newInstance(const std::string& class_name)
- {
- // 实现在 CPP 文件中
- if (class_name == "IShapeProvider")
- {
- return new ShapeProvider();
- }
- };
- virtual void releaseClass(IShapeProvider* shape_provider)
- {
- delete shape_provider;
- }
- };
对于一个比较庞大的项目, 按上面的方式去实现, 有以下几个原因
为了隐藏实现类的实现结构, 暴露类的结构容易造成各种依赖问题;
swig 技术以最简单的方式包装出去, 特别是引用第三方库的时候, 如果直接用一个实现类的头文件, 那么就有可能需要依赖第三方库的头文件, 在包装的时候就要考虑这个第三方头文件的问题.
做框架接口与实现分离, 对内部实现的以及变量的改动不影响接口的修改, 不用重新编译所有模块
写 SWIG 的 i 文件
- %module(directors="1") dandelion_swig // 指定模块名 directors="1" 代表可以对 C++ 的类在 JAVA 中继承
- %{
- #include "ishapeprovider.h" // 在后面生成的 dandelion_swig.cxx 代码中包含的头文件
- #include "classfactory.h"
- }%
- %include "ishapeprovider.h" // 将该头文件所有的类, 宏定义, 以及全局变量包装到 JAVA 中, 生成对应的类
- %include "classfactory.h"
- // 添加系统的一些文件, 处理一些常用的基本类型, 具体可以参考 SWIG 的帮助文档
- %include stdint.i
- %include carrays.i
- %include windows.i
- %include typemaps.i
- #ifdef SWIGJAVA //swig 到 java 的 swig 预定义宏
- %include <enums.swg>
- %rename (toDString) toString;
- #endif
- // 对于这种结构的定义, 可以理解为改名, 不改名在 JAVA 中是识别不了的, 如智能指针
- %template(JavaList) std::list<long>;
- %template(IShapeProviderPtr) std::shared_ptr<IShapeProvider>;
i 文件书写简要说明
STL/C++ 库的转化 swig 提供了很多基本类型库的转化. 如 std::map 可以转成 java 的 map,std::string 转成 String
可以在 C++ 写接口在 JAVA 中实现, 如要对 IShapeProvider 接口用 java 实现, 需要调用
%feature("director") IShapeProvider ;
一般在 c++ 文件中, 可通过宏定义加入 swig 的代码, 这样方便管理, 当接口文件多了的时候要增加或者删除某些功能可一起删除.
使用 SWIG 将 C++ 包装成 JAVA 类
SWIG 生成的文件一个是 CXX 的 C 文件, 以及一些 JAVA 的类文件, 还有就是模块内的全局变量文件
swig 脚本编写
swig.exe -c++ -java -package com.simple.dandelion -outdir ./java_simple_wrapper/src/com/simple/dandelion -o ./java_simple_swig/src/java_dandelion_swig.cxx -Iswig_dandelion.i
说明:
-c++ 指定当前语言是 C++ 还是 C, 默认是 C, 只有这两种, 没有其他的
-java 生成的包装语言, 可以使其他任何一种支持的语言 如 - python -csharp
-package 生成的 java 包名
-outdir java 文件的输出目录
-o 输出的 CXX 文件的路径文件名
-I i 文件路径 (文件名必须紧跟参数)
swig 命令执行完成之后, 将在当前路径下面生成以下几个文件
dandelion_swig_wrap.cxx c++ 文件, 包装文件, 将 C++ 的类的方法转成 C 语言的函数
dandelion_swig.java 与 SWIG 定义中 module 名字同名的类
dandelion_swigJni.java c++ 类中的方法在此文件中转为 java 的静态方法, 就是 JAVA 中的 native 方法
其他的 JAVA 类为 C++ 中对应的类
编译 SO 库
使用 android stuido 新建一个包含 C++ 支持的工程, 使用 CMAKE 将 dandelion_swig_wrap.cxx 编译为 SO 文件. 以下是参考链接
CMake 入门实战 http://www.hahack.com/codes/cmake
在命令行下用 cmake 交叉编译可在 android 中运行的 so 包 https://blog.csdn.net/minghuang2017/article/details/78938852
Android NDK 开发扫盲及最新 CMake 的编译使用
将 JAVA 文件编译为 JAR 包
方法很简单, 在 android studio 中建立一个库项目, 将 SWIG 生成的所有的 JAVA 文件按照包名建立路径, 拷贝到所在包的路径下面, 直接用 android studio 编译就可以了.
android 调用 so 库使用
- //MainActivity 类
- public class MainActivity extends QtActivity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- JavaNatives.init(this);
- String text=JavaNatives.stringFromJNI();
- TextView tv = (TextView) findViewById(R.id.simple_text);
- tv.setText(text);
- }
- }
- import android.content.Context;
- import android.util.Log;
- import com.simple.dandelion.*;
- public class JavaNatives {
- public static void init(Context context) {
- System.loadLibrary("simple");// 本来模块调用的 simple 库
- System.loadLibrary("dandelion_swig");//swig 包装的 CXX 文件生成的 SO, 加载顺序必须先加载其他库.
- ClassFactory class_factory=new ClassFactory(); //c++ 中的类
- IShapeProvider shape_provider=class_factory.newInstance("ShapeProvider");
- shape_provider.openFile("/sdcard/simple.shp");
- }
- }
来源: http://www.jianshu.com/p/a91f4e3e20c3