I am writing an example android app to demonstrate the use of our (university research) C/C++ library.
I know that, using jni, we can call C functions from java.
However, I have not found a step by step set of instructions for how to do this within Android Studio Artic Fox.
I have seen the need to write jni compatible C wrapper functions, but have not found how to do this (correctly formed function signatures) or where to put them.
In addition, what do I need to change in the project setup to correctly build the project (using gradle) ?
Note that I have to use directly the pre-built .so file and the public header file which defines the set of public C functions for the library.
There are plenty of examples which give partial outdated information, but still nothing comprehensive - or have I missed something ?
I put together a quick guide below, but I want to clarify how it all fits together first.
In an Android application, you can bind native methods to specially-named functions that are loaded from a native library.
These specially-named functions receive pointers to a JNIEnv struct to interact with the embedding Java application.
The native library is typically built using CMake. Any external dependencies (such as your prebuilt library) need to be made visible to CMake in its CMakeLists.txt. The weapon of choice here are IMPORTED libraries, which are exactly what you think they are.
the steps
First, create an Android project with Kotlin as language.
Right click the app at the top of the tree and select "Add C++ to module" to generate the necessary build stuff.
Change your MainActivity.kt file to be:
class MainActivity : AppCompatActivity() {
external fun doit();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val nativeThread = Thread {
doit()
}
nativeThread.start()
}
}
You will get a build error stating "cannot resolve corresponding JNI function". If you select the quick fix for that, Android Studio will generate a .cpp file with the appropriate JNI wrapper code inside it.
The generated function will look like:
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_doit(JNIEnv *env, jobject thiz) {
// TODO: implement doit()
}
and above that will be instructions on how to load the native library from Kotlin. Copy that code to your MainActivity.kt.
Edit that .cpp file to do whatever you need to do with your native library (eg #include some files and call some functions).
Finally, edit app/src/main/cpp/CMakeLists.txt to point to your headers and precompiled library.
Related
I am working on an Android application that will call a .so file created by a different Android NDK application.
I have created the following folder structure in my project and copied over the .so files as seen below:
|--app:
|--|--src:
|--|--|--main
|--|--|--|--jniLibs
|--|--|--|--|--armeabi
|--|--|--|--|--|--libmylib.so
|--|--|--|--|--x86
|--|--|--|--|--|--libmylib.so
I call this library through my application via the following code:
static {
System.loadLibrary("mylib");
}
I then call the method from this shared object via the following code:
String str = stringFromJNI();
This does not work as the program looks for mangled function name as follows:
com.example.androidlibcall.MainActivity.stringFromJNI() where my .so function will be using a different package name and hence a different function name is generated.
I am not really sure what I need to do to call the functions from the external library, I assume I can create my own library and utilize dlopen() to load the external library and make calls to it, but was wondering if there are the other methods to achieve this or not.
My ultimate goal is to be able to create applications that can call pre-existing libraries that are on the mobile device, but since I am new to NDK/Android I am not sure what is the best method for this and have not found good examples to work with.
A lot of the pre-existing similar questions seem to be dealing with older versions of Android Studio that don't seem applicable anymore.
I am using the latest version of Android Studio (3.1.2) with Gradle 4.4 on Windows 7 machine.
Please advise.
Thanks!
Generally speaking, it's not a good idea to have native methods in application's MainActivity, but this should not worry us now that we are forging a workaround.
Assume that your new project has com.example.other.MainActivity.java, and you want to call the native method com.example.androidlibcall.MainActivity.stringFromJNI() from com.example.other.MainActivity.onCreate(). To do this, create a new Java class in your other app:
package com.example.androidlibcall;
public class MainActivity {
public static native String stringFromJNI();
}
and in your existing MainActivity class,
package com.example.other;
import static com.example.androidlibcall.MainActivity.stringFromJNI;
class MainActivity {
static {
System.loadLibrary("mylib");
}
}
public class MainActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
…
String qq = stringFromJNI();
…
}
}
You see that com/example/androidlibcall/MainActivity.java contains no logic, no data, no code. It is only a wrapper that lets us easily use libmylib.so without rebuilding it.
I have two libraries with same classes defined in each one. However they have some different contents (methods/constants).
For example:
Library 1:
package com.test.package;
Class A {
// only method signatures
public void methodA() {
}
public void methodB() {
}
}
Library 2:
package com.test.package;
Class A {
public void methodA() {
// some logic that MUST be executed to provide backward compatibility
}
}
My application uses Library 1 and Library 2 and run in devices which have com.test.package.ClassA, but com.test.package.ClassA.methodB() will only exist in newer releases in framework. Said that, I need the Library 1 to be used to compile my application and the Library 2 to execute a different implementation of methodA().
I have tried to do this in Android Studio using .jar and .aar libraries format, but none of them worked for me.
Is it possible to set this configuration in an Android Studio project?
I am building both Library 1 and 2, and I cannot add methodB() in Library 2.
For a simple Java application, you can do this by unlinking the compile and runtime configurations. I set up an example repository here.
The idea is shown in this commit, but can be described as manually resetting the runtime configuration so that it doesn't include the contents of the compile configuration. After doing so, you can just include your runtime library variation in the runtime configuration.
The application's build.gradle becomes something like:
apply plugin: 'application'
mainClassName = 'my.package.MyAppClass'
configurations.runtime.extendsFrom = [] // Reset runtime configuration
dependencies {
compile 'my.group:my.artifact:2.0' // Library 1, with the new method
runtime 'my.group:my.artifact:1.0' // Library 2, without the method
}
For Android, this can be a little more complicated. The problem is that there's no runtime configuration for Android (because you don't execute it on your computer, unless you're using Robolectric or something similar).
I think there are a few workarounds you can probably use, but one initial suggestion would be to create a wrapper library that abstracts away the dependency on the other libraries. This wrapper library you can compile with the newest library version (Library 1, with the new method). You could then include the wrapper library in the Android app while setting it as a non-transitive dependency and including the other library version:
dependencies {
compile 'my.group:my.wrapped.artifact:0.1' {
transitive = false // Don't include dependencies of the wrapper
// i.e., don't include version 2.0 of the lib.
}
compile 'my.group:my.artifact:1.0'
}
This should work because by setting the dependency as non-transitive Gradle doesn't recursively include the dependencies of the wrapper library, so the version used to compile the wrapper isn't included (in theory) in the APK. You can therefore add the old version without causing a conflict.
An example is set up in the same repository, under the Android branch. Firstly, two Java libraries are created. Then an Android library is created to wrap around the compile-time library. An example activity is created to show how using the wrapper library uses the compile-time library. Then, the latest commit shows how the app is configured to use the wrapper library (which compiles with the newest library) but forces the old library to be included instead in the runtime.
Hope this helps =)
I wonder why there is no proper answer to this question, I searched for couple of hours but no good answer.
So, we work on a team in which my friend wrote a C library and compiled it as a .so file (it's called ttplib.so)(assume I don't have access to it's C code). Now I have to use that .so file in my android application. But I don't know how to load the library and how to use its methods. I have good documentation of it.
That would be great if you can tell me how to create the Android.mk file too.
Do I have to use dlopen?
Put ttplib.so in the new project's libs/armeabi or libs/armeabi-v7a folder depending what it was compiled with.
Somewhere in your new app (before interacting with the library) add this line of code
System.loadLibrary( "ttplib" );
Now that it's loaded in memory, you'll need to interact with it using JNI. You'll have to go back to the C code to export some JNI functions:
JNIEXPORT jint JNICALL Java_com_example_package_MyClass_methodName( JNIEnv* env, jobject jthis, jfloat value )
{
return 5;
}
Then you'll need to add ClassName.java in your new project:
package com.example.package;
public class MyClass
{
private native int methodName( float value );
private void someJavaMethod()
{
int i = methodName( 65.33f );
}
}
That's it, in a nutshell.
Last 10 years I was only using C#/Java so sorry about my simple questions about c++.
Now I've to add one c++ project to my solution. I do not need to be it standalone application, I need it to do some work and to transfer result to my another c# project. So I've created "Visual C++ / CLR / Class Library".
By default such project doesn't contain too much code. Just this:
// CliProject.h file
#pragma once
using namespace System;
namespace CliProject {
public ref class Class1
{
// TODO: Add your methods for this class here.
};
}
// CliProject.cpp file
#include "stdafx.h"
#include "CliProject.h"
Now for debugging I want to add "main" method so I can launch my library as standalone application. How to do that? Should I create one another class or I should use existent classes?
Create a 'CLR Console Application' project with a reference to your library. Or even better, for debugging, use a unit test framework.
I have a MonoTouch app that dynamically instantiates a class (using Type.GetType()) at runtime. The class is in an assembly that is not referenced anywhere else in the app, so the MonoTouch static compiler thinks that the assembly isn't used and ignores the assembly when it compiles the app. If I add a reference to the class in the app, then the compiler includes the assembly and the call to Type.GetType() works fine:
MyAssembly a;
I would prefer to just tell the compiler to always include all the assemblies listed in the project's "References" when it compiles the app. Is this possible?
Thanks,
-Tom B.
You will have to change your project's Linker behavior from "Link all assemblies" to "Link SDK assemblies only".
The other solution, if you have the project code that assembly was created with, is to mark the class you want to use with the PreserveAttribute.
Were you able to figure this out yet? If not, I had a similar problem: Is there a way to force MonoDevelop to build/load an assembly?
As I understand it, that's just how the C# compiler works. I was able to get around this by adding a custom pre-build step that scripts a class into the referencing assembly that includes dummy references to the unreferenced assemblies, like so:
using System;
namespace MyNamespace
{
public static class Referencer
{
Type t;
//These lines are scripted one per class in the unreferenced assemblies
//You should only need one per assembly, but I don't think more hurts.
t = typeof(Namespace1.Class1);
t = typeof(Namespace2.Class2);
...
t = typeof(NamespaceN.ClassN);
}
}