Auto-Generate JNI Code Using SWIG for Android

《Pro Android C++ with the NDK》第4章 Copyright © 2012 by Onur Cinar

Chapter 4: Auto-Generate JNI Code Using SWIG

In the previous chapter you explored JNI technology and you learned how to connect native code to a Java application. As noted, implementing JNI wrapper code and handling the translation of data types is a cumbersome and time-consuming development task. This chapter will introduce the Simplified Wrapper and Interface Generator (SWIG), a development tool that can simplify this process by automatically generating the necessary JNI wrapper code.

SWIG is not an Android- and Java-only tool. It is a highly extensive tool that can generate code in many other programming languages. As SWIG is rather large, this chapter will only cover the following key SWIG concepts and APIs that will get you started:

n Defining a SWIG interface for native code.

n Generating JNI code based on the interface.

n Integrating SWIG into the Android Build process.

n Wrapping C and C++ code.

n Exception handling.

n Using memory management.

n Calling Java from native code. As SWIG simplifies the development of JNI code, you will be using SWIG often in the next chapters.

What is SWIG?

SWIG is a compile-time software development tool that can produce the code necessary to connect native modules written in C/C++ with other programming languages, including Java. SWIG is an interface compiler, merely a code generator; it does not define a new protocol nor is it a component

framework or a specialized runtime library. SWIG takes an interface file as its input and produces the necessary code to expose that interface in Java. SWIG is not a stub generator; it produces code that is ready to be compiled and run.

SWIG was originally developed in 1995 for scientific applications; it has since evolved into a general-purpose tool that is distributed under GNU GPL open source license. More information about SWIG can be found at www.swig.org.

Installation

SWIG works on all major platforms, including Windows, Mac OS X, and Linux. At the time of this

www.swig.org. The binaries for SWIG, except the Windows

. As shown in Figure 4-1, click on the link to download the SWIG

Figure 4-1. SWIG for Windows download link

clip_image002

The SWIG installation package comes as a ZIP archive file. The Windows OS comes with native support for ZIP format. When the download completes, right-click on the ZIP file and choose Extract All from the context menu to launch the Extract Compressed Folder wizard. Using the

Browse button, choose the destination directory where you want the extracted SWIG files to go. As mentioned, the C:\android directory is used as the root directory to hold the development tools. Select C:\android as the destination directory. A dedicated empty destination directory is not needed since the ZIP file already contains a sub directory called swigwin-2.0.7 to hold the SWIG. Click the Extract button to start the installation process.

Similar to the other development tools that you have installed, in order to have SWIG easily reachable, its installation directory should be appended to system executable search path. Launch the Environment Variables dialog from System Properties, and click the New button. As shown in Figure 4-2, using the New System Variable, set the variable name to SWIG_HOME and the variable value to the SWIG installation directory, such as C:\android\swigwin-2.0.7.

Figure 4-2. New SWIG_HOME environment variable

clip_image004

From the list of system variables, double-click on the PATH variable, and append ;%SWIG_HOME% to the variable value, as shown in Figure 4-3.

Figure 4-3. Appending SWIG binary path to system PATH variable

clip_image006

If the installation was successful, you will see the SWIG version number, as shown in Figure 4-4.

Figure 4-4. Validating SWIG installation

clip_image008

Installing on Mac OS X

The SWIG web site does not provide an installation package for Mac OS X platform. A Homebrew package manager will be used to download and install SWIG. In order to use Homebrew, it needs to be installed on the host machine as well. Homebrew comes with a console-based installation application. Copy the install command from the Homebrew installation page, as shown in Figure 4-5.

Figure 4-5. Installation command for Homebrew

clip_image010

Open a Terminal window. At the command prompt, paste the install command, as shown in Figure 4-6, and press the Enter key to start the installation process. The command will first download the Homebrew installation script and it will execute it using Ruby. Follow the on-screen instructions to complete the installation process.

Figure 4-6. Installing Homebrew from command line

clip_image012

Upon completing the installation of Homebrew, SWIG can now be installed. Using the Terminal window, execute brew install swig on the command prompt. As shown in Figure 4-7, Homebrew will download the source code of SWIG and its dependencies, and then it will compile and install it automatically.

Figure 4-7. Installing SWIG using Homebrew

clip_image014

In order to validate the installation, open a new Terminal window and execute swig -version on the command line. If the installation was successful, you will see the SWIG version number, as shown in Figure 4-8.

Figure 4-8. Validating the SWIG installation

clip_image016

Installing on Ubuntu Linux

The SWIG web site does not provide an installation package for Linux flavors. The Ubuntu Linux software repository contains the latest version of SWIG, and it can be installed using the system package manager. Again, open a Terminal window. At the command prompt, execute sudo apt-get install swig, as shown in Figure 4-9. The system package manager will download and install SWIG and its dependencies automatically.

clip_image018

Installing SWIG from command line

execute swig -version on the command line. If the installation was successful, you will see the SWIG version number, as shown in Figure 4-10.

Figure 4-10. Validating SWIG installation

clip_image020

Experimenting with SWIG Through an Example

Before learning the details of SWIG, you will walk through an example application to better understand how SWIG works. The Android platform is built on top of the Linux OS, a multiuser platform. It runs the applications within a virtual machine sandbox and treats them as different users on the system to keep the platform secure. On Linux, each user gets assigned a user ID, and this user ID can be queried by using the POSIX OS API getuid function. As a platform-independent

programming language, Java does not provide access to these functions. As a part of this example application, you will be

n Writing a SWIG interface file to expose the getuid function.

n Integrating SWIG into the Android build process.

n Adding SWIG-generated source files into the Android.mk build file.

n Use the SWIG-generated proxy classes to query the getuid.

n Display the result on the screen. You will be using the hello-jni sample project as a testbed. Open Eclipse IDE, and go into the hello-jni project. As mentioned earlier, SWIG operates on interface files.

Interface File

SWIG interface files contain function prototypes, classes, and variable declarations. Its syntax is the same as any ordinary C/C++ header file. In addition to C/C++ keywords and preprocessor directives, the interface files can also contain SWIG specific preprocessor directives that can enable tuning the generated wrapper code.

In order to expose getuid, an interface file needs to be defined. Using the Project Explorer, right-click on jni directory under the hello-jni project, and choose New File from the context menu to launch the New File dialog. As shown in Figure 4-11, set file name to Unix.i and click the Finish button.

Figure 4-11. Creating the Unix.i SWIG interface file

clip_image022

Using the Editor view, populate the content of Unix.i, as shown in Listing 4-1.

Listing 4-1. Unix.i Interface File Content

/* Module name is Unix. */ %module Unix

%{ /* Include the POSIX operating system APIs. */ #include<unistd.h> %}

They are merely for annotating the interface files for developers.

Listing 4-2. Unix.i Starting with a Comment

/* Module name is Unix. */ ...

Module Name

Each invocation of SWIG requires a module name to be specified. The module name is used to name the resulting wrapper files. The module name is specified using the SWIG specific preprocessor directive %module, and it should appear at the beginning of every interface file. Complying with this rule, the Unix.i interface file also starts by defining its module name as Unix, as shown in Listing 4-3.

Listing 4-3. Unix.i Defining Its Module Name

%module Unix

User-Defined Code

SWIG only uses the interface file while generating the wrapper code; its content does not go beyond this point. It is often necessary to include user-defined code in the generated files, such as any header file that is required to compile the generated code. When SWIG generates a wrapper code, it is broken up to into five sections. SWIG provides preprocessor directives to enable developers to

specify the code snippets that should be included in any of these five sections. The syntax for SWIG preprocessor directive is shown in Listing 4-4.

Listing 4-4. Syntax for SWIG insert preprocessor directive

%<section>%{ ... This code block will be included into generated code as is. ...

%} ...

The<section>portion can take following values:

n begin: Places the code block at the beginning of the generated wrapper file. It is mostly used for defining preprocessor macros that are used in the later part of the file.

n runtime: Places the code block next to SWIG’s internal type-checking and other support functions.

n header: Places the code block into the header section next to header files and other helper functions. This is the default place to inject code into the generated files. The %{ ... %} can also be used the short form.

n wrapper: Places the code block next to generated wrapper functions.

n init: Places the code block into to function that will initialize the module upon loading.

As shown in Listing 4-5, the Unix.i interface file injects a header file into the generated wrapper code using the short form of insert header preprocessor directive.

Listing 4-5. Unix.i Inserting a Header into Generated Wrapper Code

%{ /* Include the POSIX operating system APIs. */ #include<unistd.h> %}

Type Definitions

SWIG can understand all C/C++ data types but treats anything else as objects and wraps them as pointers. The declaration of the getuid function suggests that its return type is uid_t, which is not a standard C/C++ data type. As is, SWIG will treat it as an object, and it will wrap it as a pointer. This is not the preferred behavior since uid_t is nothing more than a simple typedef-name based on unsigned integer, not an object. As shown in Listing 4-6, the Unix.i interface file uses a typedef to inform SWIG about the actual return type of getuid function.

Listing 4-6. Type Definition for uid_t

/* Tell SWIG about uid_t. */ typedef unsigned int uid_t;

Function Prototypes

The Unix.i interface file ends with the function prototype for the getuid function as shown in Listing 4-7.

Listing 4-7. Function Prototype for getuid

/* Ask SWIG to wrap getuid function. */

getuid function to Java. SWIG will generate two sets of files: wrapper C/C++ code to expose

Java Package for Proxy Classes

The Java package directory should be created in advance of invoking SWIG. Using the Project Explorer, right-click on the src directory and select New Package to launch the New Java Package dialog. As shown in Figure 4-12, set the package name to com.apress.swig and click the Finish button.

Figure 4-12. Java package for SWIG files

clip_image024

Invoking SWIG

You are now ready to invoke SWIG. Open a Terminal window or a command prompt, and go in to the directory where the hello-jni project is imported, such as C:\android\workspace\com.example. hellojni.HelloJni. As shown in Figure 4-13, execute swig -java -package com.apress.swig -outdir src/com/apress/swig jni/Unix.i on the command line.

Figure 4-13. Invoking SWIG on the command line

clip_image026

SWIG parses the Unix.i interface file and generates the Unix_wrap.c C/C++ wrapper code in the jni directory plus the UnixJNI.java and Unix.java Java proxy classes in the com.apress.swig package.

Before starting to explore these files, let’s streamline the process. SWIG can be integrated into the Android build process, instead of being manually executed from the command line.

Integrating SWIG into Android Build Process

In order to integrate SWIG into Android build process, you will define a Makefile fragment.

Android Build System Fragment for SWIG

Using the Project Explorer, right-click the jni directory and choose New File from the menu. Using the New File dialog, create a file named my-swig-generate.mk. The contents of this Makefile fragment are shown in Listing 4-8.

Listing 4-8. Contents of my-swig-generate.mk File

# # SWIG extension for Android build system. # # @author Onur Cinar #

# Check if the MY_SWIG_PACKAGE is defined ifndef MY_SWIG_PACKAGE

$(error MY_SWIG_PACKAGE is not defined.) endif

# Replace dots with slashes for the Java directory MY_SWIG_OUTDIR := $(NDK_PROJECT_PATH)/src/$(subst .,/,$(MY_SWIG_PACKAGE))

# Default SWIG type is C ifndef MY_SWIG_TYPE MY_SWIG_TYPE := c endif

# Set SWIG mode ifeq ($(MY_SWIG_TYPE),cxx) MY_SWIG_MODE :=−c++ else MY_SWIG_MODE := endif

# Append SWIG wrapper source files

+= $(foreach MY_SWIG_INTERFACE,\ $(MY_SWIG_INTERFACES),\ $(basename $(MY_SWIG_INTERFACE))_wrap.$(MY_SWIG_TYPE))

+= .cxx

$(call host-mkdir,$(MY_SWIG_OUTDIR)) swig -java \ $(MY_SWIG_MODE) \ -package $(MY_SWIG_PACKAGE) \ -outdir $(MY_SWIG_OUTDIR) \ $<

Integrating SWIG into Android.mk

In order to use this build system fragment, the existing Android.mk file needs to be modified. The build system fragment requires three new variables to be defined in Android.mk file in order to operate:

n MY_SWIG_PACKAGE: Defines the Java package where SWIG will generate the proxy classes. In your example, this will be the com.apress.swig package.

n MY_SWIG_INTERFACES: Lists the SWIG interface file that should be processed. In your example, this will be the Unix.i file.

n MY_SWIG_MODE: Instructs SWIG to generate the wrapper code in either C or C++. In your example, this will be C code.

Using the Project Explorer, expand the jni directory under the project root, and open Android.mk in editor view. Let’s now define these new variables for the project. The additions to the Android.mk file are shown in Listing 4-9 as bold.

Listing 4-9. Defining SWIG Variables in Android.mk file

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c

MY_SWIG_PACKAGE := com.apress.swig MY_SWIG_INTERFACES := Unix.i MY_SWIG_TYPE := c

include $(LOCAL_PATH)/my-swig-generate.mk

include $(BUILD_SHARED_LIBRARY)

After defining these new variables, the Androd.mk file includes the my-swig-generate.mk build system fragment that you defined earlier in this section. The build system fragment first creates the Java package directory and then invokes SWIG by setting the proper parameters based on these variables. This should happen before building the shared library since the wrapper code that will be generated by SWIG should also get compiled into the shared library. The build system fragment automatically appends the generated wrapper files into the LOCAL_SRC_FILES variable.

Choose Project Build All from the top menu to rebuild the current project. As shown in Figure 4-14, the Android NDK build logs indicate that the Unix_wrapper.c wrapper code is getting compiled into the shared library.

Figure 4-14. Build logs showing the wrapper code getting compiled

clip_image028

Updating the Activity

The getuid function is now properly exposed through the Unix proxy Java class. In order to validate it, you will be modifying the HelloJni activity to show its return value on the display. Using the

Project Explorer, expand the src directory and then the com.example.hellojni Java package. Open HelloJni in editor view, and modify the body of onCreate method as shown in Listing 4-10.

Listing 4-10. Invoking getuid Function from Unix Proxy Class

@Override public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

...

TextView tv=new TextView(this);

tv.setText("UID: "+Unix.getuid());

setContentView(tv);

➤ Run from the top menu bar to launch the application. 4-15, activity will call the getuid function and the result will be displayed on the

Figure 4-15. Activity displaying the user ID

clip_image030

As demonstrated with this example, SWIG can automatically generate all of the necessary JNI and Java code to expose a native function to Java.

Exploring Generated Code

In order to make the native function reachable from Java, SWIG has generated two Java classes and one C/C++ wrapper:

n Unix_wrap.c: Contains the JNI wrapper functions to handle the type mapping and to expose the selected native functions to Java. The generated wrapper function is shown in Listing 4-11.

Listing 4-11. Generated Wrapper Function for getuid

SWIGEXPORT jlong JNICALL Java_com_apress_swig_UnixJNI_getuid(JNIEnv *jenv, jclass jcls)

{ jlong jresult = 0 ; uid_t result;

(void)jenv; (void)jcls; result = (uid_t)getuid(); jresult = (jlong)result; return jresult;

}

n UnixJNI.java: Intermediary JNI class containing the Java native function declaration for all functions that are exposed by the wrapper. It is generated in the com.apress.swig Java package as specified in Android.mk file. The generated intermediary JNI class is shown in Listing 4-12.

Listing 4-12. Generated Intermediary JNI Class

package com.apress.swig;

public class UnixJNI { public final static native long getuid(); }

n Unix.java: Module class containing all methods and global variable getter and setters. It wraps the calls in the intermediary JNI class to implement static type checking. You will revisit this subject when you start exploring how SWIG handles the objects. It is generated in com.apress.swig Java package as well. The generated module class is shown in Listing 4-13.

Listing 4-13. Generated Module Class

package com.apress.swig;

public class Unix { public static long getuid() { return UnixJNI.getuid(); } }

Wrapping C Code

In the previous example, you learned how the functions get exposed through SWIG. In this section, you will explore how other components get wrapped by SWIG. Note that the components that are defined in the interface file are merely for SWIG to expose them to Java; they do not get included into the generated files unless they are also declared in insert preprocessor declaration. SWIG assumes that all exposed components are defined elsewhere in the code. If the component is not defined, the build will simply fail during compile-time.

Global Variables

Although there is no such thing as a Java global variable, SWIG does support global variables. SWIG generates getter and setter methods in the module class to provide access to native global variables. In order to expose a global variable to Java, simply add it to the interface file as shown in Listing 4-14.

Listing 4-14. Interface File Exposing the Counter Global Variable

%module Unix ... /* Global counter. */

Getter and Setter Methods for Counter Global Variable

...

public static void setCounter(int value) { UnixJNI.counter_set(value); }

public static int getCounter() { return UnixJNI.counter_get(); } }

Besides the variables, SWIG also provides support for those constants that are associated with a value that cannot be altered during runtime.

Constants

Constants can be defined in the interface file either through #define or %constant preprocessor directives, as shown in Listing 4-16.

Listing 4-16. Interface File Defining Two Constants

%module Unix ... /* Constant using define directive. */ #define MAX_WIDTH 640

/* Constant using %constant directive. */ %constant int MAX_HEIGHT = 320;

SWIG generates a Java interface called<Module>Constant, and the constants are exposed as static final variables on that interface, as shown in Listing 4-17.

Listing 4-17. UnixConstants Interface Exposing Two Constants

package com.apress.swig;

public interface UnixConstants {

public final static int MAX_WIDTH = UnixJNI.MAX_WIDTH_get();

public final static int MAX_HEIGHT = UnixJNI.MAX_HEIGHT_get(); }

By default SWIG generates runtime constants. The values of the constants are initialized by making JNI function calls to the native code at runtime. This can be changed by using the %javaconst preprocessor directive in interface file, as shown in Listing 4-18.

Listing 4-18. Instructions to Generate Compile-time Constant for MAX_WIDTH

%module Unix ... /* Constant using define directive. */

%javaconst(1);

#define MAX_WIDTH 640

/* Constant using %constant directive. */

%javaconst(0);

%constant int MAX_HEIGHT = 320;

This preprocessor directive instructs SWIG to generate a compile-time constant for MAX_WIDTH and a run-time constant for MAX_HEIGHT. The Java constants interface now looks like Listing 4-19.

Listing 4-19. UnixConstants Interface Exposing the Compile-time Constant

package com.apress.swig;

public interface UnixConstants {

public final static int MAX_WIDTH = 640;

public final static int MAX_HEIGHT = UnixJNI.MAX_HEIGHT_get(); }

In certain situation you may want to limit the write access on a variable and expose it as read-only to Java.

Read-Only Variables

SWIG provides the %immutable preprocessor directive to mark a variable as read-only, as shown in Listing 4-20.

Listing 4-20. Enabling and Disabling Read-only Mode in the Interface File

%module Unix ... /* Enable the read-only mode. */ %immutable;

/* Read-only variable. */ extern int readOnly;

/* Disable the read-only mode. */ %mutable;

Setter Method Is Not Generated for the Read-only Variable

...

public static int getReadOnly() { return UnixJNI.readOnly_get(); }

public static void setReadWrite(int value) { UnixJNI.readWrite_set(value); }

public static int getReadWrite() { return UnixJNI.readWrite_get(); } }

Besides the constants and read-only variables, enumerations are also frequently used in applications. Enumerations are set of named constant values.

Enumerations

SWIG can handle both named and anonymous enumerations. Depending on the developer’s choice or the target Java version, it can generate enumerations in four different ways.

Anonymous

Anonymous enumerations can be declared in the interface file, as shown in Listing 4-22.

Listing 4-22. Anonymous Enumeration

%module Unix ... /* Anonymous enumeration. */ enum { ONE = 1, TWO = 2, THREE, FOUR };

SWIG generates the final static variables in the <Module>Constants Java interface for each enumeration, as shown in Listing 4-23. Like the constants, the enumerations are also generated as run-time enumerations. The %javaconst preprocessor directive can be used to generate compile-time enumeration.

Listing 4-23. Anonymous Enumeration Exposed Through Constants Interface

package com.apress.swig;

public interface UnixConstants { ... public final static int ONE = UnixJNI.ONE_get(); public final static int TWO = UnixJNI.TWO_get(); public final static int THREE = UnixJNI.THREE_get(); public final static int FOUR = UnixJNI.FOUR_get();

}

Type-Safe

Named enumerations can be declared in interface file, as shown in Listing 4-24. Unlike the anonymous enumerations, they get exposed to Java as type-safe enumerations.

Listing 4-24. Named Enumeration

%module Unix ... /* Named enumeration. */ enum Numbers { ONE = 1, TWO = 2, THREE, FOUR };

SWIG defines a separate class with the enumeration’s name, and the enumeration values are exposed as final static member fields, as shown in Listing 4-25.

Listing 4-25. Named Enumeration Exposed as a Java Class

package com.apress.swig;

public final class Numbers { public final static Numbers ONE = new Numbers( "ONE", UnixJNI.ONE_get());

public final static Numbers TWO = new Numbers(

"TWO", UnixJNI.TWO_get()); public final static Numbers THREE = new Numbers("THREE"); public final static Numbers FOUR = new Numbers("FOUR");

... /* Helper methods. */ ...

}

This type of enumeration allows type checking and it is much safer than the constants based approach, although it cannot be used in switch statements.

enumtypeunsafe.swg extension, as shown in

Named Enumeration Exposed as Type Unsafe

..

%include "enumtypeunsafe.swg"

/* Named enumeration. */ enum Numbers { ONE = 1, TWO = 2, THREE, FOUR };

The generated Java class for the enumeration is shown in Listing 4-27. This type of enumerations can be used in switch statements since they are constants-based.

Listing 4-27. Type Unsafe Enumeration Exposed as a Java Class

package com.apress.swig;

public final class Numbers { public final static int ONE = UnixJNI.ONE_get(); public final static int TWO = UnixJNI.TWO_get(); public final static int THREE = UnixJNI.THREE_get(); public final static int FOUR = UnixJNI.FOUR_get();

}

Java Enumerations

Named enumerations can also be exposed to Java as proper Java enumerations. This type of enumerations is type checked, and they can also be used in switch statements. Named enumerations can be marked as Java enumeration exposure by including the enums.swg extension, as shown in Listing 4-28.

Listing 4-28. Java Enumeration

%module Unix ...

/* Java enumeration. */ %include "enums.swg"

/* Named enumeration. */ enum Numbers { ONE = 1, TWO = 2, THREE, FOUR };

The generated Java class is shown in Listing 4-29.

Listing 4-29. Generated Java Enumeration Class

package com.apress.swig;

public enum Numbers { ONE(UnixJNI.ONE_get()), TWO(UnixJNI.TWO_get()), THREE, FOUR;

... /* Helper methods. */ ...

}

Structures are widely used in C/C++ applications. They aggregate a set of named variables into a single data type.

Structures

Structures are also supported by SWIG, and they can be declared in the interface file, as shown in Listing 4-30.

Listing 4-30. Point Structure That Is Declared in the Interface File

%module Unix ... /* Point structure. */ struct Point {

int x; int y; };

They get wrapped as Java classes with getters and setters for the member variables, as shown in Listing 4-31.

Listing 4-31. Generated Point Java Class

package com.apress.swig;

public class Point { private long swigCPtr; protected boolean swigCMemOwn;

protected Point(long cPtr, boolean cMemoryOwn) { swigCMemOwn = cMemoryOwn; swigCPtr = cPtr;

}

protected static long getCPtr(Point obj) { return (obj == null) ? 0 : obj.swigCPtr; }

protected void finalize() { delete(); }

public synchronized void delete() { if (swigCPtr ! = 0) {

if (swigCMemOwn) { swigCMemOwn = false; UnixJNI.delete_Point(swigCPtr);

} swigCPtr = 0; } }

public void setX(int value) { UnixJNI.Point_x_set(swigCPtr, this, value); }

public int getX() { return UnixJNI.Point_x_get(swigCPtr, this); }

public void setY(int value) { UnixJNI.Point_y_set(swigCPtr, this, value); }

public int getY() { return UnixJNI.Point_y_get(swigCPtr, this); }

public Point() { this(UnixJNI.new_Point(), true); } }

Another widely used C/C++ data type is pointers, a memory address whose value refers directly to value elsewhere in the memory.

Pointers

SWIG also provides support for pointers. As seen in the previous example, SWIG stores the C pointer of the actual C structure instance in the Java class. SWIG stores the pointers using the long data type. It manages the life cycle of the C components aligned with the life cycle of the associated Java class through the use of the finalize method.

Wrapping C++ Code

In the previous section you explored the basics of wrapping C components. Now you will focus on wrapping the C++ code. First, you need to modify the Android.mk file to instruct SWIG to generate C++ code. In order to do so, open the Android.mk file in the editor view and set MY_SWIG_TYPE variable to cxx, as shown in Listing 4-32.

Listing 4-32. Android.mk Instructing SWIG to Generate C + Code

MY_SWIG_PACKAGE := com.apress.swig MY_SWIG_INTERFACES := Unix.i

MY_SWIG_TYPE := cxx

SWIG will now generate the wrapper in C++ instead of C code. You have already learned the function generation, so you’ll now focus on the type of arguments that can be passed to these functions.

Pointers, References, and Values

In C/C++, function can take arguments in many different ways, such as through pointers, references, or by simply value (see Listing 4-33).

Listing 4-33. Functions with Different Argument Types

/* By pointer. */ void drawByPointer(struct Point* p);

/* By reference */ void drawByReference(struct Point& p);

/* By value. */ void drawByValue(struct Point p);

In Java there are no such types. SWIG unifies these types together in the wrapper code as object instance reference, as shown in Listing 4-34.

Listing 4-34. Unified Methods in Generated Java Class

package com.apress.swig;

public class Unix implements UnixConstants { ...

public static void drawByPointer(Point p) { UnixJNI.drawByPointer(Point.getCPtr(p), p); }

public static void drawByReference(Point p) { UnixJNI.drawByReference(Point.getCPtr(p), p); }

public static void drawByValue(Point p) { UnixJNI.drawByValue(Point.getCPtr(p), p); }

Unified Methods Getting Called with the Same Argument Type

Point p; ... Unix.drawByPointer(p); Unix.drawByReference(p); Unix.drawByValue(p);

The C/C++ programming language allows functions to specify default values for some of their arguments. When these functions are called by omitting these arguments, the default values are used.

Default Arguments

Although default arguments are not supported by Java, SWIG provides support for functions with default arguments by generating additional functions for each argument that is defaulted. Functions with default arguments can be decelerated in the interface file, as shown in Listing 4-36.

Listing 4-36. Function with Default Arguments in the Interface File

%module Unix ... /* Function with default arguments. */ void func(int a = 1, int b = 2, int c = 3);

Generated additional functions will be exposed through the module Java class, as shown in Listing 4-37.

Listing 4-37. Additional Functions Generated to Support Default Arguments

package com.apress.swig;

public class Unix { ...

public static void func(int a, int b, int c) { UnixJNI.func__SWIG_0(a, b, c); }

public static void func(int a, int b) { UnixJNI.func__SWIG_1(a, b); }

public static void func(int a) { UnixJNI.func__SWIG_2(a); }

public static void func() { UnixJNI.func__SWIG_3(); } }

Function overloading allows applications to define multiple functions having the same name but different arguments.

Overloaded Functions

SWIG easily supports the overloaded functions since Java already provides support for them. Overloaded functions can be declared in the interface file, as shown in Listing 4-38.

Listing 4-38. Overloaded Functions Declared in the Interface File

%module Unix ... /* Overloaded functions. */ void func(double d); void func(int i);

SWIG exposes the overloaded functions through the module Java class, as shown in Listing 4-39.

Listing 4-39. Overloaded Functions Exposed Through the Module Java Class

package com.apress.swig;

public class Unix { ...

public static void func(double d) { UnixJNI.func__SWIG_0(d); }

public static void func(int i) { UnixJNI.func__SWIG_1(i); } }

SWIG resolves overloaded functions using a disambiguation scheme that ranks and sorts declarations according to a set of type-precedence rules. Besides the functions and primitive data types, SWIG can also translate C++ classes.

Class Declaration in the Interface File

..

class A {

public:

A();

A(int value);

~A();

void print();

int value; private:

void reset(); };

SWIG generates the corresponding Java class, as shown in Listing 4-41. The value member variable is public, and the corresponding getter and setter methods are automatically generated by SWIG. The reset method does not get exposed to Java since it is declared in private in the class declaration.

Listing 4-41. C/C++ Exposed to Java

package com.apress.swig;

public class A {

private long swigCPtr;

protected boolean swigCMemOwn;

protected A(long cPtr, boolean cMemoryOwn) { swigCMemOwn = cMemoryOwn; swigCPtr = cPtr;

}

protected static long getCPtr(A obj) { return (obj == null) ? 0 : obj.swigCPtr; }

protected void finalize() { delete(); }

public synchronized void delete() { if (swigCPtr ! = 0) {

if (swigCMemOwn) { swigCMemOwn = false; UnixJNI.delete_A(swigCPtr);

} swigCPtr = 0; } }

public A() { this(UnixJNI.new_A__SWIG_0(), true); }

public A(int value) { this(UnixJNI.new_A__SWIG_1(value), true); }

public void print() { UnixJNI.A_print(swigCPtr, this); }

public void setValue(int value) { UnixJNI.A_value_set(swigCPtr, this, value); }

public int getValue() { return UnixJNI.A_value_get(swigCPtr, this); } }

SWIG provides support for inheritance as well. Those classes are wrapped into a hierarchy of Java classes reflecting the same inheritance relationship. Since Java does not support multiple inheritance, any C++ class with multiple inheritance will trigger an error during the code generation phase.

Exception Handling

In native code, C/C++ functions can throw exceptions or return error codes. SWIG allows developers to inject exception handling code into the generated wrapper code by using the %exception preprocessor directive to translate the C/C++ exceptions and error codes into Java exceptions. Exception handling code can be defined in the interface file, as shown in Listing 4-42. The exception handling code should be defined before the actual function declaration.

Listing 4-42. Exception Handling Code for getuid Function

/* Exception handling for getuid. */

$action

if (!result) { jclass clazz = jenv->FindClass("java/lang/OutOfMemoryError"); jenv->ThrowNew(clazz, "Out of Memory"); return $null;

}

getuid function now looks like

Listing 4-43. Wrapper Code with Exception Handling

SWIGEXPORT jlong JNICALL Java_com_apress_swig_UnixJNI_getuid(JNIEnv *jenv, jclass jcls) { jlong jresult = 0 ; uid_t result;

(void)jenv; (void)jcls; {

result = (uid_t)getuid();

if (!result) { jclass clazz = jenv->FindClass("java/lang/OutOfMemoryError"); jenv->ThrowNew(clazz, "Out of Memory"); return 0;

}

} jresult = (jlong)result; return jresult;

}

The generated Java code did not change since the code is throwing a run-time exception. If a checked exception is thrown, SWIG can be instructed through the %javaexception preprocessor directive to reflect that accordingly to the generated Java methods, as shown in Listing 4-44.

Listing 4-44. Instructing SWIG That a Checked Exception May Be Thrown

/* Exception handling for getuid. */

%javaexception("java.lang.IllegalAccessException") getuid {

$action

if (!result) {

jclass clazz = jenv->FindClass("java/lang/IllegalAccessException");

jenv->ThrowNew(clazz, "Illegal Access");

return $null;

} }

The generated Java method signature now reflects the checked exception that may be thrown, as shown in Listing 4-45.

Listing 4-45. Java Class Reflecting the Thrown Exception

package com.apress.swig;

public class Unix { public static long getuid() throws java.lang.IllegalAccessException { return UnixJNI.getuid(); } }

Memory Management

Each proxy class that is generated by SWIG contains an ownership flag called swigCMemOwn. This flag specifies who is responsible for cleaning up the underlying C/C++ component. If the proxy class owns the underlying component, the memory will get freed by the finalize method of the Java class when it gets garbage collected. Memory can be freed without waiting for the garbage collector by simply invoking the delete method of the Java class. During runtime the Java class can be instructed to release or take ownership of the underlying C/C++ component’s memory through the swigReleaseOwnership and swigTakeOwnership methods.

Calling Java from Native Code

Until this point you have always called from Java to C/C++ code. In certain cases, you may need to call from C/C++ code back to Java code as well, such as for callbacks. SWIG does also provide support calling from C/C++ code to Java by the use of virtual methods.

Asynchronous Communication

In order to demonstrate the flow, you will convert the getuid function call to an asynchronous mode by wrapping it in a C/C++ class and returning its result through a callback. For this experiment, you can place the class declaration and definition into the SWIG interface file, as shown in Listing 4-46.

Listing 4-46. Declaration and Definition of AsyncUidProvider Class

%module Unix ... %{ /* Asynchornous user ID provider. */ class AsyncUidProvider { public:

AsyncUidProvider() { }

virtual~AsyncUidProvider() { }

void get() { onUid(getuid()); }

virtual void onUid(uid_t uid) { }

public: AsyncUidProvider(); virtual~AsyncUidProvider();

void get(); virtual void onUid(uid_t uid); };

Enabling Directors

SWIG provides support for cross language polymorphism using directors feature. The directors feature is disabled by default. In order to enable it, the %module preprocessor directive should be modified to include the directors flag. After enabling the directors extension, the feature should be applied to AsyncUidProvider class using the %feature preprocessor directive. Both changes are shown in Listing 4-47.

Listing 4-47. Enabling Directors Extension and Applying the Feature

/* Module name is Unix. */ %module(directors=1) Unix

/* Enable directors for AsyncUidProvider. */ %feature("director") AsyncUidProvider;

In order to bridge calls from C/C++ code to Java, the directors extension relies on Run-Time Type Information (RTTI) feature of the compiler.

Enabling RTTI

By default, RTTI is turned off on Android NDK build system. In order to enable it, modify the Android.mk file as shown in Listing 4-48.

Listing 4-48. Enabling RTTI in Android.mk File

# Enable RTTI LOCAL_CPP_FEATURES+= rtti

The native code portion is now ready. Choose Project Build All from the top menu to rebuild the current project.

Overriding the Callback Method

On the Java side, you need to extend the exposed AsyncUidProvider class and override the onUid method to receive the result of the getuid function call, as shown in Listing 4-49.

Listing 4-49. Extending the AsyncUidProvider in Java

package com.example.hellojni;

import android.widget.TextView;

import com.apress.swig.AsyncUidProvider;

public class UidHandler extends AsyncUidProvider { private final TextView textView;

UidHandler(TextView textView) { this.textView = textView; }

@Override public void onUid(long uid) { textView.setText("UID: "+uid); } }

Updating the HelloJni Activity

As the last step, the HelloJni activity needs to be modified to use the UidHandler class. The modified content of onCreate method is shown in Listing 4-50.

Listing 4-50. Modified onCreate Method Using the New UidHandler

@Override public void onCreate(Bundle savedInstanceState) {

...

TextView tv = new TextView(this); setContentView(tv);

UidHandler uidHandler = new UidHandler(tv); uidHandler.get();

New File from the top menu to launch the application. Upon invoking the AsnycUidProvider, the C/C++ code will call back to Java with the result of the function call and it will get displayed.

be found in SWIG Documentation at http://swig.org/Doc2.0/index.html. You will be using SWIG often in the next chapters, and you will continue exploring the other unique features offered.

posted @ 2013-05-21 09:37  张云贵  Views(4856)  Comments(0Edit  收藏  举报