Skip to content

Commit 4b367c0

Browse files
committed
Firebird usage from Android / C++ through JNI.
1 parent c8a78db commit 4b367c0

2 files changed

Lines changed: 213 additions & 16 deletions

File tree

Lines changed: 191 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,194 @@
11
#include <jni.h>
2+
#include <exception>
3+
#include <memory>
4+
#include <stdexcept>
25
#include <string>
6+
#include <dlfcn.h>
7+
#include "firebird/Interface.h"
8+
#include "firebird/Message.h"
39

4-
extern "C" JNIEXPORT jstring JNICALL
5-
Java_com_example_firebirdandroidcpp_MainActivity_stringFromJNI(
6-
JNIEnv* env,
7-
jobject /* this */) {
8-
std::string hello = "Hello from C++";
9-
return env->NewStringUTF(hello.c_str());
10-
}
10+
namespace fb = Firebird;
11+
12+
using GetMasterPtr = decltype(&fb::fb_get_master_interface);
13+
14+
static constexpr auto LIB_FBCLIENT = "libfbclient.so";
15+
static constexpr auto SYMBOL_GET_MASTER_INTERFACE = "fb_get_master_interface";
16+
17+
static void* handle = nullptr;
18+
static GetMasterPtr masterFunc = nullptr;
19+
static fb::IMaster* master = nullptr;
20+
static fb::IUtil* util = nullptr;
21+
static fb::IProvider* dispatcher = nullptr;
22+
static fb::IStatus* status = nullptr;
23+
static std::unique_ptr<fb::ThrowStatusWrapper> statusWrapper;
24+
static fb::IAttachment* attachment = nullptr;
25+
26+
27+
// Loads Firebird library and get main interfaces.
28+
static void loadLibrary() {
29+
if (handle)
30+
return;
31+
32+
if (!(handle = dlopen(LIB_FBCLIENT, RTLD_NOW)))
33+
throw std::runtime_error("Error loading Firebird client library.");
34+
35+
if (!(masterFunc = (GetMasterPtr) dlsym(handle, SYMBOL_GET_MASTER_INTERFACE))) {
36+
dlclose(handle);
37+
handle = nullptr;
38+
throw std::runtime_error("Error getting Firebird master interface.");
39+
}
40+
41+
master = masterFunc();
42+
util = master->getUtilInterface();
43+
dispatcher = master->getDispatcher();
44+
status = master->getStatus();
45+
statusWrapper = std::make_unique<fb::ThrowStatusWrapper>(status);
46+
}
47+
48+
// Unloads Firebird library.
49+
static void unloadLibrary() {
50+
if (handle) {
51+
dispatcher->shutdown(statusWrapper.get(), 0, fb_shutrsn_app_stopped);
52+
status->dispose();
53+
dispatcher->release();
54+
dlclose(handle);
55+
handle = nullptr;
56+
}
57+
}
58+
59+
// Connects to Firebird database. Creates it if necessary.
60+
static void connect(std::string databaseName) {
61+
loadLibrary();
62+
63+
try {
64+
attachment = dispatcher->attachDatabase(
65+
statusWrapper.get(),
66+
databaseName.c_str(),
67+
0,
68+
nullptr);
69+
}
70+
catch (const fb::FbException&) {
71+
attachment = dispatcher->createDatabase(
72+
statusWrapper.get(),
73+
databaseName.c_str(),
74+
0,
75+
nullptr);
76+
}
77+
}
78+
79+
// Disconnects the database.
80+
static void disconnect() {
81+
if (attachment) {
82+
attachment->detach(statusWrapper.get());
83+
attachment->release();
84+
attachment = nullptr;
85+
}
86+
87+
unloadLibrary();
88+
}
89+
90+
// Query CURRENT_TIMESTAMP using a Firebird query.
91+
static std::string getCurrentTimestamp() {
92+
const auto transaction = attachment->startTransaction(statusWrapper.get(), 0, nullptr);
93+
94+
FB_MESSAGE(message, fb::ThrowStatusWrapper,
95+
(FB_VARCHAR(64), currentTimestamp)
96+
) message(statusWrapper.get(), master);
97+
98+
attachment->execute(
99+
statusWrapper.get(),
100+
transaction,
101+
0,
102+
"select current_timestamp from rdb$database",
103+
SQL_DIALECT_CURRENT,
104+
nullptr,
105+
nullptr,
106+
message.getMetadata(),
107+
message.getData());
108+
109+
transaction->commit(statusWrapper.get());
110+
transaction->release();
111+
112+
return std::string(message->currentTimestamp.str, message->currentTimestamp.length);
113+
}
114+
115+
116+
// Converts JNI string to std::string.
117+
static std::string convertJString(JNIEnv* env, jstring str) {
118+
if (!str)
119+
return {};
120+
121+
const auto len = env->GetStringUTFLength(str);
122+
const auto strChars = env->GetStringUTFChars(str, nullptr);
123+
124+
std::string result(strChars, len);
125+
126+
env->ReleaseStringUTFChars(str, strChars);
127+
128+
return result;
129+
}
130+
131+
// Rethrow C++ as JNI exception.
132+
static void jniRethrow(JNIEnv* env)
133+
{
134+
std::string message;
135+
136+
try {
137+
throw;
138+
assert(false);
139+
return;
140+
}
141+
catch (const fb::FbException& e) {
142+
char buffer[1024];
143+
util->formatStatus(buffer, sizeof(buffer), e.getStatus());
144+
message = buffer;
145+
}
146+
catch (const std::exception& e) {
147+
message = e.what();
148+
}
149+
catch (...) {
150+
message = "Unrecognized C++ exception";
151+
}
152+
153+
const auto exception = env->FindClass("java/lang/Exception");
154+
env->ThrowNew(exception, message.c_str());
155+
}
156+
157+
158+
// JNI JMainActivity.connect.
159+
extern "C" JNIEXPORT
160+
void JNICALL Java_com_example_firebirdandroidcpp_MainActivity_connect(
161+
JNIEnv* env, jobject self, jstring databaseName) {
162+
try {
163+
connect(convertJString(env, databaseName));
164+
}
165+
catch (...) {
166+
jniRethrow(env);
167+
}
168+
}
169+
170+
// JNI JMainActivity.disconnect.
171+
extern "C" JNIEXPORT
172+
void JNICALL Java_com_example_firebirdandroidcpp_MainActivity_disconnect(
173+
JNIEnv* env, jobject self) {
174+
try {
175+
disconnect();
176+
}
177+
catch (...) {
178+
jniRethrow(env);
179+
}
180+
}
181+
182+
// JNI JMainActivity.getCurrentTimestamp.
183+
extern "C" JNIEXPORT
184+
jstring JNICALL Java_com_example_firebirdandroidcpp_MainActivity_getCurrentTimestamp(
185+
JNIEnv* env, jobject self) {
186+
try {
187+
std::string currentTimestamp = getCurrentTimestamp();
188+
return env->NewStringUTF(currentTimestamp.c_str());
189+
}
190+
catch (...) {
191+
jniRethrow(env);
192+
return nullptr;
193+
}
194+
}

android-cpp/app/src/main/java/com/example/firebirdandroidcpp/MainActivity.kt

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.example.firebirdandroidcpp
22

33
import androidx.appcompat.app.AppCompatActivity
44
import android.os.Bundle
5-
import android.widget.TextView
65
import com.example.firebirdandroidcpp.databinding.ActivityMainBinding
6+
import org.firebirdsql.android.embedded.FirebirdConf
7+
import java.io.File
78

89
class MainActivity : AppCompatActivity() {
910

@@ -12,23 +13,35 @@ class MainActivity : AppCompatActivity() {
1213
override fun onCreate(savedInstanceState: Bundle?) {
1314
super.onCreate(savedInstanceState)
1415

16+
FirebirdConf.extractAssets(baseContext, false)
17+
FirebirdConf.setEnv(baseContext)
18+
19+
connect(File(filesDir, "test.fdb").absolutePath)
20+
1521
binding = ActivityMainBinding.inflate(layoutInflater)
1622
setContentView(binding.root)
1723

18-
// Example of a call to a native method
19-
binding.sampleText.text = stringFromJNI()
24+
try {
25+
binding.sampleText.text = getCurrentTimestamp()
26+
}
27+
catch (e: Exception) {
28+
binding.sampleText.text = "Error: ${e.message}"
29+
}
30+
}
31+
32+
override fun onDestroy() {
33+
disconnect();
34+
super.onDestroy()
2035
}
2136

22-
/**
23-
* A native method that is implemented by the 'firebirdandroidcpp' native library,
24-
* which is packaged with this application.
25-
*/
26-
external fun stringFromJNI(): String
37+
private external fun connect(databaseName: String)
38+
private external fun disconnect()
39+
private external fun getCurrentTimestamp(): String
2740

2841
companion object {
2942
// Used to load the 'firebirdandroidcpp' library on application startup.
3043
init {
3144
System.loadLibrary("firebirdandroidcpp")
3245
}
3346
}
34-
}
47+
}

0 commit comments

Comments
 (0)