We are using SWIG to create the JNI API between C++ API to Java in Android.
So for example lets say that our C++ class is:
class TestAnnotation {
public:
void setMessage(char * message);
char * getMessage();
private:
char* message = nullptr;
};
SWIG produces this auto generated Java class:
public class TestAnnotation {
...
public void setMessage(String message) {
myJNI.TestAnnotation_setMessage(swigCPtr, this, message);
}
public String getMessage() {
return myJNI.TestAnnotation_getMessage(swigCPtr, this);
}
}
As you can see, message can be null and it is possible for the get & set methods to receive/return a null String (the JNI auto generated code behaves as expected in this case and allows to either use jstring or null).
My question is whether: SWIG is capable of adding annotations like #Nullable or #NonNull to match the C++ API (if we need to supply "hints" to SWIG, that would also work).
So in this case the desired auto generated Java API will be:
public class TestAnnotation {
...
public void setMessage(#Nullable String message) {
myJNI.TestAnnotation_setMessage(swigCPtr, this, message);
}
#Nullable
public String getMessage() {
return myJNI.TestAnnotation_getMessage(swigCPtr, this);
}
}
This is important as we are using this Java API with Kotlin and the absence of the annotations make it harder to use the Kotlin smart features.
Give your example C++ class in a file called test.h you can generate a Java wrapper with the annotations you wanted using this syntax:
%module test
%javamethodmodifiers TestAnnotation::getMessage() %{
#Nullable
public%};
%typemap(jstype) char *message "#Nullable String";
%include "test.h"
It's a little counter intuitive given the naming focuses on other uses cases, but totally sensible usage nonetheless.
You can of course make the typemap for the inputs less specific by not naming the parameter in it (e.g. %typame(jstype) char * "...").
And you'll want to use another typemap to set up the imports:
%typemap(javaimports) TestAnnotation %{
import android.support.annotation;
%}
Which can be made more generic with:
%typemap(javaimports) SWIGTYPE %{
import android.support.annotation;
%}
Related
I have a C++ library that I have to use in an existing Android implementation. I'm using Android NDK and using the C++ classes via JNI.
However, I am not able to find how to subclass a C++ abstract class in Java using JNI.
Problems I face:
My aim is to provide Java implementation for the virtual methods in C++ by subclassing the abstract C++ class.
I have loaded the native library and I'm trying to declare the native methods.
The C++ methods have keyword 'virtual'. When I declare the native functions in Java after loading the C++ library, 'virtual' is not recognized. What is wrong here?
Any help is appreciated. I'm a newbie to JNI. Thanks in advance.
Let's consider we have a C++ class:
class iVehicle
{
public:
virtual void Run() {}; // not-pure virtual here for simplicity of a wrapper, but could be pure (see the end of the post)
virtual int GetSize() const; // we want to reuse it in Java
};
We want to create a class Bot in Java that extends class iVehicle in the sense that calls to super invoke the C++ code from iVehicle::GetSize() and, from the C++ point of view, we can use the instances of Bot as iVehicle* variables. That's tough since C++ provides no good built-in functionality for reflection.
Here is one possible solution.
To use C++ class in Java we need to generate a Java wrapper, i.e:
class iVehicle
{
public void Run() { Native_Run(); }
public int GetSize() { return Native_GetSize(); }
private native void Native_Run();
private native int Native_GetSize();
// typecasted to pointer in C++
private int NativeObjectHolder;
// create C++ object
native static private int CreateNativeObject();
}
The usage in Java is simple:
class Bot extends iVehicle
{
public int GetSize()
{
if ( condition ) return 0;
// call C++ code
return super.GetSize();
}
}
However, there is a C++ part to this code:
static jfieldID gNativeObjectHolderFieldID;
JNIEXPORT void JNICALL Java_com_test_iVehicle_Run( JNIEnv* env, jobject thiz )
{
int Value = env->GetIntField(thiz, gNativeObjectHolderFieldID);
iVehicle* Obj = (iVehicle*)Obj;
// todo: add checks here, for NULL and for dynamic casting
Obj->Run();
}
The similar code is for GetSize().
Then creating an instance of Java's Bot you have to call CreateNativeObject() and assign the returned value to the NativeObjectHolder field.
JNIEXPORT int JNICALL Java_com_test_iVehicle_CreateNativeObject( JNIEnv* env, jobject thiz )
{
iVehicle* Obj = new iVehicle;
return (int)Obj;
}
So, this is the scheme. To make this work you will need to add the destruction code and to parse C++ classes to generate all this glue code.
Added:
In case where iVehicle is actually abstract you will have to generate a non-abstract wrapper that you are able to instantiate:
class iVehicle
{
virtual void Run() = 0;
}
class iVehicle_Wrapper: public iVehicle
{
virtual void Run() { ERROR("Abstract method called"); };
}
And instantiate iVehicle_Wrapper in CreateNativeObject(). Vuala! You have inherited an abstract C++ class in Java.
The following code will terminate during calling c_func() on Java. It does not occur if tuple is not used. I guess JNA does not support C++11 because tuple type is introduced on it. Is my guess correct?
C++ code
#include <tuple>
#include <map>
int c_func(){
static std::map< std::tuple<float, float, float>, float> mapOfTuple;
return 1;
}
Java code
...
public interface CLibrary extends Library {
public static final String JNA_LIBRARY_NAME = "test";
...
}
public static void main(String[] args) {
c_func();
}
JNA works at the interface level, not the implementation level. It doesn't even know that you used C++11 in c_func. However, your C++ function probably has a dependency on a C++11 runtime library, which may be the problem.
I have some code in C and one of the method has a function pointer as argument. I'm trying to use the C code in my Android app.
I decided to use SWIG to do all the work in generating the java files I need. Everything works well with regular function (the one that doesn't have a function pointer as argument).
But I'm not sure how I can pass my JAVA method as a callback to the C function.
Here is an example :
here is my multiply.h file
typedef int (*callback_t) (int a, int b, int c);
int foo(callback_t callback);
here is my multiply.c file
#include <stdio.h>
#include "multiply.h"
int foo(callback_t callback)
{
return callback(2, 4, 6);
}
here is my interface file multiply-swig.i
%module example
%{
/* Includes the header in the wrapper code */
#include "multiply.h"
%}
/* Parse the header file to generate wrappers */
%include "multiply.h"
then I ran the following swig command to generate the java files I need
swig -java -package com.example.ndktest -o multiply-wrap.c mulitiply-swig.i
and then swig generated the following files :
example.java
package com.example.ndktest;
public class example {
public static int foo(SWIGTYPE_p_f_int_int_int__int callback) {
return exampleJNI.foo(SWIGTYPE_p_f_int_int_int__int.getCPtr(callback));
}
}
exampleJNI.java
package com.example.ndktest;
public class exampleJNI {
public final static native int foo(long jarg1);
}
SWIGTYPE_p_f_int_int_int__int.java
package com.example.ndktest;
public class SWIGTYPE_p_f_int_int_int__int {
private long swigCPtr;
protected SWIGTYPE_p_f_int_int_int__int(long cPtr, boolean futureUse) {
swigCPtr = cPtr;
}
protected SWIGTYPE_p_f_int_int_int__int() {
swigCPtr = 0;
}
protected static long getCPtr(SWIGTYPE_p_f_int_int_int__int obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}
}
Now how can I call this foo method from my java code? The argument is of type SWIGTYPE_p_f_int_int_int__int ??? I don't understand how I can pass a JAVA method as callback to the C code ... I think I am definitely missing something here ....
Any help is appreciated, thanks
Obviously, you cannot pass a single method as an argument. But callbacks are still possible with "Director" feature via implementing callback in an interface. See this example.
I'm having difficulty getting the SWIG typemap(javapackage) to work properly. I tried making a simple version of the problem, and even that seems to fail.
foo.h:
#ifndef FOO_H
#define FOO_H
class Foo
{
public:
Foo() {};
int doSomething() { return 1 };
};
#endif
bar.h:
#ifndef BAR_H
#define BAR_H
#include "foo.h"
class Bar
{
public:
Bar() {};
int doSomething(Foo foo) { return foo.doSomething(); };
};
#endif
Foo.i
%module FooMod
%include "typemaps.i"
%include "stdint.i"
%{
#include "../header/foo.h"
%}
%include "../header/foo.h"
Bar.i
%module BarMod
%import "Foo.i"
%typemap("javapackage") Foo, Foo *, Foo & "com.me.t.foo";
%include "typemaps.i"
%include "stdint.i"
%{
#include "../header/bar.h"
%}
%include "../header/bar.h"
Running these with this the following commands:
swig -c++ -java -package com.me.t.foo -outdir ../../src/com/me/t/foo -o ../src/Foo.cpp Foo.i
swig -c++ -java -package com.me.t.bar -outdir ../../src/com/me/t/bar -o ../src/Bar.cpp Bar.i
And I get this output:
package com.me.t.bar;
public class Bar {
private long swigCPtr;
protected boolean swigCMemOwn;
protected Bar(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}
protected static long getCPtr(Bar obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}
protected void finalize() {
delete();
}
public synchronized void delete() {
if (swigCPtr != 0) {
if (swigCMemOwn) {
swigCMemOwn = false;
BarModJNI.delete_Bar(swigCPtr);
}
swigCPtr = 0;
}
}
public Bar() {
this(BarModJNI.new_Bar(), true);
}
public int doSomething(Foo foo) {
return BarModJNI.Bar_doSomething(swigCPtr, this, Foo.getCPtr(foo), foo);
}
}
BarModJNI.java:
package com.me.t.bar;
public class BarModJNI {
public final static native long new_Bar();
public final static native int Bar_doSomething(long jarg1, Bar jarg1_, long jarg2, Foo jarg2_);
public final static native long Bar_getFoo(long jarg1, Bar jarg1_);
public final static native void delete_Bar(long jarg1);
}
The files are generated properly, but notice that there is no import statement, so Foo can't be found from either of the Bar Java classes. This is a simple example, but just hard-coding an import statement isn't an option for me since the generated source files containing the C JNI code might have the wrong locations of the "Foo" class files.
This seems like a very simple and common problem, so, what I'm wondering is if I'm missing something or if I'm doing something wrong.
Thanks for the help!
Got the same problem and found answer, so posting it for community.
You need to make 3 changes.
Add import statements to generated proxy class (Bar.java):
// Bar.i
%pragma(java) jniclassimports=%{
import com.me.t.foo.Foo;
%}
Add import statements to generated JNI wrapper class (BarModJNI.java):
// Bar.i
%typemap(javaimports) Bar %{
import com.me.t.foo.Foo;
%}
Tell SWIG to make Foo.getCPtr a public member variable because Bar class will want to access it:
// Foo.i
SWIG_JAVABODY_PROXY(public, public, SWIGTYPE)
SWIG_JAVABODY_TYPEWRAPPER(public, public, public, SWIGTYPE)
Reference:
http://www.swig.org/Doc2.0/SWIGDocumentation.html#Java_code_typemaps
http://www.swig.org/Doc2.0/SWIGDocumentation.html#Java_imclass_pragmas
Just a couple of comments on the answer provided by Zbigniew. I have faced the same problem described in this post. I would like to clarify two points.
Firstly, it seems to me that step 1 is for adding the import in JNI wrapper class (whateverJNI.java) while step 2 is for adding import to the specific class.
Secondly, step 2 is not working for me, instead of %typemap(javaimports) <class> I had to use %typemap(javaimports) SWIGTYPE. Bad thing is it adds the imports to all the generated java classes and not only to the desired one.
Finally, I am still having the problem that SWIG does not recognize the imported class when wrapping the specific type and it still uses SWIGTYPE_<type> instead of directly <type>
I have faced the same issue.
I solved by writing (You need to write inside .i file)
%typemap(javaimports) namespace::classname
%{
import com.yourcompany.yourapp.*;
%}
This code generates import statement only the class that you have specified.
CAUTION: You must specify the native namespace, otherwise your class will not be seen!
Example:
If your C++ Code would be like this:
namespace A{
class MyPerfectClass{
};
}
You need to write
%typemap(javaimports) A::MyPerfectClass
%{
import com.yourcompany.yourapp.*;
%}
If you want to add this import statement to all java wrapped classes, you need to write
%typemap(javaimports) SWIGTYPE
%{
import com.yourcompany.yourapp.*;
%}
For JNI Java Class:
%pragma(java) jniclassimports=%{
import com.yourcompany.yourapp.*;
%}
The proof code is here:
package com.mycompany.myproject.A_package;
import com.mycompany.myproject.B_package.*;
public class MYCLASS{
private transient long swigCPtr;
protected transient boolean swigCMemOwn;
public MYCLASS(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}
public static long getCPtr(MYCLASSobj) {
return (obj == null) ? 0 : obj.swigCPtr;
}
.
. etc.
If you have more than one class, you can continue like:
%typemap(javaimports) A::MyPerfectClass , A::B::MyFantasticClass , MyNoNamespaceClass
I've tested with SWIG 4.0 and it works.
I've changed package and class names on proof code because I cannot share my company and class name. I hope this answer would be helpful for others who will have the same issue.
I'm using SWIG to make a Java wrapper of a C++ library (about Json (de)serialization) to use it on Android. I defined an abstract class in C++, representing an object which can be (de)serialized :
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
Now, I'm trying to generate from this class a Java interface. Here's my SWIG interface:
%module JsonSerializable
%{
#include "JsonSerializable.hpp"
%}
%import "JsonValue.i"
class IJsonSerializable {
public:
virtual void serialize(Value &root) = 0;
virtual void deserialize(Value &root) = 0;
};
But the generated Java code is (obviously, as I was not able to find out how to tell SWIG that's an interface) a simple class, with the two methods and a default constructor/destructor:
public class IJsonSerializable {
private long swigCPtr;
protected boolean swigCMemOwn;
public IJsonSerializable(long cPtr, boolean cMemoryOwn) {
swigCMemOwn = cMemoryOwn;
swigCPtr = cPtr;
}
public static long getCPtr(IJsonSerializable obj) {
return (obj == null) ? 0 : obj.swigCPtr;
}
protected void finalize() {
delete();
}
public synchronized void delete() {
if (swigCPtr != 0) {
if (swigCMemOwn) {
swigCMemOwn = false;
JsonSerializableJNI.delete_IJsonSerializable(swigCPtr);
}
swigCPtr = 0;
}
}
public void serialize(Value root) {
JsonSerializableJNI.IJsonSerializable_serialize(swigCPtr, this, Value.getCPtr(root), root);
}
public void deserialize(Value root) {
JsonSerializableJNI.IJsonSerializable_deserialize(swigCPtr, this, Value.getCPtr(root), root);
}
}
How can I generate a valid interface with SWIG ?
You can achieve what you're looking for with SWIG+Java using "Directors", however it's not quite as straightforward mapping from the C++ abstract classes onto Java as you might hope. My answer therefore is split into three parts - firstly the simple example of implementing a C++ pure virtual function in Java, secondly an explanation of why the output is like that and thirdly a "work-around".
Implementing a C++ interface in Java
Given a header file (module.hh):
#include <string>
#include <iosfwd>
class Interface {
public:
virtual std::string foo() const = 0;
virtual ~Interface() {}
};
inline void bar(const Interface& intf) {
std::cout << intf.foo() << std::endl;
}
We'd like to wrap this and make it work intuitively from the Java side. We can do this by defining the following SWIG interface:
%module(directors="1") test
%{
#include <iostream>
#include "module.hh"
%}
%feature("director") Interface;
%include "std_string.i"
%include "module.hh"
%pragma(java) jniclasscode=%{
static {
try {
System.loadLibrary("module");
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load. \n" + e);
System.exit(1);
}
}
%}
Here we've enabled directors for the whole module, and then requested they be used for class Interface specifically. Other than that and my favourite "load the shared object automatically" code there's nothing particularly noteworthy. We can test this with the following Java class:
public class Run extends Interface {
public static void main(String[] argv) {
test.bar(new Run());
}
public String foo() {
return "Hello from Java!";
}
}
We can then run this and see it's working as expected:
ajw#rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!
If you're happy with it being neither abstract nor an interface you can stop reading here, directors do everything you need.
Why does SWIG generate a class instead of an interface?
SWIG has however made what looked like an abstract class into a concrete one. That means on the Java side we could legally write new Interface();, which makes no sense. Why does SWIG do this? The class isn't even abstract, let alone an interface (See point 4 here), which would feel more natural on the Java side. The answer is twofold:
SWIG supplies mechanics for calling delete, manipulating the cPtr etc. on the Java side. That couldn't be done in an interface at all.
Consider the case where we wrapped the following function:
Interface *find_interface();
Here SWIG knows nothing more about the return type than that it's of type Interface. In an ideal world it would know what the derived type is, but from the function signature alone there's no way for it to figure this out. This means that in the generated Java somewhere there's going to have to be a call to new Interface, which wouldn't be possible/legal if Interface were abstract on the Java side.
Possible workaround
If you were hoping to provide this as an interface in order to express a type hierarchy with multiple inheritance in Java this would be quite limiting. There's a workaround however:
Manually write the interface as a proper Java interface:
public interface Interface {
public String foo();
}
Modify the SWIG interface file:
Rename the C++ class Interface to be NativeInterface on the Java side. (We ought to make it visible only to the package in question too, with our wrapped code living in a package of its own to avoid people doing "crazy" things.
Everywhere we have an Interface in C++ code SWIG will now be using NativeInterface as the type on the Java side. We need typemaps to map this NativeInterface in function parameters onto the Interface Java interface we added manually.
Mark NativeInterface as implementing Interface to make the Java side behaviour natural and believable to a Java user.
We need to supply a little bit of extra code that can act as a proxy for things which implement the Java Interface without being a NativeInterface too.
What we pass to C++ must always be a NativeInterface still, not all Interfaces will be one though (although all NativeInterfaces will), so we provide some glue to make Interfaces behave as NativeInterfaces, and a typemap to apply that glue. (See this document for a discussion of the pgcppname)
This results in a module file that now looks like:
%module(directors="1") test
%{
#include <iostream>
#include "module.hh"
%}
%feature("director") Interface;
%include "std_string.i"
// (2.1)
%rename(NativeInterface) Interface;
// (2.2)
%typemap(jstype) const Interface& "Interface";
// (2.3)
%typemap(javainterfaces) Interface "Interface"
// (2.5)
%typemap(javain,pgcppname="n",
pre=" NativeInterface n = makeNative($javainput);")
const Interface& "NativeInterface.getCPtr(n)"
%include "module.hh"
%pragma(java) modulecode=%{
// (2.4)
private static class NativeInterfaceProxy extends NativeInterface {
private Interface delegate;
public NativeInterfaceProxy(Interface i) {
delegate = i;
}
public String foo() {
return delegate.foo();
}
}
// (2.5)
private static NativeInterface makeNative(Interface i) {
if (i instanceof NativeInterface) {
// If it already *is* a NativeInterface don't bother wrapping it again
return (NativeInterface)i;
}
return new NativeInterfaceProxy(i);
}
%}
Now we can wrap a function like:
// %inline = wrap and define at the same time
%inline %{
const Interface& find_interface(const std::string& key) {
static class TestImpl : public Interface {
virtual std::string foo() const {
return "Hello from C++";
}
} inst;
return inst;
}
%}
and use it like:
import java.util.ArrayList;
public class Run implements Interface {
public static void main(String[] argv) {
ArrayList<Interface> things = new ArrayList<Interface>();
// Implements the interface directly
things.add(new Run());
// NativeInterface implements interface also
things.add(test.find_interface("My lookup key"));
// Will get wrapped in the proxy
test.bar(things.get(0));
// Won't get wrapped because of the instanceOf test
test.bar(things.get(1));
}
public String foo() {
return "Hello from Java!";
}
}
This now runs as you'd hope:
ajw#rapunzel:~/code/scratch/swig/javaintf > java Run
Hello from Java!
Hello from C++
And we've wrapped an abstract class from C++ as an interface in Java exactly as a Java programmer would expect!