Просмотр исходного кода

java: Added "Creating Java Bindings guide" to docs

Signed-off-by: Stefan Andritoiu <stefan.andritoiu@intel.com>
Signed-off-by: Mihai Tudor Panu <mihai.tudor.panu@intel.com>
Stefan Andritoiu 9 лет назад
Родитель
Сommit
af7d1d2611
1 измененных файлов: 400 добавлений и 0 удалений
  1. 400
    0
      docs/creating_java_bindings.md

+ 400
- 0
docs/creating_java_bindings.md Просмотреть файл

@@ -0,0 +1,400 @@
1
+Creating Java Bindings Guide
2
+==============
3
+* [Overview](#overview)
4
+* [Tools of trade](#tools-of-trade)
5
+* [Recommendations for the native API](#recommendations-for-the-native-api)
6
+  * [Pointers](#pointers)
7
+  * [Throwing Exceptions in Java](#throwing-exceptions-in-java)
8
+* [Caveats & Challenges](#caveats--challenges)
9
+  * [Wrapping C arrays with Java arrays](#wrapping-c-arrays-with-java-arrays)
10
+  * [Wrapping unbound C arrays with Java arrays if array is output](#wrapping-unbound-c-arrays-with-java-arrays-if-array-is-output)
11
+  * [Wrapping unbound C arrays with Java arrays if array is input](#wrapping-unbound-c-arrays-with-java-arrays-if-array-is-input)
12
+  * [Implementing callbacks in Java](#implementing-callbacks-in-java)
13
+
14
+
15
+##Overview
16
+
17
+The "Creating Java Bindings Guide" serves as a basic tutorial for using the SWIG software development tool to create 'glue code' required for Java to call into C/C++ code. It contains: guides for dealing with type conversions, exception handling, callbacks; recommendations on how to write/modify the native API to avoid issues on the Java side, and also workarounds for those issues that can't be avoided.
18
+
19
+This guide was created with the [upm](https://github.com/intel-iot-devkit/upm/) and [mraa](https://github.com/intel-iot-devkit/mraa) libraries in mind, and uses examples taken from these sources, but its usage can be extended to any project of creating Java bindings for C/C++ libraries.
20
+
21
+##Tools of trade
22
+
23
+[SWIG General Documentation](http://www.swig.org/Doc3.0/SWIGDocumentation.html)
24
+
25
+[SWIG Java-specific Documentation](http://www.swig.org/Doc3.0/Java.html)
26
+
27
+
28
+##Recommendations for the native API
29
+
30
+###Pointers
31
+As much as possible, avoid passing values/returning values through pointers given as as arguments to methods. As the Java language does not have pointers, SWIG provides a [workaround](http://www.swig.org/Doc3.0/Java.html#Java_tips_techniques) in the typemaps.i library.
32
+
33
+####Alternatives:
34
+1. Functions that read data from a driver, return it through a pointer given as argument, and return a bool value, should be __replaced by__ functions that return the value directly and throw a std::runtime_error if a read error occurs. E.g.:
35
+  ```c++
36
+  /*  
37
+   * Function reads from sensor, places read value in variable bar and  
38
+   * returns true if succesful. Function returns false if read failed.  
39
+   */  
40
+  bool func(int *bar); 
41
+  ```
42
+  __Replaced by:__
43
+  ```c++
44
+  /*  
45
+   * Function reads from sensor and returns read value.  
46
+   * Or throws std::runtime_error if a read error occurs  
47
+   */  
48
+  int func();  
49
+  ```
50
+
51
+2. Functions that return multiple values through pointers, that make sense to be grouped together into an array<sup>1</sup> (e.g. speed values, acceleration values), should be __replaced by__ functions that return a pointer to an array in which the elements are the returned values. Afterwards, [wrap the C array with a Java array](#wrapping-unbound-c-arrays-with-java-arrays-if-array-is-output). E.g.:
52
+  ```c++
53
+  /*  
54
+  * Function returns the acceleration on the three  
55
+  * axis in the given variables.  
56
+  */  
57
+  void getAccel(int *accelX, int *accelY, int *accelZ);  
58
+  ```
59
+
60
+  __Replaced by:__  
61
+  ```c++
62
+  /*  
63
+   * Function returns the acceleration on the three  
64
+   * axis as elements of a 3-element array.  
65
+   */  
66
+  int *getAccel();  
67
+  ```
68
+
69
+3. Functions that return N values through pointers, that do not make sense to grouped together (e.g. a general purpose function that returns both the light intensity and air pollution), should be __replaced by__ N functions (one for each value) that read only one specific value. E.g.:
70
+
71
+  ```c++
72
+  /*  
73
+   * Function returns the light intensity and air pollution  
74
+   */  
75
+  void getData(int *light, int *air);  
76
+  ```
77
+
78
+  __Replaced by:__  
79
+  ```c++
80
+  int getLight();  
81
+  int getAir();  
82
+  ```
83
+
84
+4. Functions that return N values through pointers; values that do not make sense to be grouped together, but are time dependent, and make sense to be read at the same time. For example, a sensor that reads air humidity and temperature. A user may want to know the temperature value _temp_ read at the exact moment the humidity value _humid_ was read. These should be __replaced by__ N+1 functions: a _getData()_ function that reads all values at the same time and stores them in global variables; and N getter functions, one for each value. E.g.
85
+
86
+  ```c++
87
+  /*  
88
+   * Function returns the light intensity and air pollution  
89
+   */  
90
+  void getData(int *temp, int *humid);  
91
+  ```
92
+
93
+  __Replaced by:__  
94
+  ```c++
95
+  void getData();  
96
+  int getTemp();  
97
+  int getHumid();   
98
+  ```
99
+
100
+  <sup>1</sup>this depends on the interpretation of the returned data. For example, arguments that return the temperature and light intensity, don't make sense to be grouped into an array of size 2. But acceleration on the three axis can be grouped together in an array of size 3. where accelX is accel[0], accelY is accel[1], accelZ is accel[2].
101
+
102
+__Notice:__
103
+Sometimes, you may be required to write JNI code. Be aware of the difference between the C JNI calling syntax and the C++ JNI calling syntax.The C++ calling syntax will not compile as C and also vice versa. It is however possible to write JNI calls which will compile under both C and C++ and is covered in the [Typemaps for both C and C++ compilation](http://www.swig.org/Doc3.0/Java.html#Java_typemaps_for_c_and_cpp) section of the SWIG Documentation.
104
+
105
+
106
+###Throwing Exceptions in Java
107
+####Language independent:
108
+The %exception directive allows you to define a general purpose exception handler. For example, you can specify the following:
109
+
110
+```c++
111
+%exception [method_name] {
112
+    try {
113
+        $action
114
+    }
115
+    catch (std::invalid_argument& e) {
116
+        ... handle error ...
117
+    }
118
+}
119
+```
120
+
121
+If [method_name] is not specified then the directive is applied to all methods in its scope.
122
+
123
+The usual thing you'd want to do is catch the C++ exception and throw an equivalent exception in your language.
124
+
125
+The exception.i library file provides support for creating language independent exceptions in your interfaces. To use it, simply put an "%include exception.i" in your interface file. This provides a function SWIG_exception() that can be used to raise common language exceptions in a portable manner. For example :
126
+
127
+
128
+```c++
129
+// Language independent exception handler  
130
+%include exception.i  
131
+
132
+%exception {  
133
+    try {  
134
+            $action  
135
+        } catch(OutOfMemory) {  
136
+            SWIG_exception(SWIG_MemoryError, "Out of memory");  
137
+    } catch(...) {  
138
+            SWIG_exception(SWIG_RuntimeError,"Unknown exception");  
139
+    }  
140
+}  
141
+```
142
+
143
+In the upm library, the upm_exception.i interface file provides the functionality to catch common exceptions and propagate them through SWIG. It uses the exception.i library file and is language independent.
144
+
145
+The upm_exception.i interface file is included in the upm.i file, therefor SWIG wraps all generated methods' body in a try-catch statement for the following exceptions:
146
+
147
+* std::invalid_argument
148
+* std::domain_error
149
+* std::overflow_error
150
+* std::out_of_range
151
+* std::length_error
152
+* std::logic_error
153
+* std::bad_alloc
154
+* std::runtime_error
155
+* std::exception
156
+
157
+
158
+####Java specific:
159
+To throw a specific Java exception:
160
+
161
+```c++
162
+%exception {  
163
+    try {  
164
+        $action  
165
+    } catch (std::out_of_range &e) {  
166
+        jclass clazz = jenv->FindClass("java/lang/Exception");  
167
+        jenv->ThrowNew(clazz, "Range error");  
168
+        return $null;  
169
+    }  
170
+}  
171
+```
172
+
173
+Where FindClass and ThrowNew are [JNI functions](http://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html).
174
+
175
+Java defines two tipes of exceptions: checked exception and unchecked exceptions (errors and runtime exceptions). Checked exceptions are subject to the [Catch or Specify Requirement](https://docs.oracle.com/javase/tutorial/essential/exceptions/catchOrDeclare.html).
176
+
177
+The C++ compiler does not force the code to catch any exception.
178
+
179
+The %exception directive does not specify if a method throws a checked exception (does not add classes to the throws clause). For this, the  %javaexception(classes) directive is used; where classes is a string containing one or more comma separated Java classes.
180
+
181
+```c++
182
+%javaexception("java.lang.Exception") {  
183
+    try {  
184
+        $action  
185
+    } catch (std::out_of_range &e) {  
186
+        jclass clazz = jenv->FindClass("java/lang/Exception");  
187
+        jenv->ThrowNew(clazz, "Range error");  
188
+        return $null;  
189
+    }  
190
+}  
191
+```
192
+
193
+In the upm library, the java_exceptions.i library file provides the functionality to catch exceptions and propagate them through SWIG as Java checked exceptions. The file provides SWIG wrappers, in the form of macros, that can be applied to methods.E.g. use the __READDATA_EXCEPTION(function)__ macro for functions that read data from a sensor and throw a std::runtime_error in case of a read failure. This will result in:
194
+
195
+```java
196
+void function throws IOException ();  
197
+```
198
+
199
+##Caveats & Challenges
200
+
201
+###Wrapping C arrays with Java arrays
202
+SWIG can wrap arrays in a more natural Java manner than the default by using the arrays_java.i library file. Just include this file into your SWIG interface file.
203
+
204
+###Wrapping unbound C arrays with Java arrays if array is output
205
+Functions that return arrays, return a pointer to that array. E.g.:
206
+
207
+```c++
208
+/*  
209
+ * Function returns the acceleration on the three  
210
+ * axis as elements of a 3-element array.  
211
+ */  
212
+
213
+int *getAccel();  
214
+```
215
+
216
+__SWIG:__  
217
+```c++
218
+%typemap(jni) int* "jintArray"  
219
+%typemap(jstype) int* "int[]"  
220
+%typemap(jtype) int* "int[]"  
221
+
222
+%typemap(javaout) int* {  
223
+    return $jnicall;  
224
+}  
225
+
226
+%typemap(out) int *getAccel {  
227
+    $result = JCALL1(NewIntArray, jenv, 3);  
228
+    JCALL4(SetIntArrayRegion, jenv, $result, 0, 3, (const signed int*)$1);  
229
+}  
230
+```
231
+
232
+###Wrapping unbound C arrays with Java arrays if array is input
233
+In C, arrays are tipically passed as pointers, with an integer value representig the length of the array. In Java, the length of an array is always known, so the length argument is redundant. This example shows how to wrap the C array and also get rid the length argument. E.g.:
234
+
235
+```c++
236
+void func(uint8_t *buffer, int length);  
237
+```
238
+
239
+__SWIG:__  
240
+```c++
241
+%typemap(jtype) (uint8_t *buffer, int length) "byte[]"  
242
+%typemap(jstype) (uint8_t *buffer, int length) "byte[]"  
243
+%typemap(jni) (uint8_t *buffer, int length) "jbyteArray"  
244
+%typemap(javain) (uint8_t *buffer, int length) "$javainput"  
245
+
246
+%typemap(in,numinputs=1) (uint8_t *buffer, int length) {  
247
+	$1 = JCALL2(GetByteArrayElements, jenv, $input, NULL);  
248
+	$2 = JCALL1(GetArrayLength, jenv, $input);  
249
+}  
250
+```
251
+
252
+!!!! There is a difference between TYPE *name and TYPE * name in typemaps!!!!!
253
+
254
+
255
+###Implementing callbacks in Java
256
+Method calls from the Java instance are passed to the C++ instance transparently via C wrapper functions. In the default usage of SWIG, this arrangement is asymmetric in the sense that no corresponding mechanism exists to pass method calls down the inheritance chain from C++ to Java. To address this problem, SWIG introduces new classes called directors at the bottom of the C++ inheritance chain. The job of the directors is to route method calls correctly, either to C++ implementations higher in the inheritance chain or to Java implementations lower in the inheritance chain. The upshot is that C++ classes can be extended in Java and from C++ these extensions look exactly like native C++ classes. For more on Java directors, read the ["Cross language polymorphism using directors"](http://www.swig.org/Doc3.0/SWIGDocumentation.html#Java_directors) chapter of the SWIG documentation.
257
+To enable directors, add the "directors" option to the %module directive, and use the %feature("director") directive to tell SWIG which classes and methods should get directors. If only a class is specified, the %feature is applied to all virtual methods in that class. If no class or method is specified, the %feature is applied globally to all virtual methods.
258
+
259
+```
260
+%module(directors="1") modulename
261
+%feature("director") IsrCallback;
262
+```
263
+
264
+__Callbacks in the UPM Java library are implemented as follows (we use the a110x Hall Effect sensors as example):__
265
+
266
+We create a new C++ class, that will be wrapped by SWIG, called _IsrCallback_; and a method _generic\_callback\_isr(void\* data)_ that takes an instance of IsrCallback as argument.
267
+
268
+```c++
269
+#if defined(SWIGJAVA)
270
+class IsrCallback
271
+{
272
+    public:
273
+        virtual ~IsrCallback()
274
+        {
275
+        }
276
+        virtual void run()
277
+        { /* empty, overridden in Java*/
278
+        }
279
+
280
+    private:
281
+};
282
+
283
+static void generic_callback_isr (void* data)
284
+{
285
+    IsrCallback* callback = (IsrCallback*) data;
286
+    if (callback == NULL)
287
+        return;
288
+    callback->run();
289
+}
290
+#endif
291
+```
292
+
293
+SWIGJAVA is a symbol that is always defined by SWIG when using Java. We enclose the _IsrCallback_ class and _generic\_callback\_isr()_ in a _\#if defined(SWIGJAVA)_ check, to ensure the code only exists when creating a wrapper for Java.
294
+We extend the sensor class with another method, _installISR\(IsrCallback \*cb\)_, which is a wrapper over the original _installISR\(void \(\*isr\)\(void \*\), void \*arg\)_ method. This will install the _generic\_callback\_isr\(\)_ method as the interrupt service routine \(ISR\) to be called, with the _IsrCallback_ object (referenced by *cb) as argument.
295
+
296
+```c++
297
+#if defined(SWIGJAVA)
298
+void A110X::installISR( IsrCallback *cb)
299
+{
300
+    installISR(generic_callback_isr, cb);
301
+}
302
+#endif
303
+```
304
+
305
+We hide the underlying method, _installISR\(void \(\*isr\)\(void \*\), void \*arg\)_ , and expose only the _installISR\(IsrCallback \*cb\)_ to SWIG, through the use of the SWIGJAVA symbol. When SWIGJAVA is defined, we change the access modifier of the underlying method to private.
306
+
307
+```c++
308
+public:
309
+#if defined(SWIGJAVA)
310
+    void installISR(IsrCallback *cb);
311
+#else
312
+    void installISR(void (*isr)(void *), void *arg);
313
+#endif
314
+private:
315
+#if defined(SWIGJAVA)
316
+    void installISR(void (*isr)(void *), void *arg);
317
+#endif
318
+```
319
+
320
+To use callback in java, we create a ISR class, which extends the new IsrCallback class created by SWIG, and we override the _run\(\)_ method with the code to be executed when the interrupt is received. An example for the a110x Hall sensor that increments a counter each time an interrupt is received:
321
+
322
+```java
323
+public class A110X_intrSample {
324
+    public static int counter=0;
325
+        
326
+    public static void main(String[] args) throws InterruptedException {
327
+        upm_a110x.A110X hall = new upm_a110x.A110X(2);
328
+
329
+        IsrCallback callback = new A110XISR();
330
+        hall.installISR(callback);
331
+        
332
+        while(true){
333
+            System.out.println("Counter: " + counter);
334
+            Thread.sleep(1000);
335
+        }
336
+    }
337
+}
338
+
339
+class A110XISR extends IsrCallback {
340
+    public A110XISR(){
341
+        super();
342
+    }
343
+    public void run(){
344
+        A110X_intrSample.counter++;
345
+    }
346
+}
347
+```
348
+####Issues with java callbacks and workarounds
349
+
350
+__SWIGJAVA not defined at compile time__
351
+
352
+Consider the following files:
353
+* example.h - Header file for our source file
354
+* example.cxx - Source file containing the class Example, for which we build java bindings
355
+* example.i - The SWIG interface, that includes the example.h header file
356
+
357
+The build process of a java module using SWIG is split into two steps:
358
+
359
+1. Generating the intermediate files, from the SWIG interface file. This will produce the java class file (Example.java), the JNI file (exampleJNI.java) and wrapper file (example_wrap.cxx). The source file (example.cxx) is not needed in the first step.
360
+
361
+  ```
362
+swig -c++ -java example.i
363
+  ```
364
+
365
+2. Generating the shared library from the C++ sources and wrapper file
366
+  ```
367
+g++ -fPIC -c example.cxx example_wrap.cxx -I/usr/lib/jvm/java-1.8.0/include -I/usr/lib/jvm/java-1.8.0/include/linux
368
+g++ -shared example_wrap.o sensor.o -o libexample.so
369
+  ```
370
+
371
+
372
+SWIGJAVA is always defined when SWIG parses the interface file, meaning it will be defined when it parses the header file (example.h) that is included in the interface file (example.i).
373
+SWIG also adds the "#define SWIGJAVA" directive in the wrapper file (example_wrap.cxx).
374
+However, in generating the shared library the SWIGJAVA symbol is only defined in the example_wrap.cxx file, because of the added "#define SWIGJAVA" directive. But we have also used the "#if defined(SWIGJAVA)" check in the source file (example.cxx), and thus need to define SWIGJAVA for it too. If we define the SWIGJAVA symbol as a compile flag, when compiling the source code to object code, the SWIGJAVA compile flag and #define SWIGJAVA" directive will clash and give a double definition warning (only a warning).
375
+
376
+In this example it is simple to compile the two source codes separately, one with the compile flag, the other without, and then create the shared library (libexample.so).
377
+But in a big automatic build like the java upm libraries, this may prove too hard or too complicated to do. A workaround to this would be to define a custom symbol (e.q. JAVACALLBACK in the upm library) and also test for it. In short, replace:
378
+```c++
379
+#if defined(SWIGJAVA)
380
+```
381
+by
382
+```c++
383
+#if defined(SWIGJAVA) || defined(JAVACALLBACK)
384
+```
385
+
386
+__Use GlobalRef instead of WeakRef__
387
+
388
+By default, SWIG uses WeakRef to the Java objects handled by a director. Once Java objects run out of scope they can get cleaned up. In many cases WeakRef is undesirable as we may still want to be able to run methods or access fields in the objects passed to the JNI layer (e.g. we want to pass a runnable-like object which should be called when something happens). To use GlobalRefs instead, the following line must be added after the director declaration:
389
+
390
+```
391
+SWIG_DIRECTOR_OWNED(module)
392
+```
393
+
394
+For, example, in case of a module called IsrCallback (used for interrupts in MRAA and UPM), we would declare said module as such:
395
+
396
+```
397
+%feature ("director") IsrCallback;
398
+SWIG_DIRECTOR_OWNED(IsrCallback)
399
+```
400
+