为 Unity3D for Android 编译 SQLite 3

文章目录
  1. 1. 一、最终的解决方案
  2. 2. 二、交叉编译初试
  3. 3. 三、结合 NDK 的编译
  4. 4. 四、Unity3D 中使用 SQLite

作为 CGSS 反向的成果聚合部分的一个环节,Unity3D 的 SQLite 插件是必经之路。在 Asset Store 里,不过15刀的价格对于其功能而言还是有点高。研究了一晚上,最后结合 NDK 将 SQLite 3 编译到了 Android 环境上,整合到 Unity3D 中是第二天的事了。

一、最终的解决方案

最终的方案其实很简单,在 GitHub 上已经有人写了自动为编译 SQLite for Android 的脚本。我做了一个 fork,增加了一些修正。

直接 git clone 之后,切换到该目录,make 就可以了。

编译完成之后,取出两个 libsqlite3.so,放到 Unity3D 工程的 Assets/Plugins/Android/libs 下的根据 CPU 架构命名的目录中。可以放到整个工程中的 Plugins,也可以放到“插件”(主工程也可以视为一个插件,我就是这么做的)的里面。目录结构大概是这样的:

D:\SOURCE\UNITY\DERETORE\ASSETS
└─DereTore
├─Plugins
│ ├─Android
│ │ └─libs
│ │ ├─armeabi-v7a # libsqlite3.so
│ │ └─x86 # libsqlite3.so
│ └─Windows
│ └─libs
│ └─x86 # sqlite3.dll
└─Resources

在 Unity3D Editor 中,选择这些动态库,在 Inspector 中为它们选择对应的平台和 CPU 架构。完成后编译工程,Android 的应该位于 ${APK}/lib/${ARCH} 路径下。

APK 中的动态库位置

二、交叉编译初试

我以前的应用场景多是 Linux 服务器,系统是 CentOS。这次一上手,就用的是 64 位 CentOS 的机器。在 SQLite 的下载页面,选择一个 autoconf 版的压缩包下载(毕竟名字都表明了嘛)。然后是一贯的搭建编译环境。

$ yum install gcc

接下来我是沿着前人的足迹前进的,“顺利编译了”X86 版本。(我们的目标是 Android,这里虽然编译链接通过,但是实际运行是跑不起来的,原因后面会说。)

接下来就要转到 ARM 移植上了。我一看需要一个 arm-linux-gcc,那肯定是需要安装的啦,赶快:

$ yum search arm-linux

显示符合条件的只有一个 gcc-arm-linux-gnu.x86_64 包,那就安装吧。

$ yum install gcc-arm-linux-gnu

好了,根据前人足迹,换上这一套配置:

$ cd ../arm
$ ../../configure CC=arm-linux-gnu-gcc --host=arm-linux /root/sqlite/bin/arm/target
$ make
$ make install

然而却说找不到 arm-linux-gcc。网上还有很多教程都用的是(不知来源的)arm-linux-gcc,除了上文的,还有这个。难道一定要一个 arm-linux-gcc有人甚至自己重新编译了一份,但是大多数教程用的都是由 ARM9 提供的一个很老的版本 ARM-Linux GCC 4.4.3。(现在 GCC 4.x 都到了 4.8.1 了!)

然后我尝试将 host 参数值换成 arm-linux-gnu,这样:

$ ../../configure CC=arm-linux-gnu-gcc --host=arm-linux-gnu /root/sqlite/bin/arm/target

结果 configure 失败了,提示编译器编译出的东西不可运行,因此编译器不可用。

我认为是 64 位 CentOS 的软件包丰富度问题,换上了阿里源、网易源(我用的是 Conoha 的 VPS,默认使用日本某 FTP 源),都没有其他的包。然后我换了一个 32 位的 CentOS,甚至还没有这交叉编译包。

三、结合 NDK 的编译

想到 CGSS 中见到的架构是 ARM EABI v7,是不是该搜索这个版本的 GCC?于是找到了有一个叫 arm-eabi-gcc 的东西,继续搜索,见到了科普。然后又在哪里看到的(我忘了),有人在 Ubuntu 上编译 SQLite for Android,既然能编译那么我也该试试,于是切到 Ubuntu 上。版本呢?我想着尽量减少交叉编译次数,就选择了 32 位的版本(14.04 x86)。

不过,看起来不管怎么样我都得需要一个 NDK。配置了 NDK 之后,继续尝试编译。这次我学乖了,首先看看 X86 的行不行:

$ ../../configure CC=gcc CFLAGS="-nostdlib -I$NDKROOT/platforms/android-19/arch-x86/usr/include/" LDFLAGS="-Wl,-rpath-link=$NDKROOT/platforms/android-19/arch-x86/usr/lib/ -L$NDKROOT/platforms/android-19/arch-x86/usr/lib/" LIBS="-lc" --prefix=/root/sqlite/bin/x86/target

看起来配置正常,make。然后在编译阶段就报错说 crti.ocrtn.o 找不到。这又是什么东西?从 StackOverflow 上我了解到,这是一个启动库(在 Windows 下,cl 会自动加 stub,Linux 下的 stub 就由这两个库构成)。至于 Android,它使用的不是标准 stub,而是 Android 自己的。上面的 -rpath-link 选项的解释在这里

途中我还学习到:

  • Linux 下的环境变量设置
    • 用户环境变量直接使用 X=Y 就可以了,比如 X=Y gcc a.c -o a.out,在 gcc 看来,环境变量 X 的值就是 Y
    • 系统环境变量类似,因为本身是 bash 脚本,所以直接将类似的设置过程写入 /etc/profile,重启。
  • apt-getyum 的使用差异
    • yum 有一个很好用的 yum search,对应的 apt-get 命令是 apt-cache search……

接下来是几组我试过的失败的参数,各位有兴趣自己试试(错误提示忘了,还原一遍就知道了吧……):

$ ../../configure -host=i686-linux-android CC=$NDKROOT/toolchains/x86-4.8/prebuilt/linux-x86/bin/i686-linux-android-gcc --prefix=/root/sqlite/bin/x86/target
$ ../../configure -host=i686-linux-android CC=$NDKROOT/toolchains/x86-4.8/prebuilt/linux-x86/bin/i686-linux-android-gcc CPPFLAGS="-I$NDKROOT/platforms/android-19/arch-x86/usr/include/" CFLAGS="-nostdlib" LDFLAGS="-Wl -L$NDKROOT/platforms/android-19/arch-x86/usr/lib/" LIBS="-lc -landroid -ldl -lm -llog -lz" --prefix=/root/sqlite/bin/x86/target
$ ../../configure -host=arm-linux-androideabi CC=arm-linux-androideabi-gcc CFLAGS="-nostdlib -I$NDKROOT/platforms/android-19/arch-arm/usr/include/" LDFLAGS="-Wl,-rpath-link=$NDKROOT/platforms/android-19/arch-arm/usr/lib/ -L$NDKROOT/platforms/android-19/arch-arm/usr/lib/" LIBS="-lc -landroid -ldl -lm -llog -lz" --prefix=/root/sqlite/bin/arm/target
$ ../../configure -host=arm-linux-androideabi CC=arm-linux-androideabi-gcc CPPFLAGS="-I$NDKROOT/platforms/android-19/arch-arm/usr/include/" CFLAGS="-nostdlib" LDFLAGS="-Wl,-rpath-link=$NDKROOT/platforms/android-19/arch-arm/usr/lib/ -L$NDKROOT/platforms/android-19/arch-arm/usr/lib/" LIBS="-lc -landroid -ldl -lm -llog -lz" --prefix=/root/sqlite/bin/arm/target

我也试过 Roman10 的构建脚本,使用 ndk-build 编译,不过也没成功。

最后是用第一段所说的自动构建的方式编译链接完成的,无意中找到的 repo。ndk-build 的几个常用参数说明见这里

要注意,SQLite 的源代码有两种发行版本,amalgamation 版是整合版(所有代码位于一个 .c 文件中),autoconf 在此基础上加入了一些 TCL 的配置脚本。后者更适合对于 PC 上的 Linux 的编译,不过这些自动配置对为 Android 编译来说反而是累赘。

四、Unity3D 中使用 SQLite

CGSS 有自己对 SQLite 的封装,编译失败的时候我差点就要用这些封装+它带的 libsqlite3android.so 了。

编译后我照着 Momo 的教程将文件放到了合适的位置、设置 Inspector 属性(这一步他没提,自己填坑)。终于,数据库的连接测试成功了。

但是在执行查询的时候没任何反应。使用一下 adb logcat -d -s Unity,发现这样的信息:

04-19 21:21:27.817  8205  8221 I Unity   : EntryPointNotFoundException: sqlite3_column_origin_name
04-19 21:21:27.817 8205 8221 I Unity : at (wrapper managed-to-native) Mono.Data.Sqlite.UnsafeNativeMethods:sqlite3_column_origin_name (intptr,int)
04-19 21:21:27.817 8205 8221 I Unity : at Mono.Data.Sqlite.SQLite3.ColumnOriginalName (Mono.Data.Sqlite.SqliteStatement stmt, Int32 index) [0x00000] in <filename unknown>:0
04-19 21:21:27.817 8205 8221 I Unity : at Mono.Data.Sqlite.SqliteDataReader.GetSchemaTable (Boolean wantUniqueInfo, Boolean wantDefaultValue) [0x00000] in <filename unknown>:0
04-19 21:21:27.817 8205 8221 I Unity : at Mono.Data.Sqlite.SqliteDataReader.GetSchemaTable () [0x00000] in <filename unknown>:0

可以搜索到大概的原因,在 SQLite 的编译指导文档中可以确认。然后使用宏定义加入这个定义——注意,不是设置环境变量,而是加入到 CFLAGS(SQLite 仅使用了 C 编译器)这个特殊“环境变量”(内置宏)中,所以我最后是将这个定义加入了 LOCAL_CFLAGS 中。加入之后就可以正常查询了。

最后,Momo 还展示了一种很有趣的同时适用于 JNI 和 P/Invoke 的代码编写技术

分享到 评论