He Xiaojie Dev (Senior Android architect) Copyright belongs to the author, please contact the author for authorization.




SQLite is a cross-platform database, so what’s the point?


No, I’ve been working on a project that requires SQLite databases, and I don’t even want to spend multiple sets of code on different platforms, since each platform includes different SDKS. For a simple example, to use SQLite on Android, you need to use the SQLiteDatabase class to operate in Java. On iOS, libsqlite3. TBD and sqlite3.h are required to operate using Objective-C. On PC, sqlite3.h is used as the main function, but there are still some inconsistency. For example, there are a wide variety of programming languages, most of which have different packages and inconsistent apis that can cause headaches.


Therefore, different code is bound to be used to operate SQLite on different platforms. Of course, with the exception of SQLite, it may be common practice to use different code on different platforms for the same functionality.

Please enter the title bcdef

Roll your eggs for normal! As a lazy person, when such a pot needs to be carried on one’s own, it is natural to look for simpler solutions. The goal is a set of code to go around the world!

Please enter the title abcdefg


There are a lot of techniques to choose from, such as C++. Sqlite3. H is quite useful. But I stuck with my favorite CodeTyphon because it had a more convenient encapsulation.


Luckily, CodeTyphon already comes with the SQlite3CONN unit, which can be referenced directly. For information on how to find referable libraries, look at the typhon-ide Pkgs and FPC Pkgs pages of CTC and you will find what you are looking for.



CTC


Let’s start with a simple database to test if the code works:


$ sqlite3 demo.db

> create table user(id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(32) NOT NULL);

> insert into user(name) value ('ABC');

> insert into user(name) value ('XYZ');Copy the code


Then declare a structure based on the database structure, which will later be used for data passing:


type

 TDemoRec = record

   AId: Integer;

   AName: PChar;

 end;Copy the code


The C++ equivalent of this construct looks like this:


struct DemoRec {

   int AId;

   char* AName;

};Copy the code


Now that we can see how easy it is to use SQLite, I’ve defined a class that holds some data:


TSQLite = class

 private

   FDatabase: TSQLite3Connection;

   FQuery: TSQLQuery;

   FTransaction: TSQLTransaction;

 published

   property Database: TSQLite3Connection read FDatabase write FDatabase;

   property Transaction: TSQLTransaction read FTransaction write FTransaction;

   property Query: TSQLQuery read FQuery write FQuery;

 end;Copy the code


Once you have these things, you can easily play with them, such as executing a SQL statement:


function TSQLite.ExecuteSQL(ASQL: string): Boolean;

begin

 FQuery.Close;

 FQuery.SQL.Text:= ASQL;

 try

   FQuery.ExecSQL;

   Exit(True);

 except

   Exit(False);

 end;

end;Copy the code


This code seems too simple, and perhaps we’d rather have a reason why something went wrong, so we can change it:


function TSQLite.ExecuteSQL(ASQL: string; var AError: string): Boolean;

begin

 FQuery.Close;

 FQuery.SQL.Text:= ASQL;

 try

   FQuery.ExecSQL;

   Exit(True);

 except

   on E: Exception do begin

     AError:= e.Message;

     Exit(False);

   end;

 end;

end;Copy the code


Now, when you call this method, you just pass in an extra string argument to get the information when something went wrong.


In this system, it is also easy to query and encapsulate two additional methods:


// Query according to SQL statement

function TSQLite.Select(ASQL: string; var AError: string): Boolean;

begin

 FQuery.Close;

 FQuery.SQL.Text:= ASQL;

 try

   FQuery.Open;

   Exit(True);

 Except

   on E: Exception do begin

     AError:= e.Message;

     Exit(False);

   end;

 end;

end;



// Get the number of rows in the query result

function dbGetSelectResultCount(APath: PChar): Integer;

var

 database: TSQLite;

begin

 Result := -1;

 if (DatabaseExists(string(APath))) then begin

   database := GetDatabase(string(APath));

   Result := database.Query.RecordCount;

 end;

end;



// Get a record of the specified line number

function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec;

var

 database: TSQLite;

 tmp: string;

begin

 Inc(AIndex);

 if (DatabaseExists(string(APath))) then begin

   database := GetDatabase(string(APath));

   if (database.Query.RecordCount >= AIndex) then begin

     database.Query.RecNo:= AIndex;

     Result.AId:= database.Query.FieldByName('id').AsInteger;

     tmp := database.Query.FieldByName('name').AsString;

     Result.AName:= StrAlloc(tmp.Length);

     strcopy(Result.AName, PChar(tmp));

   end;

 end;

end;Copy the code


As a cross-platform library, it needs to be called by other programs, so there must be an export function, and different platforms, the required function form is different, especially since Android uses JNI to call the dynamic library, the export function must comply with the JNI specification.


The following example is a good example of how to export functions:



// iOS, PC

function dbGetSelectResultCount(APath: PChar): Integer; cdecl;

function dbGetSelectResult(APath: PChar; AIndex: Integer): TDemoRec; cdecl;



// Android

function Java_com_sqlite_sample_NativeAPI_dbGetSelectResultCount(env: PJNIEnv; obj: jobject; APath: jstring): jint; stdcall;

function Java_com_sqlite_sample_NativeAPI_dbGetSelectResult(env: PJNIEnv; obj: jobject; APath: jstring; AIndex: jint): jobject; stdcall;Copy the code


The only thing to note is the call protocol, which must be set to STDCall for JNI and cDECL for everything else.


So the next step is to compile, directly use the FPC cross-platform compiler, the compilation method is very simple:


$ fpc64 -Fisqlite -Fusqlite sample.lprCopy the code


Libsample. dylib can be generated on Mac and libsample.so can be generated on Linux.


Cross-platform compilation is a bit trickier, but much simpler than you might think:


$export ANDROID_LIB = / usr/local/codetyphon/binLibraries/android - 5.0 - api21 - arm /

$ export FPC=/usr/local/codetyphon/fpc/fpc64/bin/x86_64-linux/fpc

$ ${FPC} -Tandroid -Parm -Fl${ANDROID_LIB} -Fiqslite -Fusqlite sample.lprCopy the code


At this point, you can generate libsample.so for the Android system based on arm architecture. By changing the parameter after -p, you can also compile so for x86, MIPS and other architectures.


Once you’re done, take a look at how the iOS library is compiled. Since iOS no longer allows dynamic loading of dylib, we had to compile the code into static libraries, a.a files, statically linked into iOS projects.


$export FPC_ROOT = / usr/local/lib/FPC / 3.1.1

$ export FPC=${FPC_ROOT}/ppcrossa64

$ ${FPC} -Tdarwin -dIPHONEALL -Cn -Fisqlite -Fusqlite sample.lpr

$ ar -q libsample.a `grep "\.o$" link.res`

$ ranlib libsample.aCopy the code


At this point you get a libsample.a file for the 64-bit real machine, and two more.a files must be compiled for compatibility on 32-bit iOS and emulators.


32-bit true: Replace the compiler with PPCrossarm

Emulator: Replace the compiler with PPCX64 and replace the -t argument with iphonesimCopy the code


When we have.a with three different schemas, sometimes we need to merge them, using the following command to merge them:


lipo -create libsample_A64.a libsample_ARM.a libsample_EMU.a -output libsample.aCopy the code


The result is a fused.A, which can be used in a variety of situations.


Now that we’re all set, let’s take a look at how to use our libraries, using dbGetSelectResultCount and dbGetSelectResult as examples for each platform.


Android:


package com.sqlite.sample;



public class NativeAPI {

   static {  System.loadLibrary("sample"); }

   public static native int dbGetSelectResultCount(String APath);

   public static native DemoRec dbGetSelectResult(String APath, int AIndex);

}Copy the code


IOS:




extern int dbGetSelectResultCount(const char* APath);

extern struct DemoRec dbGetSelectResult(const char* APath, int AIndex);



PC (C++ as an example) :



typedef int (*dbSelectResultCount)(const char* APath);

typedef struct DemoRec (*dbSelectResult)(const char* APath, int AIndex);



void* handle = dlopen("./libsample.so", RTLD_LAZY);

dbSelectResultCount mSelectResultCount = (dbSelectResultCount) dlsym(handle, "dbGetSelectResultCount");

dbSelectResult mSelectResult = (dbSelectResult) dlsym(handle, "dbGetSelectResult");Copy the code



As you can see, the resulting API is consistent regardless of platform, thus unifying the invocation. On this basis, to do secondary packaging is also very convenient. In addition, since there is almost no code coupling, it is easy to change the logic of SQLite’s underlying library without affecting the upper level calls as long as the API remains unchanged.


The following is a complete call code, taking the iOS terminal as an example, the other terminals are the same:


// Copy the database file

NSString * originPath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"db"];

NSString * destPath = [ViewController getDocumentPath];

NSString * dbFile = [destPath stringByAppendingPathComponent:@"demo.db"];

[ViewController copyFile:originPath destFile:dbFile];



// Open the database

int b = dbOpen([dbFile UTF8String]);

printf("Open Database => %d\n", b);

// Execute the query

b = dbSelect([dbFile UTF8String], "select * from user");

printf("Select => %d\n", b);

// Get the number of rows in the query result

int count = dbGetSelectResultCount([dbFile UTF8String]);

printf("Select Rows => %d\n", count);

// retrieve each item of data

for (int i = 0; i < count; i++) {

   struct DemoRec r = dbGetSelectResult([dbFile UTF8String], i);

   printf("Data %d => {id => %d, name => %s}\n", i, r.AId, r.AName);

}

// Close the database

b = dbClose([dbFile UTF8String]);

printf("Close Database => %d\n", b);Copy the code


The output of this code is:



As you can see, the call succeeds and the data is passed correctly. The effect is exactly the same on other platforms.


The demo project is open source and can be accessed on Github:


https://github.com/rarnu/cross_sqlite