Dart FFI can't load dynamic library on Android - android

I have a simple C++ function compiled into a dylib file that I'm trying to run on an Android phone. The function is super simple, it just adds to numbers and returns the result. However, I keep getting this error:
Another exception was thrown: Invalid argument(s): Failed to load dynamic library 'libadd.dylib': dlopen failed: library "libadd.dylib" not found .
I'm really not sure what I'm doing wrong. I've done the following steps:
My Dart implementation:
import 'dart:ffi' as ffi;
import 'dart:io' show Platform, Directory;
import 'package:path/path.dart' as path;
typedef C_ADD = ffi.Int Function(
ffi.Int a, ffi.Int b); // FFI signature of C function
typedef ADD = int Function(int a, int b);
void linkAndCallFunction() {
var libraryPath = path.join(Directory.current.path, "libadd.dylib");
final dylib = ffi.DynamicLibrary.open(libraryPath);
final ADD add = dylib.lookup<ffi.NativeFunction<C_ADD>>("add").asFunction();
final result = add(40, 2);
print(result);
}
I've added these to the build.gradle files:
build.gradle:
buildscript{
ext{
ndkVersion = "25.1.8937393"
}
...
and app/build.gradle:
android {
ndkVersion rootProject.ext.ndkVersion
externalNativeBuild {
cmake {
path "../../lib/CMakeLists.txt"
}
}
This is my CMakeLists.txt file:
cmake_minimum_required(VERSION 3.10.2)
project(add LANGUAGES CXX C)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
add_library(add SHARED ./add.cpp)
and my file structure of the project looks like this:
lib/
- add.cpp
- add.o
- CMakeLists.txt
- libadd.dylib
- main.dart
it also may be worth mentioning that in order to compile add.cpp into a dylib I ran the following commands:
g++ -c add.cpp
ar rvs libadd.dylib add.o
and if you're wondering, add.cpp looks like this:
#define EXPORT extern "C" __attribute__((visibility("default")))
__attribute__((used))
EXPORT
int add(int a, int b){
return a + b;
}
Where is this error coming from? am I compiling to a dylib incorrectly?

The answer to this problem was relatively simple, it just stemmed from my lack of knowledge on how Android actually compiles to a static library. Hopefully it helps someone else who is trying to understand how to setup external C++ code in a flutter program.
The static library is generated automatically and is set up in CMakeLists.txt.
Firstly, I moved all the C++ files into the android folder. Then, I set up CMakeLists.txt like this:
cmake_minimum_required(VERSION 3.10.2)
add_library( add // library name, will create libadd.so
SHARED
add.cpp
)
The problem before was that I was trying to manually compile the file myself, when I should have been letting CMakeLists do it instead. According to the android documentation:
"The convention CMake uses to name the file of your library is as follows:
liblibrary-name.so
For example, if you specify "native-lib" as the name of your shared library in the build script, CMake creates a file named libnative-lib.so. "
https://developer.android.com/studio/projects/configure-cmake
So when I run the program, the static library is created automatically and placed in the correct place. Then, the Dart FFI can find it with the DynamicLibrary.open() function. In this case, CMakeLists will generate a file called libadd.so, and I can callDynamicLibrary.open('libadd.so') and the program works.

Related

Pass a variable at build time into externalNativeBuild in Android

Is there a way to pass a constant at compile time into the externalNativeBuild gradle enclosure, and to be able to use it in code?
For example:
externalNativeBuild {
cmake {
arguments "-DTEST=" + rootProject.ext.value
}
}
And the to be able to use it somehow like next:
void foo() {
int bla = TEST;
....
}
Note that the above code is not working, as TEST is not recognized by the c++ compiler, which is what I am trying to solve.
The easiest solution will be to use add_definitions:
add_definitions(-DTEST=${TEST})
to your CMakeLists.txt file.
Another one would be to use configuration file
Basing on Native sample app from Android Studio:
Create file: config.h.in in the same folder as default native-lib.cpp with following contents:
#ifndef NATIVEAPP1_CONFIG_H_IN
#define NATIVEAPP1_CONFIG_H_IN
#cmakedefine TEST ${TEST}
#endif //NATIVEAPP1_CONFIG_H_IN
In CMakeLists.txt add:
# This sets cmake variable to the one passed from gradle.
# This TEST variable is available only in cmake but can be read in
# config.h.in which is used by cmake to create config.h
set(TEST ${TEST})
configure_file( config.h.in ${CMAKE_BINARY_DIR}/generated/config.h )
include_directories( ${CMAKE_BINARY_DIR}/generated/ )
(add it for example, above add_library call)
Finally, add #include "config.h" in you cod (ex. in native-lib.cpp) to use TEST macro.
Rebuild

How to properly use a c++ static library (.lib) in a Android shared library (.so) project in Visual Studio?

I need to create a .so Android shared library for a project, and I need to use functions from a C++ static library (.lib). The .so library is meant to be used just as a bridge, so I can use functions from my static library in Android apps and games. I've created a C++ cross-platform Android project in Visual Studio 2017 (also testing it in VS2019), included the header file for the static library and the library itself, and everything is theoretically configured and linked.
The current library I'm using is a simple static library for tests that has just a single function:
namespace StaticLibrary
{
extern "C" void Func()
{
// something
}
}
The file for the Android looks something like this:
#include "StaticLibraryHeader.h"
extern "C"
{
// ...
void StaticLibFunc()
{
StaticLibrary::Func();
}
}
When I try to compile the project, the compiler says:
C:\\Microsoft\AndroidNDK64\android-ndk-r16b\toolchains\x86_64-4.9\prebuilt\windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: error: F:\Android_IOS_libs\MobileLibraries2019\Build\x64\Debug\libStaticLibTest_x64_Debug.lib: bad extended name index at 778
and:
undefined reference to 'Func'
I can get rid of this second error by using the --whole-archive option of the linker but then this new error shows up, along with the first one:
C:\\Microsoft\AndroidNDK64\android-ndk-r16b\toolchains\x86_64-4.9\prebuilt\windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: fatal error: F:\Android_IOS_libs\MobileLibraries2019\Build\x64\Debug\libStaticLibTest_x64_Debug.lib: attempt to map 60 bytes at offset 6324 exceeds size of file; the file may be corrupt
I've also used Google Lib2A ( https://code.google.com/archive/p/lib2a/ ) to create a .a file, but then the following error happens:
C:\\Microsoft\AndroidNDK64\android-ndk-r16b\toolchains\x86_64-4.9\prebuilt\windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: error: F:\Android_IOS_libs\MobileLibraries2019\Krilloud_Android\lib\x64\libStaticLibTestDLL_x64_Debug.a: member at 192 is not an ELF object
C:\\Microsoft\AndroidNDK64\android-ndk-r16b\toolchains\x86_64-4.9\prebuilt\windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: error: F:\Android_IOS_libs\MobileLibraries2019\Krilloud_Android\lib\x64\libStaticLibTestDLL_x64_Debug.a: member at 870 is not an ELF object
C:\\Microsoft\AndroidNDK64\android-ndk-r16b\toolchains\x86_64-4.9\prebuilt\windows-x86_64/lib/gcc/x86_64-linux-android/4.9.x/../../../../x86_64-linux-android/bin\ld: error: F:\Android_IOS_libs\MobileLibraries2019\Krilloud_Android\lib\x64\libStaticLibTestDLL_x64_Debug.a: member at 1656 is not an ELF object
I'm not an Android/Java developer, so I'm lost here. How do I properly link and use my static library in a .so project? Can it be done using Visual Studio or do I really need to install Android Studio for this? I will also need to create the same thing for iOS, so I was hoping to have both projects in a Visual Studio solution.

Why the same C++ library has much smaller size in static build than shared build, and static is not working while shared works?

I have problem when I try to use C++ library in Android studio project.
I am using statically built Qt 5.14.2 for Android.
I have Qt library project which has only 1 class with this 2 functions:
testclass.h
#include <QDebug>
#include <jni.h>
extern "C"
{
JNIEXPORT void JNICALL Java_com_example_MyTestAndroidApp_LibraryClass_log();
void logNormal();
}
testclass.cpp
JNIEXPORT void JNICALL Java_com_example_MyTestAndroidApp_LibraryClass_log()
{
qDebug() << "---> log from Qt library";
}
void logNormal()
{
qDebug() << "---> log from Qt library";
}
An in the .pro file I am using shared or staticlib to switch from shared(.so) and static(.a) library.
TEMPLATE = lib
CONFIG += shared
#CONFIG += staticlib
CONFIG += c++11
After successfully building the lib (shared and static) I got this files(for ABI x86 but others ABIS have similar sizes too):
libTestLibrary.so ---> 3,359 KB
libTestLibrary.a ---> 7 KB
For the shared (.so) library I was able to successfully call the function from android app.
In Android Studio I am creating new Native C++ project. I have Java JNI LibraryClas which is used to load the library and call the log() function from the library. I placed the library in jniLibs folder.
But for the static(.a) library there are a lot of problems. I tried to call the logNormal() function from the C++ part of the android app(from native-lib.cpp). I am not using JNI like in the shared library case. Here is what I tried:
Create new Native C++ project
Created libs(for the static library) and include(for testclass.h) folders inside app\src\main\cpp
Inside CMakeList.txt I have added this:
add_library(TestLibrary STATIC IMPORTED)
set_target_properties(TestLibrary PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libTestLibrary.a)
target_link_libraries( native-lib TestLibrary ${log-lib} )
And I called logNormal(); from native-lib.cpp.
First I got Error about missing Qt5Core library. After adding it the same way like my library I got errors for others missing libraries and files.
I have this questions:
Why static library is so much smaller then the shared library? (7KB vs 3,359KB) I found this which is opposite from my case:
Can I somehow build my static lib with all libraries and files that it need, so I don't get errors for missing libraries?
Can I use JNI to call C++ functions from static lib? Or when calling C++ functions from static lib it must be done from the C++ part of the android project?
Sorry for the long post and more then 1 question. Any hint or help is welcome. Thank you so much in advance.

How do I Print to Console using dart:ffi in Flutter?

Trying to print to console when running my Flutter app (on Android) from the following C++ code I am running through dart:ffi:
#include <iostream>
std::cout << "Hello, World!";
Does not give me any output in the terminal. How would I print to the Flutter terminal from C++?
I know my functions otherwise work correctly because I am getting correct actions on pointers / return values.
Edit: When only running on android, there is a simpler way. I have updated the answer below.
You'll have to pass a wrapper to the print function into the native code
void wrappedPrint(Pointer<Utf8> arg){
print(Utf8.fromUtf8(arg));
}
typedef _wrappedPrint_C = Void Function(Pointer<Utf8> a);
final wrappedPrintPointer = Pointer.fromFunction<_wrappedPrint_C>(_wrappedPrint_C);
final void Function(Pointer) initialize =
_nativeLibrary
.lookup<NativeFunction<Void Function(Pointer)>>("initialize")
.asFunction<void Function(Pointer)>();
initialize(wrappedPrintPointer);
and then use that in your C library:
void (*print)(char *);
void initialize(void (*printCallback)(char *)) {
print = printCallback;
print("C library initialized");
}
void someOtherFunction() {
print("Hello World");
}
When only running on android, things get simpler. Instead of all of the above, do:
Simply use the android logging mechanism, it will show up on the console, at least when using flutter run. I'm assuming flutter attaches to logcat using the app's PID.
To do so, update the CMakeLists.txt with:
find_library( # Defines the name of the path variable that stores the
# location of the NDK library.
log-lib
# Specifies the name of the NDK library that
# CMake needs to locate.
log )
# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
<your-libs-name-here>
# Links the log library to the target library.
${log-lib} )
and in your c lib do:
#include <android/log.h>
void someOtherFunction() {
__android_log_print(ANDROID_LOG_DEBUG, "flutter", "Hello world! You can use %s", "formatting");
}

DllImport - DllNotFoundException with android .so

It's the first time I try to create a library c++ for Android/iOS.
I'm using Visual Studio 2015 - Xamarin.
First I created a project : Visual C++ -> Cross Platform -> Shared Library. In the hared library, I created 2 files.
SayHello.h :
#pragma once
#include <string.h>
class SayHello {
public:
SayHello();
~SayHello();
static char* Hello();
};
SayHello.cpp :
#include "SayHello.h"
extern "C"
{
SayHello::SayHello(){}
SayHello::~SayHello(){}
char * SayHello::Hello()
{
return "Hello !";
}
}
Then I generated a file libSayHello.so and created a project Android with xamarin to try to call the function hello.
There is my MainActivity.cs :
[DllImport("libSayHello.so")]
static extern String Hello();
protected override void OnCreate(Bundle bundle)
{
// I paste only my added code :
String hello = Hello();
Toast.MakeText(this.ApplicationContext, hello, ToastLength.Long);
}
I did all steps in this tutorial, but I have an exception :
System.DllNotFoundException: libSayHello.so
I searched for this, but I must be so noob cause I did not find anything. How should I use my libSayHello.so ?
EDIT:
There is my libSayHello.so seen with 7zip:
And my project :
I think this will be the best sample for you.
This all works according to the following scheme:
Android supports 7 CPU architectures.
But Xamarin supports 5 of them. So in the settings of your Xamarin.Android project check which architectures you will support:
[Xamarin.Droid.project]->[Properties]->[Android Options]->[Advanced]->[Supported architectures]
Check which archs are necessary for your project. According to this your shared library should be compiled for these archs. And you should put your shared libraries in Xamarin.Droid.project's folder lib:
To see them in Solution Explorer you should mention them in your Xamarin.Android project's .CSPROJ.
Add there next item groups:
<ItemGroup>
<AndroidNativeLibrary Include="lib\{ARCH}\libCLib.so">
<Abi>{ARCH}</Abi>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
</ItemGroup>
{ARCH} could be next: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64.
Now you can put DllImport in your code:
[DllImport("libCLib", EntryPoint = "clib_add")]
public static extern int Add(int left, int right);
I think you have to tell about entry point, because i had runtime errors without this statement System.EntryPointNotFoundException.
And don't forget to add next in your code:
using System.Runtime.InteropServices;
Confirm the library is put in "/lib/{arch}/" folder.
Try [DllImport("SayHello")]. the engine may add "lib" and ".so" automatically.

Categories

Resources