使用指针将 Objective C 转换为 Kotlin

Converting Objective C with Pointers to Kotlin

从事 Kotlin 多平台项目。需要集成SQLCipher,Android部分我们已经做了,现在我在iOS这边做。

我已经构建了 SQLCipher 并使用 c_interop 进行了集成。

Swift 的 SQLCipher 文档是这样说的:

var rc: Int32
var db: OpaquePointer? = nil
var stmt: OpaquePointer? = nil
let password: String = "correct horse battery staple"
rc = sqlite3_open(":memory:", &db)
rc = sqlite3_key(db, password, Int32(password.utf8CString.count))

我一直在看这个,因为将 Swift 转换为 Kotlin 更容易,但是由于它使用 c_interop 作为 KMM,我猜它使用 SQLCipher 定义的普通 C 代码像这样:

sqlite3 *db;
sqlite3_stmt *stmt;
bool sqlcipher_valid = NO;

if (sqlite3_open([databasePath UTF8String], &db) == SQLITE_OK) {
const char* key = [@"BIGSecret" UTF8String];
sqlite3_key(db, key, (int)strlen(key));

我在 Kotlin 中是这样完成这部分的:

var rc: Int
val db: CValuesRef<CPointerVar<cnames.structs.sqlite3>>? = null
val db2: CValuesRef<cnames.structs.sqlite3>? = null

if(sqlite3_open(databasePath, db) == SQLITE_OK) {
   NSLog("Successfully opened connection to database")
}
rc = sqlite3_key(db2, null, 0)

但是我在 XCode 在 sqlite3_open(databasePath, db)

中不断崩溃

它说 线程 1:EXC_BAD_ACCESS(代码=1,地址=0x0)

我假设这些问题是因为我真的不熟悉指针,也不确定如何转换为 Kotlin。

从 SQLCiphers 示例中,他们将一个字符串传递给 sqlite3_key,但是当我尝试传递一个字符串时,它说 类型不匹配:推断类型是 String 但 CValuesRef<*>?

我为 sqlite3_open 声明的第一个数据库与 sqlite3_key 的类型不同,但是他们的示例显示他们使用相同的数据库,所以真的不确定如何进行这个.

完整的 SQLCipher 实现是 here

我想通了,希望对您有所帮助。 首先,我必须在 memscoped 中执行所有操作,因此我们可以访问 alloc() 方法。

然后我意识到一个问题是我的数据库路径不正确。 使用 XCode,您可以单击 Window -> 设备和模拟器,单击您的应用程序,然后单击齿轮并按下载容器。然后检查应用程序的容器以查看数据库的位置。对我来说,它在 Application Support.

里面

然后您可以像这样检查您的数据库是否存在:

val fileManager = NSFileManager.defaultManager()
val doesDBExist = fileManager.fileExistsAtPath(databasePath)

然后像这样定义一个指针:

val db: CPointerVar<sqlite3> = allocPointerTo()

sqlite3_open 如果没有找到,实际上会创建一个新的数据库,所以我使用 v2 方法来只是检查它是否存在。请注意,我们使用 .ptr 来引用传入的指针。

val openResult = sqlite3_open_v2(databasePath, db.ptr, SQLITE_OPEN_READWRITE, null)

然后如果你想检查你的密钥,你可以这样做(用你的密钥替换 null):

val keyResult = sqlite3_key(db.value, null, 0)

这应该会给您结果 1,这是一个错误。如 SQLCipher 文档所述,您需要执行进一步的步骤。

val result = sqlite3_exec(db.value, "SELECT count(*) FROM sqlite_master;", null, null, null)

这将尝试在您的数据库上执行命令,如果您的数据库未加密,那么 return 使用 null 应该没问题。如果您输入密码,它应该能够查询数据库。

然后我们需要关闭数据库

sqlite3_close(db.value)

所以当我想检查我的数据库是否加密时,对我来说完整的方法是这样的:

override fun isDatabaseEncrypted(databasePath: String): Boolean {
    memScoped {
        val fileManager = NSFileManager.defaultManager()
        val doesDBExist = fileManager.fileExistsAtPath(databasePath)
        NSLog("Result is $doesDBExist")

        val db: CPointerVar<sqlite3> = allocPointerTo()

        val openResult = sqlite3_open_v2(databasePath, db.ptr, SQLITE_OPEN_READWRITE, null)
        NSLog("openResult is $openResult")
        val keyResult = sqlite3_key(db.value, null, 0)
        NSLog("keyResult is $keyResult")
        val result = sqlite3_exec(db.value, "SELECT count(*) FROM sqlite_master;", null, null, null)
        sqlite3_close(db.value)

        NSLog("Result is $result")
        return result != SQLITE_OK
    }
}

我知道这比我原来的问题更详细,但希望它能为任何与我有类似问题的人更好地解释事情。