I've created a Qt dynamic lib that uses Qt SQL to open an SQLite database, but I'm getting this error:
QSqlDatabase: QSQLITE driver not loaded
QSqlDatabase: available drivers:
The DLL was working fine as part of a Qt Android application, however I need to use it through JNI from an existing Java application developed in Eclipse.
This is the shortest example code that reproduces the problem. I load the library from Java and call its init() method:
System.loadLibrary("plugins_sqldrivers_libqsqlite");
System.loadLibrary("Qt5Sql");
System.loadLibrary("MyQtLib");
MyQtLib.init();
And inside the Qt library I just call QSqlDatabase::addDatabase():
JNIEXPORT void JNICALL Java_test_MyQtLib_foo(JNIEnv *, jclass)
{
// Manually create a QCoreApplication instance.
int argc = 1;
static char arg[] = "";
static char *arg2 = arg;
app = new QCoreApplication(argc, &arg2);
// Try to add an SQLite db connection.
QSqlDatabase::addDatabase("QSQLITE");
}
Since the error is QSQLITE driver not loaded, and the Qt library was working inside a Qt application, I assume that Qt is doing some initialization that I'm missing.
But this didn't remove the error, so it must be something else. Normally, the Qt application will use QtApplication.java and QtActivity.java to perform some initialization, so they must be doing something more there that I'm not doing.
Eventually I stepped into the Qt sources to see how the SQLITE plugin is loaded (for the desktop build at least).
The relevant function was QFactoryLoader::update(). In it I noticed that it iterates all the directories in QCoreApplication::libraryPaths():
QStringList paths = QCoreApplication::libraryPaths();
for (int i = 0; i < paths.count(); ++i) {
If any of them has a sub-directory named "sqldrivers", it goes inside it and tries to load all the dynamic libraries in that sub-directory.
I then printed out the library paths in a test project I ran directly from Qt Creator - qDebug() << a.libraryPaths();, and I saw this path - /data/data/org.qtproject.example.untitled/qt-reserved-files/plugins. In this directory on my android phone there was a subdirectory named sqldrivers, that contained a single file - libqsqlite.so.
I then checked the .java files, and indeed QtActivity::startApp() adds the library path:
boolean bundlingQtLibs = false;
if (m_activityInfo.metaData.containsKey("android.app.bundle_local_qt_libs")
&& m_activityInfo.metaData.getInt("android.app.bundle_local_qt_libs") == 1) {
localPrefix = getApplicationInfo().dataDir + "/";
pluginsPrefix = localPrefix + "qt-reserved-files/";
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
extractBundledPluginsAndImports(pluginsPrefix);
bundlingQtLibs = true;
}
The solution then would be to ensure that there is a sqldrivers/libqsqlite.so somewhere on the phone, and then add the parent folder of sqldrivers to the library path using QCoreApplication::addLibraryPath().
Related
We are working on an Android and iOS (but we want to just get Android working for now) application that needs to rely on some native drivers (.so, .a and some .ini files). The company providing those drivers also provides a Xamarin project to showcase how the drivers are used. They seem to be storing those driver and other files as assets (.ini files under Assets/Files and .so files under Assets/lib/arm64-v8a and Assets/lib/armeabi-v7a respectively) and extracting the former using the following code:
void ExtractAssets()
{
var assets = ApplicationContext.Assets;
var paths = assets.List("Files");
foreach (var path in paths)
{
// Read the compressed file and extract
string readPath = Path.Combine("Files", path);
byte[] buffer = new byte[32000];
int bytesRead = 0;
using (var inStream = assets.Open(readPath))
using (var outStream = ApplicationContext.OpenFileOutput(path, Android.Content.FileCreationMode.Private))
{
do
{
bytesRead = inStream.Read(buffer, 0, buffer.Length);
if (bytesRead > 0)
{
outStream.Write(buffer, 0, bytesRead);
}
}
while (bytesRead > 0);
}
System.Diagnostics.Debug.WriteLine(string.Format("Extracted '{0}' to '{1}'", readPath, path));
}
}
Since Xamarin is becoming obsolete and MAUI is the next big thing, we decided to build our project on that. My colleague and I have spent the entire afternoon on a sample project and we still do not manage to simply load a libhello-jni.so file (we use this one) at runtime to call a stringFromJNI(). We tried:
Using an Android Native Binding project — we never really got what that's about and how mappings are supposed to work
Trying to include the libhello-jni.so file as an asset and load it using JavaNative.LoadLibrary("hello-jni"); and this had various unsuccessful results, it either did not find the library at all or it was complaining about the lib being built for x86 time processor instead of x64.
Can anyone please help us with some sample code allowing us to just simply bind a native .so file in a MAUI project, or at least provide us with a good guide? Thank you and sorry for the long post.
Not sure if this is exactly what you are going for or not, but we use bindings to a C library that we develop for various platforms, including Android. The way to correctly place native libraries into the final APK is via the EmbeddedNativeLibrary type in .csproj as demonstrated here. This will include your native library alongside the other native libraries that the toolchain is going to create (like the .NET runtime, garbage collection native lib, etc).
One thing changed in .NET 6, however. This affects us as an SDK distributor, because we need to include all necessary files inside of Nuget packages, so it may not affect you. In Xamarin, when you embed native libraries, the native libraries are literally stitched into your assembly and no further files are needed. In .NET 6, now they are all placed into a separate .aar file which also needs to be available when building the final application.
If you do everything correctly, you will see your native library inside of the APK in the lib/<arch> directory. Our native library is not JNI, and thus we use P/Invoke so we don't need a LoadLibrary call but I am reasonably certain that the call should succeed with the libraries in this directory.
I cross-compiled my C application for Android ARM with arm-linux-gnueabi tool under Ubuntu 16.04 LTS. I compiled it with static linking flag. This C application is big and it has complicated makefile. It compiled successfully without any errors. But it behaves differently on Android phone and Ubuntu PC. More precisely i have two problems:
popen() and system() functions don't work on Android. They are carried out but do nothing and don't give any errors. This problem i solved with dirty hack.
fgets() functions works strange on Android.
1. About first problem.
I did small research and found that Android doesn't use ordinary libc library (glibc or another library which implements POSIX standard properly). It uses Bionic library instead of it (sorry, Android is new OS for me). I looked into popen() and system() functions code and noticed that these functions use _PATH_BSHELL macros. _PATH_BSHELL is path to the actual system shell. This path is "/system/bin/sh" on Android and "/bin/sh" on Ubuntu.
When i understood it i tried to hook popen() and system() functions. I copied code of these functions from the Bionic source, than i defined macros #define _MY_PATH_BSHELL "/system/bin/sh" and replaced calls like execve(_MY_PATH_BSHELL, argp, environ); by execve(_MY_PATH_BSHELL, argp, environ); calls. So it started work properly.
2. About second problem.
On Ubuntu this code works properly:
is_received = false;
while(!is_received) {
FILE *cmd = popen(command, "r");
is_received = fgets(buf, sizeof(buf), cmd) == NULL ? false : true;
}
But on Android fgets() always returns NULL and this loop works infinitely long. I tried to use read() function instead of fgets() and it worked.
On Android this code with read() works properly:
is_received = false;
while(!is_received) {
FILE *cmd = hooked_popen(command, "r");
int fd = fileno(cmd);
is_received = read(fd, buf, sizeof(buf)) == 0 ? false : true;
}
My questions.
How to solve my problems with popen() and system() neatly and correctly? I think i have to link statically with Bionic library. Is it right? How can i do it in console without Android Studio? I read that it is necessary to use NDK but it is not clear to me how.
Why fgets() behavior isn't similar on Android and Ubuntu?
I downloaded an APK from Play Store that contains native code binaries. In the APK file there is an lib/x86 folder that supposedly contains a library file containing native procedures, normally a .so extension. Since the code is in x86, is it possible to write a Java program to invoke the library on the desktop? Even if you dont have the source code for that library. The NDK function just has to accept parameters and return a value. For example, can we write
class AppNativeLoader
{
public static native void generateRand(int seed);
static
{
System.loadLibrary( "AndroidNDKLib" );
}
}
public class WCallTest
{
public static void main( String[ ] args )
{
long seed = System.currentTimeMillis();
if(args.length > 0) {
seed = Long.valueOf(args[0]);
}
long rand = AppNativeLoader.generateRand(seed);
System.out.println(rand);
}
}
NOTE: This is just an example. The actual environment differs. Using JRE 7 on RHEL, I extracted the x86 .so and placed it in the same directory as the .class file. I still get an UnSatisfiedLinkerError. Anything amiss? Assuming there are no callbacks and the function doesn't utilize and Android APIs, is this possible?
EDIT: I opened the lib in IDA Pro and I saw the following dependencies
.plt:0000B100 ; Needed Library 'liblog.so'
.plt:0000B100 ; Needed Library 'libz.so'
.plt:0000B100 ; Needed Library 'libc.so'
.plt:0000B100 ; Needed Library 'libm.so'
.plt:0000B100 ; Needed Library 'libstdc++.so'
.plt:0000B100 ; Needed Library 'libdl.so'
These should be available in my desktop environment, no?
Not all Linux environments are identical (even crossing distribution boundaries is not guaranteed to work). NDK binaries are built against Bionic and a handful of other Android specific libraries, whereas your RedHat system uses glibc and a bunch of other things available from the RedHat repositories.
tl;dr you can't run Android binaries on desktop Linux.
You can try downloading the needed shared libraries from here (make sure to choose the correct API version, and an architecture matching the architecture of the NDK shared library, to find out which shared libraries you need you can simply use ldd).
Then, to easily access the methods exposed by the shared lib, you can decompile the java code of the app using jadx, and then write your own code around the JNI classes.
Then, to compile your java code, you can use any version of the JDK.
Then, to execute it, you'll have to use a version of JRE matching the architecture of the NDK shared library (in your case, you'll have to download the 32-bit JRE).
However, this is not guaranteed to work: I am currently getting segfaults in the NDK shared library I'm trying to use on my PC, and since most NDK binaries are stripped, debugging is going to be a nightmare.
We have been working on an Android project using Adobe AIR for a long time and now we need to implement the feature to search for Upnp devices in the network. From my understanding, it is not possible to implement this feature on AIR level (please correct me if I am wrong), so an Android native extension is required. I could not find any UPNP Native Extension available and decided to build one based on Cling library Cling UPNP Browser. I could get it work as native Android application but when I tried to convert it to AIR native extension, it did not work at all. Does anyone successfully implement the UPNP on Android - AIR, any help will be greatly appreciate.
After spending more time to search for other alternative, finally, I have made it work using CyberLink For Java. The implementation of CyberLink library is very straight forward. For anyone plans to create your own UPNP Native Extension for Android, here is the process to build your .jar native extension.
Include the external cyberlink .jar library to your Android Java project.
To start searching for UPNP Devices, you have to open a new thread:
new SearchingForUpnpTask().execute("Empty Param"); //You can specify your own param...
private class SearchingForUpnpTask extends AsyncTask<String, Void, DeviceList>{
protected DeviceList doInBackground(String... params){
ControlPoint ctrPoint = new ControlPoint();
ctrPoint.start("upnp:rootdevice");
DeviceList devList = ctrPoint.getDeviceList();
int nRootDevs = devList.size();
for (int n=0; n < nRootDevs; n++) {
Device dev = devList.getDevice(n);
String devName = dev.getFriendlyName();
System.out.println("[" + n + "] = " + devName);
}
...
return devList;
}
To compile the .jar file for your Android will require some works as Adobe AIR does not understand your external cyberlink .jar file that we include. When you try to debug the native extension, you will receive the error Log: ...the class 'org...ControlPoint' is not found in the method ... . In order to make it work, you have to combine all the .jar files into one. We have 2 options here:
1st method: Export your Andoird .jar file, change the extension to .zip then unzip it. Then change the .jar extension of the cyperlink .jar file to .zip then unzip it. Copy the source from the cyperlink .jar folder to your Android's .jar folder. Finally, jar the whole folder again.
2nd method (easier): Use jarjar.jar from Google (thanks to Joe Ward).
Hope this helps.
I'm trying to build an Android app which makes use of the NativeActivity facility of the NDK.
I'm having the following structure:
a bunch of native shared libraries installed in /system/vendor/<company>; I'm working
with a custom built Android image so there's no problem having the libraries there with
proper permissions and everything
a couple of applications using the NativeActivity that depend in turn on the libraries
mentioned above
The libraries installed in the /system/vendor and my applications use a couple of
configuration files. There's no problem reading them using the standard C API
fopen/fclose. But those libraries and my application also need to store some files
as the result of their operation, like configuration, some run-time parameters, calibration
data, log files etc. With the storing of the files there is a slight issue as I'm not allowed to write into /system/vendor/... (as the file system under "/system/..." is mounted read-only and I do not want to hack on that).
So what would be the best way to create and store those files and where would be the
best "conforming with Android" storage area ?
I've been reading a couple of threads in the android-ndk Google group and here on SO that mention either the internal application private storage or the external SD card, but as I do not have extended experience with Android I'm not sure what would be the proper approach. If the approach involves some specific Android API a small code example in C++ would be very helpful; I've seen a couple of examples involving Java and JNI (e.g. in this SO question) but I would like to stay away from that right now.
Also there seems to be a problem with using from C++ the native activity's
internalDataPath/externalDataPath pair (a bug that makes them be always NULL).
For relatively small files(application config files, parameter files, log files etc.)
is best to use the internal application private storage, that is /data/data/<package>/files.
The external storage if it exists at all (being it SD card or not) should be used for large files that do not need frequent access or updates.
For the external data storage the native application has to "request" the correct permissions in the application's AndroidManifest.xml:
<manifest>
...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE">
</uses-permission>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE">
</uses-permission>
</manifest>
For the internal application private storage fopen/fclose(or C++ stream equivalents if available) API could be used. Following example illustrates using the Android NDK AssetManager to retrieve and read a configuration file. The file must be placed into the assets directory inside the native application’s project folder so that the NDK build could pack them inside the APK. The internalDataPath/externalDataPath bug I was mentioning in the question was fixed for the NDK r8 version.
...
void android_main(struct android_app* state)
{
// Make sure glue isn't stripped
app_dummy();
ANativeActivity* nativeActivity = state->activity;
const char* internalPath = nativeActivity->internalDataPath;
std::string dataPath(internalPath);
// internalDataPath points directly to the files/ directory
std::string configFile = dataPath + "/app_config.xml";
// sometimes if this is the first time we run the app
// then we need to create the internal storage "files" directory
struct stat sb;
int32_t res = stat(dataPath.c_str(), &sb);
if (0 == res && sb.st_mode & S_IFDIR)
{
LOGD("'files/' dir already in app's internal data storage.");
}
else if (ENOENT == errno)
{
res = mkdir(dataPath.c_str(), 0770);
}
if (0 == res)
{
// test to see if the config file is already present
res = stat(configFile.c_str(), &sb);
if (0 == res && sb.st_mode & S_IFREG)
{
LOGI("Application config file already present");
}
else
{
LOGI("Application config file does not exist. Creating it ...");
// read our application config file from the assets inside the apk
// save the config file contents in the application's internal storage
LOGD("Reading config file using the asset manager.\n");
AAssetManager* assetManager = nativeActivity->assetManager;
AAsset* configFileAsset = AAssetManager_open(assetManager, "app_config.xml", AASSET_MODE_BUFFER);
const void* configData = AAsset_getBuffer(configFileAsset);
const off_t configLen = AAsset_getLength(configFileAsset);
FILE* appConfigFile = std::fopen(configFile.c_str(), "w+");
if (NULL == appConfigFile)
{
LOGE("Could not create app configuration file.\n");
}
else
{
LOGI("App config file created successfully. Writing config data ...\n");
res = std::fwrite(configData, sizeof(char), configLen, appConfigFile);
if (configLen != res)
{
LOGE("Error generating app configuration file.\n");
}
}
std::fclose(appConfigFile);
AAsset_close(configFileAsset);
}
}
}