作为 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}
路径下。
二、交叉编译初试
我以前的应用场景多是 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.o
和 crtn.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-get
和yum
的使用差异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 的代码编写技术。