首页资源分类嵌入式系统安卓 > Pro Android C with the NDK

Pro Android C with the NDK

已有 445510个资源

下载专区

上传者其他资源

    文档信息举报收藏

    标    签:AndroidNDK

    分    享:

    文档简介

    AndroidC++高级编程NDK英文版

    文档预览

    Building graphic-rich and better performing native applications Pro Android C++ with the NDK Onur Cinar For your convenience Apress has placed some of the front matter material after the index. Please use the Bookmarks and Contents at a Glance links to access them. Contents at a Glance About the Author��������������������������������������������������������������������������������������������������������������� xix About the Technical Reviewer������������������������������������������������������������������������������������������� xxi Preface���������������������������������������������������������������������������������������������������������������������������� xxiii ■■Chapter 1: Getting Started with C++ on Android���������������������������������������������������������������1 ■■Chapter 2: Exploring the Android NDK�����������������������������������������������������������������������������41 ■■Chapter 3: Communicating with Native Code using JNI��������������������������������������������������67 ■■Chapter 4: Auto-Generate JNI Code Using SWIG��������������������������������������������������������������95 ■■Chapter 5: Logging, Debugging, and Troubleshooting���������������������������������������������������127 ■■Chapter 6: Bionic API Primer�����������������������������������������������������������������������������������������155 ■■Chapter 7: Native Threads���������������������������������������������������������������������������������������������179 ■■Chapter 8: POSIX Socket API: Connection-Oriented Communication����������������������������209 ■■Chapter 9: POSIX Socket API: Connectionless Communication�������������������������������������247 ■■Chapter 10: POSIX Socket API: Local Communication���������������������������������������������������259 ■■Chapter 11: C++ Support�����������������������������������������������������������������������������������������������275 v vi Contents at a Glance ■■Chapter 12: Native Graphics API�����������������������������������������������������������������������������������285 ■■Chapter 13: Native Sound API���������������������������������������������������������������������������������������335 ■■Chapter 14: Profiling and NEON Optimization���������������������������������������������������������������363 Index���������������������������������������������������������������������������������������������������������������������������������381 1 Chapter Getting Started with C++ on Android Needless to say, exploring and practicing are the best methods for learning. Having a fully functional development environment ready at the very beginning of this book will enable you to explore and experiment with the material while working through the chapters. The Android C++ development environment is mainly formed by the following components:  Android Software Development Kit (SDK)  Android Native Development Kit (NDK)  Android Development Tools (ADT) Plug-In for Eclipse  Java Development Kit (JDK)  Apache ANT Build System  GNU Make Build System  Eclipse IDE This chapter will provide step-by-step instructions for setting up the proper Android C++ development environment. Android development tools are provided for the major operating systems:  Microsoft Windows  Apple Mac OS X  Linux Since the requirements and the installation procedure vary depending on the operating system, the following sections will walk you through the steps for setting up the Android C++ development environment based on the operating system. You can skip over the ones that don’t apply to you. 1 Download at http://www.pin5i.com/ 2 CHAPTER 1: Getting Started with C++ on Android Microsoft Windows Android development tools require Windows XP (32-bit only), Vista, or Windows 7. In this section, you will be downloading and installing the following components:  Java JDK 6  Apache ANT Build System  Android SDK  Cygwin  Android NDK  Eclipse IDE Note  Android development tools only support Java compiler compliance level 5 or 6. Although the later versions of JDK can be configured to comply with those levels, using JDK 6 is much simpler and less prone to errors. Multiple JDK flavors are supported by Android development tools, such as IBM JDK, Open JDK, and Oracle JDK (formerly known as Sun JDK). In this book, it is assumed that Oracle JDK will be used since it supports a broader range of platforms. In order to download Oracle JDK, navigate to www.oracle.com/technetwork/java/javase/downloads/index.html and follow these steps: 1. Click the JDK 6 download button, as shown in Figure 1-1. At the time of this writing the latest version of Oracle JDK 6 is Update 33. Figure 1-1.  Oracle JDK 6 Download button Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 3 2. Clicking the Oracle JDK 6 Download button takes you to a page listing the Oracle JDK 6 installation packages for supported platforms. 3. Check “Accept License Agreement” and download the installation package for Windows x86, as shown in Figure 1-2. Figure 1-2.  Download Oracle JDK 6 for Windows x86 Now you can install. The Oracle JDK 6 installation package for Windows comes with a graphical installation wizard. The installation wizard will guide you through the process of installing JDK. The installation wizard will first install the JDK, and then the JRE. During the installation process, the wizard will ask for the destination directories, as well as the components to be installed. You can continue with the default values here. Make a note of the installation directory for the JDK part, shown in Figure 1-3. Figure 1-3.  Oracle JDK 6 installation direDcotowrynload at http://www.pin5i.com/ 4 CHAPTER 1: Getting Started with C++ on Android The JDK will be ready to use upon completion of the installation process. The installation wizard does not automatically add the Java binary directory into the system executable search path, also known as the PATH variable. This needs to be done manually as the last step of the JDK installation. 1. Choose Control Panel from the Start button menu. 2. Click the System icon to launch the System Properties dialog. 3. Switch to the Advanced tab and click the Environment Variables button, as shown in Figure 1-4. Figure 1-4.  System Properties dialog 4. Clicking the Environment Variables button will launch the Environment Variables dialog. The dialog is separated into two parts: the top one is for the user and the bottom is for the system. 5. Click the New button in the system variables section to define a new environment variable, as shown in Figure 1-5. Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 5 Figure 1-5.  Environment Variables dialog 6. Set the variable name to JAVA_HOME and the variable value to the Oracle JDK installation directory that you noted during the Oracle JDK installation, as shown in Figure 1-6. Figure 1-6.  New JAVA_HOME environment variable 7. Click OK button to save the environment variable. Download at http://www.pin5i.com/ 6 CHAPTER 1: Getting Started with C++ on Android 8. From the list of system variables, double-click the PATH variable and append ;%JAVA_HOME%\bin to the variable value, as shown in Figure 1-7.   Appending Oracle JDK binary path to system PATH variable Start ➤ Accessories ➤ . Using the command prompt, execute javac –version. If the installation was 1-8. Figure 1-8.  Validating Oracle JDK installation Downloading and Installing the Apache ANT on Windows Apache ANT is a command-line build tool that whose mission is to drive any type of process that can be described in terms of targets and tasks. Android development tools require Apache ANT version 1.8 or later for the build process to function. At the time of this writing, the latest version of Apache ANT is 1.8.4. In order to download Apache ANT, navigate to http://ant.apache.org/bindownload.cgi and download the installation package in ZIP format, as shown in Figure 1-9. Then follow these steps: Figure 1-9.  Apache ANT download package inDZoIPwfnorlmoaadt at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 7 1. The Windows operating system comes with native support for ZIP files. When the download completes, right-click the ZIP file. 2. Choose Extract All from the context menu to launch the Extract Compressed Folder wizard. 3. Using the Browse button, choose the destination directory, as shown in Figure 1-10. A dedicated empty destination directory is not needed since the ZIP file already contains a sub directory called apache-ant-1.8.4 that holds the Apache ANT files. In this book, the C:\android directory will be used as the root directory to hold the Android development tools and dependencies. Make a note of the destination directory. Figure 1-10.  Extracting Apache ANT ZIP archive 4. Click the Extract button to install Apache ANT. Upon installing the Apache ANT, follow these steps to append its binary path to system executable search path: 1. Launch the Environment Variables dialog from System Properties. 2. Click the New button in the system variables section to define a new environment variable. 3. Set the variable name to ANT_HOME and the variable value to the Apache ANT installation directory (such as C:\android\apache-ant-1.8.4), as shown in Figure 1-11. Figure 1-11.  New ANT_HOME environmeDnot vwanrilaobaled at http://www.pin5i.com/ 8 CHAPTER 1: Getting Started with C++ on Android 4. Click the OK button to save the new environment variable. 5. From the list of system variables, double-click the PATH variable and append ;%ANT_HOME%\bin to the variable value, as shown in Figure 1-12. Appending Apache ANT binary path to system PATH variable ant -version. If the installation was successful, you will see the Apache 1-13. Figure 1-13. Validating Apache ANT installation Downloading and Installing the Android SDK on Windows The Android software development kit (SDK) is the core component of the development toolchain, providing framework API libraries and developer tools that are necessary for building, testing, and debugging Android applications. Navigate to http://developer.android.com/sdk/index.html to download the Android SDK. At the time of this writing, the latest version for Android SDK is R20. Two types of installation packages are currently provided: a graphical installer and a ZIP archive. Although the graphical installer is offered as the main installation package, it is known to have issues on certain platforms. Click the link for Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 9 “Other Platforms” and download the Android SDK ZIP archive, as shown in Figure 1-14. Then follow these steps: Figure 1-14.  Android SDK download page 6. When the download completes, right-click the ZIP file and choose Extract All from the context menu to launch the Extract Compressed Folder wizard. 7. Using the Browse button, choose the destination directory. A dedicated empty destination directory is not needed since the ZIP file already contains a sub directory called android-sdk-windows that contains the Android SDK files. Make a note of the destination directory. 8. Click the Extract button install Android SDK. Binary paths of Android SDK should be appended to the system executable search path. In order to do so, follow these steps: 1. Launch the Environment Variables dialog from System Properties. 2. Click the New button in the system variables section to define a new environment variable. 3. Set the variable name to ANDROID_SDK_HOME and the variable value to the Android SDK installation directory (such as C:\android\android-sdkwindows), as shown in Figure 1-15. Download at http://www.pin5i.com/ 10 CHAPTER 1: Getting Started with C++ on Android Figure 1-15.  ANDROID_SDK_HOME environment variable 4. Click the OK button to save the new environment variable. 5. There are three important directories that need to be added to the system executable search path: the SDK root directory, the tools directory holding the Android platform-independent SDK Tools, and the platform-tools directory holding the Android platform tools. Ignore the fact that platformtools directory does not exist yet. From the list of system variables on the Environment Variables dialog, double-click the PATH variable and append ;%ANDROID_SDK_HOME%;%ANDROID_SDK_HOME%\tools;%ANDROID_SDK_HOME%\ platform-tools to the variable value, as shown in Figure 1-16. Figure 1-16.  Appending Android SDK binary paths to system PATH variable In order to validate the installation, open a command prompt window. Using the command prompt, execute 'SDK Manager' including the quotes. If the installation was successful, you will see the Android SDK Manager, as shown in Figure 1-17. Figure 1-17.  Android SDK Manager applicationDownload at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 11 Downloading and Installing the Cygwin on Windows The Android Native Development Kit (NDK) tools were initially designed to work on UNIX-like systems. Some of the NDK components are shell scripts, and they are not directly executable on the Windows operating system. Although the latest version of the Android NDK is showing progress in making itself more independent and self-packaged, it still requires Cygwin to be installed on the host machine in order to fully operate. Cygwin is a UNIX-like environment and command-line interface for the Windows operating system. It comes with base UNIX applications, including a shell that allows running the Android NDK’s build system. At the time of this writing, Android NDK requires Cygwin 1.7 to be installed in order to function. Navigate to http://cygwin.com/install.html and download the Cygwin installer, setup.exe (see Figure 1-18). Figure 1-18.  Download the Cygwin setup application Upon starting the setup application, you will see the Cygwin installation wizard welcome screen. Click the Next button and follow these steps to proceed with the installation: 1. Installation will ask you to choose the download source. Keep the default selection of “Install from Internet” and click the Next button to proceed. 2. In the next dialog, the installer will ask you select the directory where you want to install Cygwin, as shown in Figure 1-19. By default Cygwin will be installed under C:\cygwin directory. Note the destination directory and click the Next button. Download at http://www.pin5i.com/ 12 CHAPTER 1: Getting Started with C++ on Android   Choosing Cygwin installation directory 3. The next dialog will ask you select the local package directory. This is the temporary directory that will be used to download the packages. Keep the default value and click the Next button. 4. In the next dialog, you will select the Internet connection type. Unless you need to use a proxy to access the Internet, keep the default selection of “Direct Connection” and click the Next button to proceed. 5. The installer will ask you to select a download site. From the list of mirror sites, either chooses a random one or the one closest geographically to your location. Then click the Next button. 6. Cygwin is not a single application; it is a large software distribution containing multiple applications. In the next dialog, the Cygwin installer will provide you a list of all available packages. Android NDK requires GNU Make 3.8.1 or later in order to function. Using the search field, filter the package list by keyword “make,” expand the Devel category, and select the GNU Make package, as shown in Figure 1-20. Click the Next button to start the installation. Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 13 Figure 1-20.  Select GNU Make package When the installation completes, the Cygwin binary path needs to be added to the system executable search path. 1. Launch the Environment Variables dialog from System Properties. 2. Click the New button in the system variables section to define a new environment variable. 3. Set the variable name to CYGWIN_HOME and the variable value to the Cygwin installation directory (such as C:\cygwin), as shown in Figure 1-21. Download at http://www.pin5i.com/ 14 CHAPTER 1: Getting Started with C++ on Android Figure 1-21.  CYGWIN_HOME environment variable 4. From the list of system variables in the Environment Variables dialog, double-click the PATH variable and append ;%CYGWIN_HOME%\bin to the variable value, as shown in Figure 1-22. Figure 1-22.  Appending Cygwin binary path to system PATH variable After completing this last installation step, Cygwin tools are now part of the system executable search path. In order to validate the installation, open a command prompt window. Using the command prompt, execute make -version. If the installation was successful, you will see the GNU Make version number, as shown in Figure 1-23. Figure 1-23.  Validating Cygwin installation Downloading and Installing the Android NDK on Windows The Android Native Development Kit (NDK) is a companion tool to Android SDK that lets you develop Android applications using native programming languages such as C++. Android NDK provide header files, libraries, and cross-comDpowilenrlotaodolacthhatitnps:/. /Awtwthwe.ptiinm5ei.coofmth/is writing, the latest version for CHAPTER 1: Getting Started with C++ on Android 15 Android NDK is R8. In order to download the Android NDK, navigate to http://developer.android. com/tools/sdk/ndk/index.html and go to the Downloads section shown in Figure 1-24. Then follow these steps: Figure 1-24.  Android NDK download page 1. Android NDK installation package is provided as a ZIP archive. When the download completes, right-click the ZIP file and choose Extract All from the context menu to launch the Extract Compressed Folder wizard. 2. Using the Browse button, choose the destination directory. A dedicated empty destination directory is not needed since the ZIP file already contains a sub directory called android-ndk-r8 that contains the Android NDK files. Make a note of the destination directory. 3. Click the Extract button to install Android NDK. The binary paths of Android SDK can be appended to the system executable search path by following these steps: 1. Again, launch the Environment Variables dialog from System Properties. 2. Click the New button in the system variables section to define a new environment variable. Set the variable name to ANDROID_NDK_HOME and the variable value to the Android NDK installation directory (such as C:\android\android-ndk-r8), as shown in Figure 1-25. Figure 1-25.  ANDROID_NDK_HOME envirDoonmwennltovaadriaabtlhettp://www.pin5i.com/ 16 CHAPTER 1: Getting Started with C++ on Android 3. Click the OK button to save the new environment variable. 4. From the list of system variables in the Environment Variables dialog, double-click the PATH variable and append ;%ANDROID_NDK_HOME% to the variable value, as shown in Figure 1-26.   Appending Android NDK binary path to system PATH variable ndk-build. If the installation was successful, you will 1-27, which is fine. Figure 1-27.  Validating Android NDK installation Downloading and Installing the Eclipse on Windows Eclipse is a highly extensible, multi-language integrated development environment. Although it is not a requirement for native Android development, Eclipse does provide a highly integrated coding environment, bringing Android tools to your fingertips to streamline the application development. At the time of this writing, the latest version of Eclipse is Juno 4.2. In order to download Eclipse, navigate to http://www.eclipse.org/downloads/, as shown in Figure 1-28, and follow these steps: Figure 1-28.  Eclipse download page Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 17 1. Download the Eclipse Classic for Windows 32 Bit from the list. The Eclipse installation package is provided as a ZIP archive. 2. When the download completes, right-click the ZIP file and choose Extract All from the context menu to launch the Extract Compressed Folder wizard. 3. Using the Browse button, choose the destination directory. A dedicated empty destination directory is not needed since the ZIP file already contains a sub directory called eclipse that holds the Eclipse files. 4. Click the Extract button to install Eclipse. 5. In order to make Eclipse easily accessible, go to the Eclipse installation directory. 6. Right-click the Eclipse binary and choose Send ➤ Desktop to make a shortcut to Eclipse on your Windows desktop. To validate the Eclipse installation, double-click the Eclipse icon. If the installation was successful, you will see the Eclipse Workspace Launcher dialog shown in Figure 1-29. Figure 1-29.  Validating Eclipse installation Apple Mac OS X Android development tools require Mac OS X 10.5.8 or later and an x86 system. Since Android development tools were initially designed to work on UNIX-like systems, most of its dependencies are already available on the platform either through OS X directly or through the Xcode developer tools. In this section, you will be downloading and installing the following components:  Xcode  Java JDK 6  Apache ANT Build System  GNU Make Download at http://www.pin5i.com/ 18 CHAPTER 1: Getting Started with C++ on Android  Android SDK  Android NDK  Eclipse IDE Installing Xcode on Mac Xcode provides developer tools for application development on the OS X platform. Xcode can be found at Mac OS X installation media or through the Mac App Store free of charge. Navigate to https://developer.apple.com/xcode/ for more information. Starting the Xcode installer will take you 1. Approve the licenses. 2. Select the destination directory. 3. The Install wizard will show the list of Xcode components that can be installed. From this list, select the UNIX Development package shown in Figure 1-30. Figure 1-30. Xcode custom installation dialog 4. Click the Continue button to start the installation. Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 19 Validating the Java Development Kit on Mac Android development tools require Java Development Kit (JDK) version 6 in order to run. The Apple Mac OS X operating system ships with the JDK already installed. It is based on the Oracle JDK but configured by Apple for better integration with Mac OS X. New versions of the JDK are available through the Software Update. Make sure that JDK 6 or later is installed. To validate the JDK installation, open a Terminal window and execute javac –version on the command line. If JDK is properly installed, you will see JDK version number, as shown in Figure 1-31. Figure 1-31.  Validating JDK Validating the Apache ANT on Mac Apache ANT is a command-line build tool that drives any type of process that can be described in terms of targets and tasks. Android development tools require Apache ANT version 1.8 or later for the build process to function. Apache ANT is installed as a part of Xcode’s UNIX Development package. In order to validate the Apache ANT installation, open a Terminal window and execute ant –version on the command line. If the installation was successful, you will see the Apache ANT version number, as shown in Figure 1-32. Figure 1-32.  Validating Apache ANT Validating the GNU Make GNU Make is a build tool that controls the generation of executables and other parts of an application from application’s source code. Android NDK requires GNU Make 3.8.1 or later in order to function. GNU Make is installed as a part of Xcode’s UNIX Development package. In order to validate the GNU Make installation, open a Terminal window and execute make –version on the command line. If the installation was successful, you will see the GNU Make version number, as shown in Figure 1-33. Download at http://www.pin5i.com/ 20 CHAPTER 1: Getting Started with C++ on Android Figure 1-33.  Validating GNU Make http://developer.android.com/sdk/index.html to download the Android SDK, as 1-34, and follow these steps: Figure 1-34.  Android SDK download page 1. Click the “Download the SDK for Mac” button to start downloading the SDK installation package. 2. The Android SDK installation package is provided as a ZIP archive. OS X provides native support for ZIP archives. If you are using the Safari browser, the ZIP file will be automatically extracted after the download. Otherwise, double-click the ZIP file to open it as a compressed folder. 3. Drag and drop the android-sdk-macosx directory to its destination location using the Finder, as shown in Figure 1-35. In this book, the /android directory will be used as the root directory holding the Android development tools and dependencies. Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 21 Figure 1-35.  Installing Android SDK to its destination location In order to make Android SDK easily accessible, the binary paths of Android SDK should be appended to the system executable search path. Open a Terminal window and execute the following commands, as shown in Figure 1-36: Figure 1-36.  Appending Android SDK binary path to system PATH variable  echo export ANDROID_SDK_HOME=/android/android-sdk-macosx > >   ~/.bash_profile  echo export PATH = \$ANDROID_SDK_HOME/tools:\$ANDROID_SDK_HOME/platformtools:\$PATH > > ~/.bash_profile In order to validate the Android SDK installation, open a new Terminal window and execute android -h on the command line. If the installation was successful, you will see the help messages shown in Figure 1-37. Figure 1-37.  Validating Android SDK installation Download at http://www.pin5i.com/ 22 CHAPTER 1: Getting Started with C++ on Android Downloading and Installing the Android NDK on Mac Android Native Development Kit (NDK) is a companion tool to Android SDK that lets you develop Android applications using native programming languages such as C++. The Android NDK provides header files, libraries, and cross-compiler toolchains. At the time of this writing, the latest version for Android NDK is R8. In order to download the Android NDK, navigate to http://developer.android. com/tools/sdk/ndk/index.html and go to the Downloads section, as shown in Figure 1-38. Then follow these steps: Figure 1-38.  Android NDK download page 1. Click to download the installation package. The Android NDK installation package is provided as a BZIP’ed TAR archive. OS X does not automatically extract this type of archive files. 2. In order to manually extract the archive file, open a Terminal window. 3. Go into the destination directory /android. 4. Execute tar jxvf ~/Downloads/android-ndk-r8-darwin-x86.tar.bz2, as shown in Figure 1-39. Figure 1-39.  Installing Android NDK The binary paths of Android NDK should be appended to system-executable search path to make it easily accessible. Open a Terminal window and execute the following commands (see Figure 1-40). Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 23 Figure 1-40.  Appending Android NDK binary path to system PATH variable  echo export ANDROID_NDK_HOME=/android/android-ndk-r8 > > ~/.bash_profile  echo export PATH = \$ANDROID_NDK_HOME:\$PATH > > ~/.bash_profile Validate the Android NDK installation by opening a new Terminal window and executing ndk-build on the command line. If the installation was successful, you will see the NDK build complaining about the project directory, as shown in Figure 1-41, which is fine. Figure 1-41.  Validating Android NDK Downloading and Installing the Eclipse on Mac Eclipse is a highly extensible, multi-language integrated development environment. Although it is not a requirement for native Android development, Eclipse does provide a highly integrated coding environment, bringing Android tools to your fingertips to streamline the application development. At the time of this writing, the latest version of Eclipse is Juno 4.2. In order to install Eclipse, navigate to http://www.eclipse.org/downloads/, as shown in Figure 1-42, and follow these steps: Figure 1-42.  Eclipse download page Download at http://www.pin5i.com/ 24 CHAPTER 1: Getting Started with C++ on Android 1. Download the Eclipse Classic for Mac OS X 32 Bit from the list. The Eclipse installation package is provided as a GZIP’ed TAR archive. If you are using the Safari browser, the archive file can be automatically decompressed but not extracted after the download. 2. In order to manually extract the archive, open a Terminal window and go into the destination directory of /android. 3. Execute tar xvf ~/Downloads/eclipse-SDK-4.2-macosx-cocoa.tar, as shown in Figure 1-43.   Installing Eclipse 1. Go to the Eclipse installation directory. 2. Drag and drop the Eclipse application to Dock, as shown in Figure 1-44. Figure 1-44.  Adding Eclipse to dock Double-click the Eclipse icon to validate the Eclipse installation. If the installation was successful, you will see the Eclipse Workspace Launcher dialog shown in Figure 1-45. Figure 1-45.  Validating Eclipse Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 25 Ubuntu Linux Android development tools require Ubuntu Linux version 8.04 32-bit or later or any other Linux flavor with GNU C Library (glibc) 2.7 or later. In this section, you will be downloading and installing the following components:  Java JDK 6  Apache ANT Build System  GNU Make  Android SDK  Android NDK  Eclipse IDE Checking the GNU C Library Version You can check the GNU C Library version by executing ldd --version on a Terminal window, as shown in Figure 1-46. Figure 1-46.  Checking the GNU C library version Enabling the 32-Bit Support on 64-Bit Systems On 64-bit Linux distributions, Android development tools require the 32-bit support package to be installed. In order to install the 32-bit support package, open a Terminal window and execute sudo apt-get install ia32-libs-multiarch, as shown in Figure 1-47. Figure 1-47.  Installing ia32-libs-multiarch Download at http://www.pin5i.com/ 26 CHAPTER 1: Getting Started with C++ on Android Downloading and Installing the Java Development Kit on Linux Android development tools require Java Development Kit (JDK) version 6 in order to run. Java Runtime Edition (JRE) itself is not sufficient. Java JDK 6 needs to be installed prior installing the Android development tools. Except for the GNU Compiler for Java (gcj), a variety of JDK flavors are supported by Android development tools, such as IBM JDK, Open JDK, and Oracle JDK (formerly known as Sun JDK). Due to licensing issues, Oracle JDK is not available in the Ubuntu software repository. In this book, it is assumed that Open JDK will be used. In order to install Open JDK, open a Terminal window and execute sudo apt-get install openjdk-6-jdk, as shown in Figure 1-48.   Installing Open JDK 6 1-49. java –version Figure 1-49.  Validating Open JDK installation Downloading and Installing the Apache ANT on Linux Apache ANT is a command-line build tool that drives any type of process that can be described in terms of targets and tasks. Android development tools require Apache ANT version 1.8 or later for the build process to function. Apache ANT is provided through the Ubuntu software repository. In order to install Apache ANT, open a Terminal window and execute sudo apt-get install ant, as shown in Figure 1-50. Figure 1-50.  Installing Apache ANT Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 27 Open a Terminal window and execute ant -version on the command line to validate the Apache ANT installation. If the installation was successful, you will see the Apache ANT version number, as shown in Figure 1-51. Figure 1-51.  Validating Apache ANT installation Downloading and Installing the GNU Make on Linux GNU Make is a build tool that controls the generation of executables and other parts of an application from application’s source code. Android NDK requires GNU Make 3.8.1 or later in order to function. GNU Make is provided through Ubuntu software repository. In order to install GNU Make, open a Terminal window and execute sudo apt-get install make, as shown in Figure 1-52. Figure 1-52.  Installing GNU Make Open a Terminal window and validate the GNU Make installation by executing make –version on the command line. If the installation was successful, you will see the GNU Make version number, as shown in Figure 1-53. Figure 1-53.  Validating GNU Make installation Download at http://www.pin5i.com/ 28 CHAPTER 1: Getting Started with C++ on Android Downloading and Installing the Android SDK on Linux The Android Software Development Kit (SDK) is the core component of the development toolchain, providing framework API libraries and developer tools that are necessary for building, testing, and debugging Android applications. At the time of this writing, the latest version for Android SDK is R20. Navigate to http://developer.android.com/sdk/index.html to download the Android SDK, as shown in Figure 1-54. Then follow these steps to install it: Android SDK download page 1. The Android SDK installation package is provided as a GZIP’ed TAR archive. Open a Terminal window and go to the destination directory. In this book, ~/ android directory will be used as the root directory for holding the Android development tools and dependencies. 2. Extract the Android SDK by executing tar zxvf ~/Downloads/android-sdk_ r20-linux.tgz on the command line, as shown in Figure 1-55. Figure 1-55. Installing Android SDK In order to make Android SDK easily accessible, binary paths of Android SDK should be appended to the system executable search path. Assuming that you are using the BASH shell, open a Terminal window and execute the following commands (shown in Figure 1-56): Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 29 Figure 1-56.  Appending Android SDK binary path to system PATH variable  echo export ANDROID_SDK_HOME = ~/android/android-sdk-linux > > ~/.bashrc  echo export PATH = \$ANDROID_SDK_HOME/tools:\$ANDROID_SDK_HOME/platformtools:\$PATH > > ~/.bashrc In order to validate the Android SDK installation, open new a Terminal window and execute android –h on the command line. If the installation was successful, you will see the help messages shown in Figure 1-57. Figure 1-57.  Validating Android SDK installation Downloading and Installing the Android NDK on Linux The Android Native Development Kit (NDK) is a companion tool to Android SDK that lets you develop Android applications using native programming languages such as C++. Android NDK provides header files, libraries, and cross-compiler toolchains. At the time of this writing, the latest version for Android NDK is R8. In order to download the Android NDK, navigate to http://developer.android. com/tools/sdk/ndk/index.html and go to the Downloads section, as shown in Figure 1-58. Follow these steps to install it: Figure 1-58.  Android NDK download pagDe ownload at http://www.pin5i.com/ 30 CHAPTER 1: Getting Started with C++ on Android 1. Open a Terminal window and go into the destination directory ~/android. 2. The Android NDK installation package is provided as a BZIP’ed TAR archive. Execute tar jxvf ~/Downloads/android-ndk-r8-linux-x86.tar.bz2, as shown in Figure 1-59, to extract the archive file.   Installing Android NDK 1-60): Figure 1-60.  Appending Android NDK binary path to system PATH variable  echo export ANDROID_NDK_HOME = ~/android/android-ndk-r8 > > ~/.bashrc  echo export PATH = \$ANDROID_NDK_HOME:\$PATH > > ~/.bashrc Open a new Terminal window and execute ndk-build on the command line to validate the Android NDK installation. If the installation was successful, you will see NDK build complaining about project directory, as shown in Figure 1-61, which is fine. Figure 1-61.  Validating Android NDK installation Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 31 Downloading and Installing the Eclipse on Linux Eclipse is a highly extensible, multi-language integrated development environment. Although it is not a requirement for native Android development, Eclipse does provide a highly integrated coding environment, bringing Android tools to your fingertips to streamline the application development. At the time of this writing, the latest version of Eclipse is Juno 4.2. Download Eclipse by navigating to www.eclipse.org/downloads/, as shown in Figure 1-62: Figure 1-62.  Eclipse download page 1. Download the Eclipse Classic for Linux 32 Bit from the list. 2. Open a Terminal window and go into the destination directory ~/android. 3. The Eclipse installation package is provided as a GZIP’ed TAR archive. Extract the archive by invoking tar xvf ~/Downloads/eclipse-SDK-4.2linux-gtk.tar.gz on the command line, as shown in Figure 1-63. Figure 1-63.  Installing Eclipse To validate the Eclipse installation, go into the eclipse directory and execute ./eclipse on the command line. If the installation was successful, you will see the Eclipse Workspace Launcher dialog shown in Figure 1-64. Download at http://www.pin5i.com/ 32 CHAPTER 1: Getting Started with C++ on Android   Validating Eclipse installation application development on the Eclipse platform. ADT is free software that is provided under the open source Apache License. More information about the latest ADT version and the most current installation steps can be found at the ADT Plug-in for Eclipse page at http://developer.android.com/ sdk/eclipse-adt.html. You will be using Eclipse’s Install New Software wizard to install ADT. 1. Launch the wizard by choosing Help ➤ Install New Software from the top menu bar, as shown in Figure 1-65. Figure 1-65.  Eclipse install new software Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 33 2. The wizard will start and display a list of available plug-ins. Since ADT is not part of the official Eclipse software repository, you need to first add Android’s Eclipse software repository as a new software site. To do this, click the Add button, as shown in Figure 1-66. Figure 1-66.  Add new software repository 3. The Add Repository dialog appears. In the Name field, enter Android ADT, and in the Location field, enter the URL for Android’s Eclipse software repository: https://dl-ssl.google.com/android/eclipse/ (see Figure 1-67). Figure 1-67.  Add Android ADT software repository 4. Click the OK button to add the new software site. 5. The Install New Software wizard will display a list of available ADT plugins, as shown in Figure 1-68. Each of these plug-ins is crucial for Android application development, and it is highly recommended that you install all of them. Download at http://www.pin5i.com/ 34 CHAPTER 1: Getting Started with C++ on Android   Installing ADT 6. Click the Select All button to select all of the ADT plug-ins. 7. Click the Next button to move to the next step. 8. Eclipse will go through the list of selected plug-ins to append any dependencies to the list and then will present the final download list for review. Click the Next button to move to the next step. 9. ADT contains a set of other third-party components with different licensing terms. During the installation process, Eclipse will present each software license and will ask you to accept the terms of the license agreements in order to continue with the installation. Review the license agreements, choose to accept their terms, and then click the Finish button to start the installation process. ADT plug-ins come within unsigned JAR files, which may trigger a security warning, as shown in Figure 1-69. Click the OK button to dismiss the warning and continue the installation. When the installation of the ADT plug-ins is complete, Eclipse will need to restart in order to apply the changes. Figure 1-69.  Security warning Upon restarting, ADT will ask you for the location of the Android SDK. Choose “Use existing SDKs” and select the Android SDK installation directory using the Browse button, as shown in Figure 1-70. Download at http://www.pin5i.com/ 4 CHAPTER 1: Getting Started with C++ on Android 35 Figure 1-70.  Selecting the Android SDK location Click the Next button to proceed to next step. Installing the Android Platform Packages Upon selecting the Android SDK location, ADT validates the Android SDK and the Android Platform packages. The Android SDK installation only contains the Android development tools. The Android Platform packages need to be installed separately to be able to build Android applications. Upon completing the validation, a SDK validation warning dialog is displayed, as shown in Figure 1-71. Figure 1-71.  ADT Android SDK validation Download at http://www.pin5i.com/ 36 CHAPTER 1: Getting Started with C++ on Android Click the Open SDK Manager button to launch the Android SDK Manager. Then follow these steps, as shown in Figure 1-72: Figure 1-72.  Android SDK manager 1. Expand the Tools category from the list of available packages and select Android SDK Platform-Tools. 2. Select the Android 4.0 (API 14) category. 3. Click the Install N Packages button to start the installation. Android SDK manager will show the license agreements for the selected packages. Accept the license agreements to continue the installation. Configuring the Emulator The Android SDK comes with a full-featured emulator, a virtual device that runs on your machine. The Android emulator allows you to develop and test Android applications locally on your machine without using a physical device. The Android emulator runs a full Android system stack, including the Linux kernel. It is a fully virtualized device that can mimic all of the hardware and software features of a real device. Each of these features can be customized by the user using the Android Virtual Device (AVD) Manager. Launch the AVD Manager, choose Window ➤ AVD Manager Window AVD Manager from the top menu bar, as shown in Figure 1-73. Download at http://www.pin5i.com/ CHAPTER 1: Getting Started with C++ on Android 37 Figure 1-73.  AVD Manager menu Click the New button on right side of the AVD Manager dialog to define a new emulator configuration, as shown in Figure 1-74. Figure 1-74.  AVD Manager In this book, you will use the Android Emulator often while working through the material. The following virtual machine configuration is recommended to execute the example code in this book. Complete the fields using the following values, as shown in Figure 1-75: Download at http://www.pin5i.com/ 38 CHAPTER 1: Getting Started with C++ on Android Figure 1-75. New emulator configuration  The Name parameter should be set to Android_14.  The Target parameter should be set to Android 4.0 – API Level 14.  The SD Card size should be set to at least 128 MB. The other settings can be left as is. In order to validate the newly defined emulator configuration, open up the AVD Manager, select the name of the emulator configuration from the list, and click the Start button to launch the emulator instance. If the configuration was suDcocwesnslofaudl, atht ehtetpm:/u/lwatwowr w.piilnl 5cio.cmome/up (see Figure 1-76). CHAPTER 1: Getting Started with C++ on Android 39 Figure 1-76.  Newly defined emulator configuration running Summary In this chapter you have configured your Android C++ development environment by installing the Android development tools and dependencies based on the target operating system. You have defined the Android emulator configuration to execute the example code that will be presented in the following chapters. The next chapter will provide a detailed introduction to the Android NDK. Download at http://www.pin5i.com/ 2 Chapter Exploring the Android NDK In the previous chapter, you configured your development environment by installing Android development tools and dependencies. Among these tools, the Android Native Development Kit (NDK) is the tool you will be using for C++ development on Android platform. The Android NDK is a companion toolset for the Android Software Development Kit (SDK), designed to augment the Android SDK to allow developers to implement and embed performance-critical portions of their applications using machine code-generating programming languages like C, C++, and Assembly. In this chapter, you will start exploring the Android NDK. You will be taking the hello-jni sample application that comes with the Android NDK and manipulating it to demonstrate the Android NDK build system. Components Provided with the Android NDK The Android NDK is not a single tool; it is a comprehensive set of APIs, cross-compilers, linkers, debuggers, build tools, documentation, and sample applications. The following are some of the key components of Android NDK:  ARM, x86, and MIPS cross-compilers  Build system  Java Native Interface headers  C library  Math library  POSIX threads  Minimal C++ library  ZLib compression library  Dynamic linker library  Android logging library 41 Download at http://www.pin5i.com/ 42 CHAPTER 2: Exploring the Android NDK  Android pixel buffer library  Android native application APIs  OpenGL ES 3D graphics library  OpenSL ES native audio library  OpenMAX AL minimal support Structure of the Android NDK During the installation process, all of the Android NDK components are installed under the target  ndk-build: This shell script is the starting point of the Android NDK build system. This chapter will cover ndk-build in detail while exploring the Android NDK build system.  ndk-gdb: This shell script allows debugging native components using the GNU Debugger. Chapter 5 will cover ndk-gdb in detail while discussing the debugging of native components.  ndk-stack: This shell script helps facilitate analyzing the stack traces that are produced when native components crash. Chapter 5 will cover ndk-stack in detail while discussing the troubleshooting and crash dump analysis of native components.  build: This directory contains the modules of the entire Android NDK build system. This chapter will cover the Android NDK build system in detail.  platforms: This directory contains header files and libraries for each supported Android target version. These files are used automatically by the Android NDK build system based on the specified target version.  samples: This directory contains sample applications to demonstrate the capabilities provided by the Android NDK. These sample projects are very useful for learning how to use the features provided by the Android NDK.  sources: This directory contains shared modules that developers can import into their existing Android NDK projects.  toolchains: This directory contains cross-compilers for different target machine architectures that the Android NDK currently supports. Android NDK currently supports ARM, x86, and MIPS machine architectures. The Android NDK build system uses the cross-compiler based on the selected machine architecture. The most important component of the Android NDK is its build system, which brings all other components together. To better understand how the build system works, you will be starting with a working example. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 43 Starting with an Example You will start with the hello-jni sample application that comes with the Android NDK. Later, you will modify it to demonstrate the different functionalities provided by the Android NDK build system, such as  Building a shared library  Building multiple shared libraries  Building static libraries  Sharing common modules using shared libraries  Sharing modules between multiple NDK projects  Using prebuilt libraries  Building standalone executables  Other build system variables and macros  Defining new variables and conditional operations Open the Eclipse IDE that you installed in the previous chapter. Although the Android NDK does not require the use of an IDE, using one will help to visually inspect the project structure and the build flow. During the startup, Eclipse will ask you to choose the workspace; you can continue with the default. Specifying the Android NDK Location Since this is the first time the workspace will be used for Android NDK development, the location of the Android NDK needs to be specified. 1. On Windows and Linux platforms, choose the Preferences menu item from the top menu bar. On Mac OS X platform, use the application menu in Eclipse and choose the Preferences menu item. 2. As shown in Figure 2-1, the left pane of the Preferences dialog contains the list of preferences categories in a tree format. Expand Android and then choose NDK from the tree. Download at http://www.pin5i.com/ 44 CHAPTER 2: Exploring the Android NDK   Android NDK location preference 3. Using the right pane, click the Browse button and select the location of Android NDK installation using the file explorer. The NDK location preference is only for the current Eclipse workspace. If you use another workspace later, you will need to repeat this process again. Importing the Sample Project As stated in the previous section, Android NDK installation contains example applications under the samples directory. You will be using one of those sample applications now. Using the top menu bar, choose File, and then the Import menu item to launch the Import wizard. From the list of import sources, expand Android and choose Existing Android Code into Workspace, as shown in Figure 2-2. Click Next to proceed to the next step. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 45 Figure 2-2.  Import existing Android code into workspace As shown in Figure 2-3, use the Browse button to launch the file explorer and navigate to /samples/hello-jni directory. The hello-jni project is simple “Hello World” Android NDK project. The project directory contains both the actual project and the test project. For the sake of simplicity, uncheck the test project for now, and only keep the main project checked. It is always a good practice to not change anything in the Android NDK installation directory to keep things safe. Check the “Copy projects into workspace” option to ask Eclipse to copy the project code into the workspace, so that you can operate on a copy rather than the original project. Click Next to start importing the project into the workspace. Download at http://www.pin5i.com/ 46 CHAPTER 2: Exploring the Android NDK Figure 2-3.  Importing hello-jni Android NDK project You will notice an error message on the console at the end of the import process, as shown in Figure 2-4. As you may recall, in the previous chapter you only downloaded the platform APIs for Android 4.0 (API Level 14) using the SDK Manager. The hello-jni project is developed for Android 1.5 (API Level 3). Figure 2-4.  Unable to resolve target API level 3 API levels are backward compatible. Instead of downloading API Level 3, using the Project Explorer view in Eclipse, right-click to com.example.hellojni.HelloJni project, and choose Properties from the context menu to launch the project properties dialog. The right pane of the project properties dialog contains the list of project properties categories in a tree format. Choose Android from the tree, and using the right pane, select Android 4.0 as the project build target (see Figure 2-5). Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 47 Figure 2-5.  Choose Android 4.0 as the project build target Click the OK button to apply the changes. Eclipse will rebuild the project using the selected project build target. Adding Native Support to Project The Import Android Project wizard only imports projects as Android Java projects. The native support needs to be added manually in order to include the native components into the build flow. Using the Project Explorer view in Eclipse, right-click to the com.example.hellojni.HelloJni project, hover on the Android Tools menu item, and choose “Add Native Support” from the context menu. The Add Android Native Support dialog will be launched, as shown in Figure 2-6. Since the project already contains a native project, you can leave the library name as is, and click to the Finish button to proceed. Figure 2-6.  Add Android native support Download at http://www.pin5i.com/ 48 CHAPTER 2: Exploring the Android NDK If this is the first time you are adding native support to a Java-only project, you can specify the preferred name of the shared library in this dialog and it will be used while auto-generating the build files as a part of the process. Running the Project Now that the project is ready, you can run it on the Android emulator. Choose Run from the top menu, and select Run from the submenu. Since this is the first time you are running this project, Eclipse will ask you to select how you would like to run the project through the Run As dialog. Choose Android Application from the list and click OK button to proceed. Android Emulator will be launched; the project 2-7. Android Emulator is Figure 2-7.  Android Emulator running the native project As you may have noticed, the process to run the project is exactly the same as running a Java-only project. Adding the native support to the project automatically incorporates the necessary steps into the build process transparently from the user. You can still check the Console view to watch the messages coming from the Android NDK build system, as shown in Figure 2-8. Figure 2-8.  Console view showing Android NDDKobwuinldlomaedssaatghetstp://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 49 Although Eclipse did a great job streamlining the entire build and deployment process for us, as stated earlier in this chapter, Eclipse is not a requirement to build Android NDK projects. The entire build process can be executed from the command line as well. Building from the Command Line In order to build the hello-jni project from the command line, first open up a command prompt in Windows or a Terminal window in Mac OS X or Linux, and change your directory to hello-jni project. Building an Android project with native components requires a two-step process. The first step is to build the native components, and the second step is to build the Java application and then package both Java application and its native components together. To build the native components, execute ndk-build on the command line. The ndk-build is a helper script that invokes the Android build system. As shown in Figure 2-9, Android NDK build script will output progress messages throughout the build process. Figure 2-9. Building the native components using ndk-build Now that those native components are properly built, you can proceed with the second step. The Android SDK build system is based on Apache ANT. Since this is the first time you are going to build the project from the command line, the Apache ANT build files should be generated first. Execute android update project –p . –n hello-jni –t android-14 --subprojects on the command line to generate the Apache ANT build files, as shown in Figure 2-10. Download at http://www.pin5i.com/ 50 CHAPTER 2: Exploring the Android NDK   Generating Apache ANT build files ant debug Examining the Structure of an Android NDK Project Let’s go back into Eclipse and study the structure of an Android application with native components. As shown in Figure 2-11, an Android project with native components contains a set of additional directories and files. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 51 Figure 2-11.  Structure of hello-jni Android NDK project  jni: This directory contains the source code for the native components plus the Android.mk build file describing how the native components should be built. The Android NDK build system refers to this directory as the NDK project directory and it expects to find it at project root.  libs: This directory gets created during the build process by the Android NDK build system. It contains individual subdirectories for target machine architecture that are specified, such as armeabi for the ARM. This directory gets incorporated into the APK file during the packaging process.  obj: This directory is an intermediate directory holding the object files that are produced after compiling the source code. Developers are not expected to touch this directory. The most important component of the Android NDK project here is the Android.mk build file, which describes the native components. Understanding the build system is the key to successfully using the Android NDK and all its components. Build System The Android NDK comes with its own build system that is based on GNU Make. The primary goal of this build system is to allow developers to only write very short build files to describe their native Android applications; the build system handles many details including the toolchain, platform, CPU, Download at http://www.pin5i.com/ 52 CHAPTER 2: Exploring the Android NDK and ABI specifics on behalf of the developer. Having the build process encapsulated allows the later updates of the Android NDK to add support for more toolchains, platforms, and system interfaces without requiring changes in the build files. The Android NDK build system is formed by multiple GNU Makefile fragments. The build system includes the necessary fragments based on type of the NDK project needed to render the build process. As shown in Figure 2-12, these build system fragments can be found in the build/core sub-directory of the Android NDK installation. Although developers are not expected to directly interface with these files, knowing their locations becomes highly beneficial when troubleshooting build-system–related problems. Figure 2-12.  Android NDK build system fragments In addition to those fragments, the Android NDK build system relies on two other files that are expected to be provided by the developer as a part of the NDK project: Android.mk and Application.mk. Let’s review them now. Android.mk Android.mk is a GNU Makefile fragment that describes the NDK project to the Android NDK build system. It is a required component of every NDK project. The build system expects it to be present in the jni sub-directory. Using the Project Explorer in Eclipse, double-click the Android.mk file to open it in the editor view. Listing 2-1 shows the contents of the Android.mk file from the hello-jni project. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 53 Listing 2-1.  Contents of Android.mk File from hello-jni Project # Copyright (C) 2009 The Alndroid Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # LOCAL_PATH := $(call my-dir)   include $(CLEAR_VARS)   LOCAL_MODULE := hello-jni LOCAL_SRC_FILES := hello-jni.c   include $(BUILD_SHARED_LIBRARY) Let’s go through this file line by line to better understand its syntax. Since this is a GNU Makefile fragment, its syntax is exactly the same as any other Makefile. Each line contains a single instruction. The lines starting with a hash (#) sign indicate a comment and they are not processed by the GNU Make tool. By the naming convention, the variable names are upper-case. The first instruction after the comments block is the definition of the LOCAL_PATH variable. As a requirement of the Android build system, the Android.mk file should always begin with the definition of LOCAL_PATH variable. LOCAL_PATH := $(call my-dir) The LOCAL_PATH is used by the Android build system to locate the source files. Since setting this variable to a hard-coded value is not appropriate, the Android build system provides a macro function called my-dir. By setting the variable to the return of the my-dir macro function, it gets set to the current directory. The CLEAR_VARS variable gets set by the Android build system to the location of clear-vars.mk fragment. Including this Makefile fragment clears the LOCAL_  variables such as LOCAL_MODULE, LOCAL_SRC_FILES, etc., with the exception of LOCAL_PATH. include $(CLEAR_VARS) This is needed because multiple build files and module definitions are parsed by the Android build system in a single execution, and the LOCAL_  variables are global. Clearing them prevent conflicts. Each native component is referred to as a module. Download at http://www.pin5i.com/ 54 CHAPTER 2: Exploring the Android NDK The LOCAL_MODULE variable is used to name these modules with a unique name. This line sets the name of the module to hello-jni LOCAL_MODULE := hello-jni since the module name is also used to name the generated file as a result of the build process. The build system adds the proper prefix and the suffix to the file. In this example, the hello-jni module will generate a shared library file, and it will be named as libhello-jni.so by the build system. The list of source files that will be built and assembled to produce the module is defined using the LOCAL_SRC_FILES variable. hello-jni module is produced by only one source file, but LOCAL_SRC_FILES variable can contain Android.mk file simply described BUILD_SHARED_LIBRARY variable is set by the Android NDK build system to the location of build-shared-library.mk file. This Makefile fragment contains the necessary build procedure to build and assemble the source files as a shared library: include $(BUILD_SHARED_LIBRARY) The hello-jni is a simple module; however, unless your module requires any special treatment, your Android.mk file will contain the exact same flow and instructions. Building Multiple Shared Libraries Depending on your application’s architecture, multiple shared library modules can also be produced from a single Android.mk file. In order to do so, multiple modules need to be defined in the Android.mk file, as shown in Listing 2-2. Listing 2-2.  Android.mk Build File with Multiple Shared Library Modules LOCAL_PATH := $(call my-dir)   # # Module 1 # include $(CLEAR_VARS)   LOCAL_MODULE := module1 LOCAL_SRC_FILES := module1.c Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 55 include $(BUILD_SHARED_LIBRARY)   # # Module 2 # include $(CLEAR_VARS)   LOCAL_MODULE := module2 LOCAL_SRC_FILES := module2.c   include $(BUILD_SHARED_LIBRARY) The Android NDK build system will produce libmodule1.so and libmodule2.so shared libraries after processing this Android.mk build file. Building Static Libraries Static libraries are also supported by the Android NDK build system. Static libraries are not directly consumable by the actual Android application, and they don’t get included into the application package. Static libraries can be used to build shared libraries. For example, when integrating third party code into an existing native project, instead of including the source code directly, the third party code can be compiled as a static library and then combined into the shared library, as shown in Listing 2-3. Listing 2-3.  Android.mk File Showing the Use of Static Library LOCAL_PATH := $(call my-dir)   # # 3rd party AVI library # include $(CLEAR_VARS)   LOCAL_MODULE := avilib LOCAL_SRC_FILES := avilib.c platform_posix.c   include $(BUILD_STATIC_LIBRARY)   # # Native module # include $(CLEAR_VARS)   LOCAL_MODULE := module LOCAL_SRC_FILES := module.c   LOCAL_STATIC_LIBRARIES := avilib   include $(BUILD_SHARED_LIBRARY) Download at http://www.pin5i.com/ 56 CHAPTER 2: Exploring the Android NDK Upon building the module as a static library, it can get consumed by the shared libraries by including its module name into the LOCAL_STATIC_LIBRARIES variable. Sharing Common Modules using Shared Libraries Static libraries allow you to keep your source code modular; however, when the static library gets linked into a shared library, it becomes part of that shared library. In the case of multiple shared libraries, linking with the same static library simply increases the application size due to multiple copies of the common module. In such cases, instead of building a static library, the common module can be built as a shared library, and the dependent modules then dynamically link to it to eliminate the duplicate copies (see Listing 2-4).   Android.mk File Showing Code Sharing Between Shared Libraries   rd party AVI library     include $(BUILD_SHARED_LIBRARY)   # # Native module 1 # include $(CLEAR_VARS)   LOCAL_MODULE := module1 LOCAL_SRC_FILES := module1.c   LOCAL_SHARED_LIBRARIES := avilib   include $(BUILD_SHARED_LIBRARY)   # # Native module 2 # include $(CLEAR_VARS)   LOCAL_MODULE := module2 LOCAL_SRC_FILES := module2.c   LOCAL_SHARED_LIBRARIES := avilib   include $(BUILD_SHARED_LIBRARY) Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 57 Sharing Modules between Multiple NDK Projects Using both the static and shared libraries, the common modules can be shared between modules. However, the caveat here is that all these modules should be part of the same NDK project. Starting from version R5, Android NDK also allows sharing and reusing modules between NDK projects. Considering the previous example, the avilib module can be shared between multiple NDK projects by doing the following:  First, move the avilib source code to a location outside the NDK project, such as C:\android\shared-modules\avilib. In order to prevent name conflicts, the directory structure can also include the module provider’s name, such as C:\android\shared-modules\transcode\avilib. Caution  The Android NDK build system does not accept the space character in shared module path.  As a shared module, avilib requires its own Android.mk file, as shown in Listing 2-5. Listing 2-5.  Android.mk File of the Shared avilib Module LOCAL_PATH := $(call my-dir)   # # 3rd party AVI library # include $(CLEAR_VARS)   LOCAL_MODULE := avilib LOCAL_SRC_FILES := avilib.c platform_posix.c   include $(BUILD_SHARED_LIBRARY)  Now the avilib module can be removed from the Android.mk file of the NDK project. A call to function macro import-module with parameter transcode/ avilib should be added to the end of the build file, as shown in Listing 2-6, to use this shared module. The import-module function macro call should be placed at the end of the Android.mk file to prevent any build system conflicts. Listing 2-6.  NDK Project Using the Shared Module # # Native module # include $(CLEAR_VARS)   LOCAL_MODULE := module LOCAL_SRC_FILES := module.c   Download at http://www.pin5i.com/ 58 CHAPTER 2: Exploring the Android NDK LOCAL_SHARED_LIBRARIES := avilib   include $(BUILD_SHARED_LIBRARY)   $(call import-module,transcode/avilib)  The import-module function macro needs to first locate the shared module and then import it into the NDK project. By default, only the < Android NDK>/ sources directory is searched by the import-module function macro. In order to include the c:\android\shared-modules directory into the search, define a new environment variable called NDK_MODULE_PATH and set it to the root directory of shared modules, such as c:\android\shared-modules.  You want to distribute your modules to other parties without distributing your source code.  You want to use prebuilt version of your shared modules to speed up the builds. Although they are already compiled, prebuild modules still required an Android.mk build file, as shown in Listing 2-7. Listing 2-7.  Android.mk File for Prebuilt Shared Module LOCAL_PATH := $(call my-dir)   # # 3rd party prebuilt AVI library # include $(CLEAR_VARS)   LOCAL_MODULE := avilib LOCAL_SRC_FILES := libavilib.so   include $(PREBUILT_SHARED_LIBRARY) The LOCAL_SRC_FILES variable, instead of pointing to the source files, points to the location of the actual prebuilt library relative to the LOCAL_PATH. Caution  The Prebuilt library definition does not carry any information about the actual machine architecture that the prebuilt library is built for. Developers need to ensure that the prebuilt library is built for the same machine architecture as the NDK project. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 59 The PREBUILT_SHARED_LIBRARY variable points to the prebuilt-shared-library.mk Makefile fragment. It does not build anything, but it copies the prebuilt library to the NDK project’s libs directory. By using PREBUILT_STATIC_LIBRARY variable, static libraries can also be used as prebuilt libraries the same way as the shared libraries. NDK project can use the prebuilt library the same way as the ordinary shared libraries. ... LOCAL_SHARED_LIBRARIES := avilib ... Building Standalone Executable The recommended and supported way of using native components on Android platform is through packaging them as shared libraries. However, in order to facilitate testing and quick prototyping, Android NDK also provides support for building a standalone executable. The standalone executables are regular Linux applications that can be copied to the Android device without being packaged into an APK file, and they can get executed directly without being loaded through a Java application. Standalone executables can be produced by importing the BUILD_EXECUTABLE variable in the Android.mk build file instead of BUILD_SHARED_LIBRARY, as shown in Listing 2-8. Listing 2-8. Android.mk File for Standalone Executable Module # # Native module standlone executable # include $(CLEAR_VARS) LOCAL_MODULE := module LOCAL_SRC_FILES := module.c LOCAL_STATIC_LIBRARIES := avilib include $(BUILD_EXECUTABLE) The BUILD_EXECUTABLE variable points to the build-executable.mk Makefile fragment that contains the necessary build steps to produce a standalone executable on Android platform. The standalone executable gets placed into libs/ directory with the same name as the module. Although it is placed into this directory, it does not get included into the APK file during the packaging phase. Other Build System Variables Besides the variables covered in the previous sections, there are other variables that are supported by the Android NDK build system. This section will briefly mention them. The variables that are defined by the build system are  TARGET_ARCH: Name of the target CPU architecture, such as arm.  TARGET_PLATFORM: NamDoewonflothade atat rhgtetpt:A//nwdwrowid.ppinla5it.fcoormm/, such as android-3. 60 CHAPTER 2: Exploring the Android NDK  TARGET_ARCH_ABI: Name of the target CPU architecture and the ABI, such as armeabi-v7a.  TARGET_ABI: Concatenation of target platform and ABI, such as android-3armeabi-v7a. The variables that can be defined as a part of the module description are  LOCAL_MODULE_FILENAME: Optional variable to redefine the name of the generated output file. By default the build system uses the value of LOCAL_MODULE as the name of the generated output file, but it can be overridden using this variable.  LOCAL_CPP_EXTENSION: The default extension of C++ source files is .cpp. This variable can be used to specify one or more file extensions for the C++ source code. ... LOCAL_CPP_EXTENSION := .cpp .cxx ...  LOCAL_CPP_FEATURES: Optional variable to indicate that the module relies on specific C++ features such as RTTI, exceptions, etc. ... LOCAL_CPP_FEATURES := rtti ...  LOCAL_C_INCLUDES: Optional list of paths, relative to NDK installation directory, to search for header files. ... LOCAL_C_INCLUDES := sources/shared-module LOCAL_C_INCLUDES := $(LOCAL_PATH)/include ...  LOCAL_CFLAGS: Optional set of compiler flags that will be passed to the compiler while compiling the C and C++ source files. ... LOCAL_CFLAGS := − DNDEBUG –DPORT = 1234 ...  LOCAL_CPP_FLAGS: Optional set of compiled flags that will be passed to the compiler while compiling the C++ source files only.  LOCAL_WHOLE_STATIC_LIBRARIES: A variant of LOCAL_STATIC_LIBRARIES that indicates that the whole content of the static library should be included in the generated shared library. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 61 Tip  LOCAL_WHOLE_STATIC_LIBRARIES is very useful when there are circular dependencies between several static libraries.  LOCAL_LDLIBS: Optional list of linker flags that will be passed to the linker while linking the object files to generate the output file. It is primarily used to pass the list of system libraries to dynamically link with. For example, to link with the Android NDK logging library, use this code: LOCAL_LDFLAGS := − llog  LOCAL_ALLOW_UNDEFINED_SYMBOLS: Optionally disables the checking for missing symbols in the generated file. When not defined, the linker will produce error messages indicating the missing symbols.  LOCAL_ARM_MODE: Optional and ARM machine architecture-specific variable indicating the type of ARM binary to be generated. By default, the build system generates in thumb mode with 16-bit instructions, but this variable can be set to arm to indicate that the 32-bit instructions should be used. LOCAL_ARM_MODE := arm This variable changes the build system behavior for the entire module; the .arm extension can also be used to only build specific files in arm mode. LOCAL_SRC_FILES := file1.c file2.c.arm  LOCAL_ARM_NEON: Optional and ARM machine architecture-specific variable indicating that ARM Advanced Single Instruction Multiple Date (SIMD) (a.k.a. NEON) intrinsics should be enabled in the source files. LOCAL_ARM_NEON := true This variable changes the build system behavior for the entire module; the .neon extension can also be used to only build specific files with NEON intrinsics. LOCAL_SRC_FILES := file1.c file2.c.neon  LOCAL_DISABLE_NO_EXECUTE: Optional variable to disable the NX Bit security feature. NX Bit, which stands for Never Execute, is a technology used in CPUs to segregate areas of memory for use by either code or storage. This prevents malicious software from taking control of the application by inserting its code into the application’s storage memory area.  LOCAL_DISABLE_NO_EXECUTE := true  LOCAL_EXPORT_CFLAGS: This variable allows recording a set of compiler flags that will be added to the LOCAL_CFLAGS definition of any other module that is using this module through either LOCAL_STATIC_LIBRARIES or LOCAL_SHARED_LIBRARIES. LOCAL_MODULE := aviliDb ownload at http://www.pin5i.com/ 62 CHAPTER 2: Exploring the Android NDK ... LOCAL_EXPORT_CFLAGS := − DENABLE_AUDIO ... LOCAL_MODULE := module1 LOCAL_CFLAGS := − DDEBUG ... LOCAL_SHARED_LIBRARIES := avilib The compiler will get executed with flags –DENABLE_AUDIO –DDEBUG while building the module1.  LOCAL_EXPORT_CPPFLAGS: Same as the LOCAL_EXPORT_CLAGS but for C++ code-specific compiler flags.  LOCAL_EXPORT_LDFLAGS: Same as the LOCAL_EXPORT_CFLAGS but for the linker flags.  LOCAL_EXPORT_C_INCLUDES: This variable allows recording set include paths that will be added to the LOCAL_C_INCLUDES definition of any other module that is using this module through either LOCAL_STATIC_LIBRARIES or LOCAL_SHARED_ LIBRARIES.  LOCAL_SHORT_COMMANDS: This variable should be set to true for modules with a very high number of sources or dependent static or shared libraries. Operating systems like Windows only allow a maximum of 8191 characters on the command line; this variable makes the build commands shorter than this limit by breaking them. This is not recommended for smaller modules since enabling it will make the build slower.  LOCAL_FILTER_ASM: This variable defines the application that will be used to filter the assembly files from the LOCAL_SRC_FILES. Other Build System Function Macros This section covers the other function macros that are supported by the Android NDK build system.  all-subdir-makefiles: Returns a list of Android.mk build files that are located in all sub-directories of the current directory. For example, calling the following includes all Android.mk files in the sub-directories into the build process: include $(call all-subdir-makefiles)  this-makefile: Returns the path of the current Android.mk build file.  parent-makefile: Returns the path of the parent Android.mk build file that included the current build file.  grand-parent-makefile: Same as the parent-makefile but for the grandparent. Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 63 Defining New Variables Developers can define other variables to simplify their build files. The names beginning with LOCAL_ and NDK_ prefixes are reserved for use by the Android NDK build system. It is recommended to use MY_ prefix for variables that are defined by the developers, as shown in Listing 2-9. Listing 2-9.  Android.mk File Showing the Use of Developer-Defined Intermediate Variables ... MY_SRC_FILES := avilib.c platform_posix.c LOCAL_SRC_FILES := $(addprefix avilib/, $(MY_SRC_FILES)) ... Conditional Operations The Android.mk build file can also contain conditional operations on these variables, for example, to include a different set of source files per architecture, as shown in Listing 2-10. Listing 2-10.  Android.mk Build File with Conditional Operation ... ifeq ($(TARGET_ARCH),arm) LOCAL_SRC_FILES + = armonly.c else LOCAL_SRC_FILES + = generic.c endif ... Application.mk The Application.mk is an optional build file that is used by the Android NDK build system. Same as the Android.mk file, it is also placed in the jni directory. Application.mk is also a GNU Makefile fragment. Its purpose is to describe which modules are needed by the application; it also defines the variables that are common for all modules. The following variables are supported in the Application.mk build file:  APP_MODULES: By default the Android NDK build system builds all modules that are declared by the Android.mk file. This variable can override this behavior and provide a space-separated list of modules that need to be built.  APP_OPTIM: This variable can be set to either release or debug to alter the optimization level of the generated binaries. By default the release mode is used and the generated binaries are highly optimized. This variable can be set to debug mode to generate un-optimized binaries that are easier to debug.  APP_CLAGS: This variable lists the compiler flags that will be passed to the compiler while compiling C and C++ source files for any of the modules.  APP_CPPFLAGS: This variable lists the compilers flags that will be passed to the compiler while compiling the C++ source files for any of the modules. Download at http://www.pin5i.com/ 64 CHAPTER 2: Exploring the Android NDK  APP_BUILD_SCRIPT: By default the Android NDK build system looks for the Android.mk build file under the jni sub-directory of the project. This behavior can be altered by using this variable, and a different build file can be used.  APP_ABI: By default Android NDK build system generates binaries for armeabi ABI. This variable can be used to alter this behavior and generate binaries for a different ABI, like so: APP_ABI := mips Additionally, more than one ABI can be set APP_ABI := armeabi mips in order to generate binaries for all supported ABIs APP_ABI := all  APP_STL: By default the Android NDK build system uses the minimal STL runtime library, also known as the system library. This variable can be used to select a different STL implementation. APP_STL := stlport_shared  APP_GNUSTL_FORCE_CPP_FEATURES: Similar to LOCAL_CPP_EXTENSIONS variable, this variable indicates that all modules rely on specific C++ features such as RTTI, exceptions, etc.  APP_SHORT_COMMANDS: Similar to the LOCAL_SHORT_COMMANDS variable, this variable makes the build system use shorter commands on projects with high amount of source files. Using the NDK-Build Script As stated earlier in this chapter, the Android NDK build system is started by executing the ndk-build script. The script can take a set of arguments to allow you to easily maintain and control the build process.  By default the ndk-build script expects to be executed within the main project directory. The –C argument can be used to specify the location the NDK project on the command line so that the ndk-build script can be started from an arbitrary location. ndk-build –C /path/to/the/project  The Android NDK build system does not rebuild objects if their source file is not being modified. You can execute the ndk-build script using the –B argument to force rebuilding all source code. ndk-build -B Download at http://www.pin5i.com/ CHAPTER 2: Exploring the Android NDK 65  In order to clean the generated binaries and object files, you can execute ndk-build clean on the command line. Android NDK build system removes the generated binaries. ndk-build clean  The Android NDK build system relies on GNU Make tool to build the modules. By default GNU Make tool executes one build command at a time, waiting for it to finish before executing the next one. GNU Make can execute build commands in parallel if the –j argument is provided on the command line. Optionally, the number of commands that can be executed in parallel can also be specified as a number following the argument. ndk-build –j 4 Troubleshooting Build System Problems The Android NDK build system comes with extensive logging support for troubleshooting build system related problems. This section briefly explores them. Logging of the internal state of the Android NDK build system can be enabled by typing ndk-build NDK_LOG = 1 on the command line. The Android NDK build system will produce extensive amount of logging with log messages prefixed with “Android NDK: ” (see Figure 2-13). Figure 2-13.  Ndk-build script displaying debug information If you are only interested in seeing the actual build commands that get executed, you can type ndk-build V = 1 on the command line. Android NDK will only display the build commands, as shown in Figure 2-14. Download at http://www.pin5i.com/ 66 CHAPTER 2: Exploring the Android NDK   Ndk-build script displaying the build commands Download at http://www.pin5i.com/ 3 Chapter Communicating with Native Code using JNI In the previous chapter, you started exploring the Android NDK by going through its components, its structure, and its build system. Using this information you can now build and package any kind of native code with your Android applications. In this chapter, you will focus on the integration part by using the Java Native Interface (JNI) technology to enable the Java application and the native code to communicate with each other. What is JNI? The JNI is a powerful feature of the Java programming language. It allows certain methods of Java classes to be implemented natively while letting them be called and used as ordinary Java methods. These native methods can still use Java objects in the same way that the Java code uses them. Native methods can create new Java objects or use objects created by the Java application, which can inspect, modify, and invoke methods on these objects to perform tasks. Starting with an Example Before going into the details of JNI technology, let’s walk through an example application. This will provide you with the foundation necessary to experiment with the APIs and concepts as you work through the chapter. By going through the example application, you will learn the following key concepts:  How the native methods are called from Java code  Declaration of native methods  Loading the native modules from shared libraries  Implementing the native methods in C/C++ 67 Download at http://www.pin5i.com/ 68 CHAPTER 3: Communicating with Native Code using JNI To begin, open the Eclipse IDE and go into the hello-jni sample project that you imported in the previous chapter. The hello-jni application is a single activity Android application. Using the Project Explorer view, expand the src directory, and then expand the com.example.hellojni package. Open the HelloJni activity in the editor view by double-clicking the HelloJni.java source file. The HelloJni activity has a very simple user interface that is formed by a single android.widget.TextView widget. In the body of activity’s onCreate method, the string value of the TextView widget is set to the return value of stringFromJNI method, as shown in Listing 3-1. Listing 3-1.  HelloJni Activity onCreate Method /** Called when the activity is first created. */ { super.onCreate(savedInstanceState);   /* Create a TextView and set its content. * the text is retrieved by calling a native * function. */ TextView tv = new TextView(this); tv.setText( stringFromJNI() ); setContentView(tv); There is nothing new here. You will find the stringFromJNI method just below the onCreate method. Declaration of Native Methods As shown in Listing 3-2, the method declaration of stringFromJNI contains the native keyword to inform the Java compiler that the implementation of this method is provided in another language. The method declaration is terminated with a semicolon, the statement terminator symbol, because the native methods do not have a body. Listing 3-2.  Method Declaration of Native stringFromJNI Method /* A native method that is implemented by the * 'hello-jni' native library, which is packaged * with this application. */ public native String stringFromJNI(); Although the virtual machine now knows that the method is implemented natively, it still does not know where to find the implementation. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 69 Loading the Shared Libraries As mentioned in the previous chapter, native methods are compiled into a shared library. This shared library needs to be loaded first for the virtual machine to find the native method implementations. The java.lang.System class provides two static methods, load and loadLibrary, for loading shared libraries during runtime. As shown in Listing 3-3, the HelloJni activity loads the shared library hello-jni. Listing 3-3. HelloJni Activity Loading the hello-jni Shared Library /* this is used to load the 'hello-jni' library on application * startup. The library has already been unpacked into * /data/data/com.example.HelloJni/lib/libhello-jni.so at * installation time by the package manager. */ static { System.loadLibrary("hello-jni"); } The loadLibrary method is called within the static context because you want the native code implementations to be loaded as the class is loaded and initialized for the first time. Bear in mind that Java technology is designed with the goal of being platform independent. As a part of the Java framework API, the design of loadLibrary is not any different. Although the actual shared library produced by Android NDK is named libhello-jni.so, the loadLibrary method only takes the library name, hello-jni, and adds the necessary prefix and suffix as required by the operating system in use. The library name is same as the module name that is defined in Android.mk using the LOCAL_MODULE build system variable. The argument to loadLibrary does not include the location of the shared library either. The Java library path, system property java.library.path, holds the list of directories that the loadLibrary method will search for in the shared libraries. The Java library path on Android contains /vendor/lib and /system/lib. The caveat here is that loadLibrary will load the shared library as soon as it finds a library with the same name while going through the Java library path. Since the first set of directories in the Java library path is the Android system directories, Android developers are strongly encouraged to pick unique names for the shared libraries in order to prevent any name clashes with the system libraries. Now let’s look into the native code to see how the native method is declared and implemented. Implementing the Native Methods Using the Project Explorer view, expand the jni directory and double-click the hello-jni.c source file to open it in the editor view. The C source code starts by including the jni.h header file, as shown in Listing 3-4. This header file contains definitions of JNI data types and functions. Listing 3-4. Native Implementation of stringFromJNI Method #include #include Download at http://www.pin5i.com/ 70 CHAPTER 3: Communicating with Native Code using JNI ... jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return (*env)->NewStringUTF(env, "Hello from JNI !"); } The stringFromJNI native method is also declared with a fully qualified function named Java_com_example_hellojni_HelloJni_stringFromJNI. This explicit function naming allows the virtual machine to automatically find native functions in loaded shared libraries. javah, to automate this task. The javah tool parses a Java class file for native methods  < Eclipse Workspace>/com.example.hellojni. , where the HelloJni project is imported. The javah tool operates on compiled Java class parse, like so: javah –classpath bin/classes com.example.hellojni.HelloJni The javah tool will parse the com.example.hellojni.HelloJni class file, and it will generate the C/C++ header file as com_example_hellojni_HelloJni.h, as shown in Listing 3-5. Listing 3-5.  The com_example_hellojni_HelloJni.h Header File /* DO NOT EDIT THIS FILE - it is machine generated */ #include < jni.h> /* Header for class com_example_hellojni_HelloJni */   #ifndef _Included_com_example_hellojni_HelloJni #define _Included_com_example_hellojni_HelloJni #ifdef __cplusplus extern "C" { #endif /* * Class: com_example_hellojni_HelloJni * Method: stringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject);   Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 71 /* * Class: com_example_hellojni_HelloJni * Method: unimplementedStringFromJNI * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_unimplementedStringFromJNI (JNIEnv *, jobject);   #ifdef __cplusplus } #endif #endif The C/C++ source file simply needs to include this header file and provide the implementation for the native methods, as shown in Listing 3-6. Listing 3-6.  The com_example_hellojni_HelloJni.c Source File #include "com_example_hellojni_HelloJni.h"   JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jobject thiz) { return (*env)->NewStringUTF(env, "Hello from JNI !"); } Instead of running the javah tool each time from the command line, it can be integrated into Eclipse as an external tool to streamline the process of generating the header files. Running from Eclipse IDE Open the Eclipse IDE, and choose Run ➤ External Tools External Tools Configurations from the top menu bar. Using the External Tools Configurations dialog, select Program, and then click the New launch configuration button. Using the Main tab, fill in the tool information as follows and as shown in Figure 3-1:  Name: Generate C and C++ Header File  Location: ${system_path:javah}  Working Directory: ${project_loc}/jni  Arguments: -classpath "${project_classpath};${env_var:ANDROID_SDK_HOME}/ platforms/android-14/android.jar" ${java_type_name} Download at http://www.pin5i.com/ 72 CHAPTER 3: Communicating with Native Code using JNI Figure 3-1.  The javah external tool configuration You will need to replace the semicolon symbol with colon symbol on Mac OS X and Linux platforms. Switch to the Refresh tab; put a checkmark next to the “Refresh resource upon completion” and select “The project containing the selected resource” from the list, as shown in Figure 3-2. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 73 Figure 3-2.  Refresh project upon running the javah tool Switch to the Common tab, and put a checkmark next to the External Tools under the “Display in favorites menu” group, as shown in Figure 3-3. Figure 3-3.  Display the javah tool in the favorites menu Click the OK button to save the external tool configuration. In order to test the new configuration, using the Project Explorer view, select the HelloJni class, then choose Run ➤ External Tools ➤ Generate C and C++ Header File. The javah tool will parse the selected class file for native methods, and it will generate a C/C++ header file called com_example_hellojni_HelloJni.h under the jni directory with the methoDdodwenslocardipatitohntstp. ://www.pin5i.com/ 74 CHAPTER 3: Communicating with Native Code using JNI Now that you have automated the way to generate the native method declarations, let’s look into the generated method declarations more in detail. Method Declarations Although the Java method stringFromJNI does not take any parameters, the native function takes two parameters, as shown in Listing 3-7. Listing 3-7.  Mandatory Parameters of Native Methods JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject); JNIEnv, is an interface pointer that points to a function table of available JNI jobject, is a Java object reference to the HelloJni class instance. interface pointer. JNIEnv is a pointer to thread-local data, which in turn contains a pointer to JNIEnv interface pointer as their Caution  The JNIEnv interface pointer that is passed into each native method call is also valid in the thread associated with the method call. It cannot be cached and used by other threads. Depending on whether the native code is a C or C++ source file, the syntax for calling JNI functions differs. In C code, JNIEnv is a pointer to JNINativeInterface structure. This pointer needs to be dereferenced first in order to access any JNI function. Since the JNI functions in C code do not know the current JNI environment, the JNIEnv instance should be passed as the first argument to each JNI function call, like so: return (*env)->NewStringUTF(env, "Hello from JNI !"); In C++ code, JNIEnv is actually a C++ class instance. JNI functions are exposed as member functions. Since JNI methods have access to the current JNI environment, the JNI method calls do not require the JNIEnv instance as an argument. In C++, the same code looks like return env->NewStringUTF("Hello from JNI !"); Instance vs. Static Methods The Java programming language has two types of methods: instance methods and static methods. Instance methods are associated with a class instance, and they can only be called on a class instance. Static methods are not associated with a class instance, and they can be called directly Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 75 from a static context. Both instance and static methods can be declared as native, and their implementations can be provided as native code through the JNI technology. Native instance methods get the instance reference as their second parameter as a jobject value, as shown in Listing 3-8. Listing 3-8.  Native Instance Method Definition JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jobject thiz); Since static methods are not tied to an instance, they get the class reference instead as their second parameter as a jclass value, shown in Listing 3-9. Listing 3-9.  Native Static Method Definition JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jclass clazz); As you may have noticed in the method definitions that JNI provides its own data types to expose Java types to native code. Data Types There are two kinds of data types in Java:  Primitive types: boolean, byte, char, short, int, long, float, and double  Reference types: String, arrays, and other classes Let’s take a closer look at each of these data types. Primitive Types Primitive types are directly mapped to C/C++ equivalents, as shown in Table 3-1. The JNI uses type definitions to make this mapping transparent to developers. Download at http://www.pin5i.com/ 76 CHAPTER 3: Communicating with Native Code using JNI Table 3-1.  Java Primitive Data Types Java Type JNI Type Boolean Jboolean Byte Jbyte Char Jchar Short Jshort Int Jint Long Jlong Float Jfloat Double Jdouble C/C++ Type unsigned char char unsigned short short Int long long float double Size Unsigned 8 bits Signed 8 bits Unsigned 16 bits Signed 16 bits Signed 32 bits Signed 64 bits 32 bits 64 bits 3-2. Their internal data structure is not Table 3-2.  Java Reference Type Mapping Java Type Native Type java.lang.Class jclass java.lang.Throwable jthrowable java.lang.String jstring Other objects jobject java.lang.Object[] jobjectArray boolean[] jbooleanArray byte[] jbyteArray char[] jcharArray short[] jshortArray int[] jintArray long[] jlongArray float[] jfloatArray double[] jdoubleArray Other arrays Jarray Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 77 Operations on Reference Types Reference types are passed as opaque references to the native code rather than native data types, and they cannot be consumed and modified directly. JNI provides a set of APIs for interacting with these reference types. These APIs are provided to the native function through the JNIEnv interface pointer. In this section, you will briefly explore these APIs pertinent to the following types and components:  Strings  Arrays  NIO Buffers  Fields  Methods String Operations Java strings are handled by the JNI as reference types. These reference types are not directly usable as native C strings. JNI provides the necessary functions to convert these Java string references to C strings and back. Since Java string objects are immutable, JNI does not provide any function to modify the content of an existing Java string. JNI supports both Unicode and UTF-8 encoded strings, and it provides two sets of functions through the JNIEnv interface pointer to handle each of these string encodings. New String New string instances can be constructed from the native code by using the functions NewString for Unicode strings and NewStringUTF for UTF-8 strings. As shown in Listing 3-10, these functions take a C string and returns a Java string reference type, a jstring value. Listing 3-10.  New Java String from a Given C String jstring javaString; javaString = (*env)->NewStringUTF(env, "Hello World!"); In case of a memory overflow, these functions return NULL to inform the native code that an exception has been thrown in the virtual machine so the native code should not continue. We will get back to the topic of exception handling later in this chapter. Converting a Java String to C String In order to use a Java string in native code, it needs to be converted to a C string first. Java strings can be converted to C strings using the functions GetStringChars for Unicode strings and GetStringUTFChars for UTF-8 strings. These functions take an optional third argument, a pass-byreference output parameter called isCopy that can allow the caller to determine whether the returned C string address points to a copDyoowrntlhoaedpaint nhettdp:o//bwjewcwt i.npinth5ei.choema/p. This is shown in Listing 3-11. 78 CHAPTER 3: Communicating with Native Code using JNI Listing 3-11.  Converting a Java String to C String const jbyte* str; jboolean isCopy;   str = (*env)->GetStringUTFChars(env, javaString, &isCopy); if (0 != str) { printf("Java string: %s", str);   if (JNI_TRUE == isCopy) { printf("C string is a copy of the Java string."); } else { printf("C string points to actual string."); } GetStringChars and GetStringUTFChars functions need to be ReleaseStringChars for Unicode strings and ReleaseStringUTFChars for   Releasing the C Strings Returned by JNI Functions (*env)->ReleaseStringUTFChars(env, javaString, str); Array Operations Java arrays are handled by the JNI as reference types. The JNI provides the necessary functions to access and manipulate Java arrays. New Array New array instances can be constructed from the native code using the NewArray function, with the  being Int, Char, Boolean, etc. such as NewIntArray. As shown in Listing 3-13, the size of the array should be provided as a parameter when invoking these functions. Listing 3-13.  New Java Array from Native Code jintArray javaArray; javaArray = (*env)->NewIntArray(env, 10); if (0 != javaArray) { /* You can now use the array. */ } Same as the NewString function, in case of a memory overflow, the NewArray function will return NULL to inform the native code that an exception has been thrown in the virtual machine and that the native code should not continue. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 79 Accessing the Array Elements JNI provides two types of access to Java array elements. Code can either get a copy of the array as a C array, or it can ask JNI to get a direct pointer to the array elements. Operating on a Copy The GetArrayRegion function copies the given primitive Java array to the given C array, as shown in Listing 3-14. Listing 3-14. Getting a Copy of Java Array Region as a C Array jint nativeArray[10]; (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray); The native code can then use and modify the array elements as an ordinary C array. When the native code wants to commit its changes back to the Java array, the SetArrayRegion function can be used to copy the C array back to Java array, as shown in Listing 3-15. Listing 3-15. Committing Back the Changes from C Array to Java Array (*env)->SetIntArrayRegion(env, javaArray, 0, 10, nativeArray); When the array sizes are big, copying the array in order to operate on them causes performance problems. In such cases, the native code should either only get or set the region of the array elements instead of getting the entire array, if possible. Otherwise, JNI provides a different set of functions to obtain a direct pointer to the array elements instead of their copies. Operating on Direct Pointer The Get ArrayElements function allows the native code to get a direct pointer to array elements, when possible. As shown in Listing 3-16, the function takes a third optional parameter, a pass-by-reference output parameter called isCopy that can allow the caller to determine whether the returned C array points to a copy or the pinned array in the heap. Listing 3-16. Getting a Direct Pointer to Java Array Elements jint* nativeDirectArray; jboolean isCopy; nativeDirectArray = (*env)->GetIntArrayElements(env, javaArray, &isCopy); JNI does not provide a set method, since the array elements can be accessed and manipulated as an ordinary C array. JNI requires the native code to release these pointers when it finishes; otherwise memory leaks happen. JNI provides the ReleaseArrayElemens functions to enable native code to release the C arrays that are returned by GetArrayElements function calls, as shown in Listing 3-17. Download at http://www.pin5i.com/ 80 CHAPTER 3: Communicating with Native Code using JNI Listing 3-17.  Releasing the Direct Pointer to Java Array Elements (*env)->ReleaseIntArrayElements(env, javaArray, nativeDirectArray, 0); This function takes a fourth parameter, the release mode. Table 3-3 contains a list of supported release modes. Table 3-3.  Supported Release Modes Release Mode Action 0 JNI_COMMIT JNI_ABORT Copy back the content and free the native array. Copy back the content but do not free the native array. This can be used for periodically updating a Java array. Free the native array without copying its content. New Direct Byte Buffer Native code can create a direct byte buffer that will be used by the Java application by providing a native C byte array as the basis. The NewDirectByteBuffer is used in Listing 3-18. Listing 3-18.  New Byte Buffer Based on the Given C Byte Array unsigned char* buffer = (unsigned char*) malloc(1024); ... jobject directBuffer; directBuffer = (*env)->NewDirectByteBuffer(env, buffer, 1024); Note  The memory allocated in native methods is out of the scope and control of the virtual machine’s garbage collector. Native functions should manage their memory properly by freeing the unused allocations to prevent memory leaks. Getting the Direct Byte Buffer The direct byte buffer can also be created in the Java application. Native code can use the GetDirectBufferAddress function call to obtain the memory address of the native byte array, as shown in Listing 3-19. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 81 Listing 3-19.  Getting the Native Byte Array from the Java Byte Buffer unsigned char* buffer; buffer = (unsigned char*) (*env)->GetDirectBufferAddress(env, directBuffer); Accessing Fields Java has two types of fields: instance fields and static fields. Each instance of a class owns its copy of the instance fields, whereas all instances of a class share the same static fields. The JNI provides functions to access both field types. Listing 3-20 shows an example of a Java class with one static and one instance field. Listing 3-20.  Java Class with Both Static and Instance Fields public class JavaClass { /** Instance field */ private String instanceField = "Instance Field";   /** Static field */ private static String staticField = "Static Field";   ... } Getting the Field ID The JNI provides access to both types of fields through field IDs. You can obtain field IDs through the class object for the given instance. The class object is obtained through the GetObjectClass function, as shown in Listing 3-21. Listing 3-21.  Getting the Class from an Object Reference jclass clazz; clazz = (*env)->GetObjectClass(env, instance); Depending on the field type, there are two functions to obtain the field ID from the class. The GetFieldId function is for instance fields, as shown in Listing 3-22. Listing 3-22.  Getting the Field ID of an Instance Field jfieldID instanceFieldId; instanceFieldId = (*env)->GetFieldID(env, clazz, "instanceField", "Ljava/lang/String;"); The GetStaticFieldId is for static fields, as shown in Listing 3-23. Both functions return the field ID as a jfieldID type. Download at http://www.pin5i.com/ 82 CHAPTER 3: Communicating with Native Code using JNI Listing 3-23.  Getting the Field ID of a Static Field jfieldID staticFieldId; staticFieldId = (*env)->GetStaticFieldID(env, clazz, "staticField", "Ljava/lang/String;"); The last parameter of both functions takes the field descriptor that represents the field type in Java. In the example code, "Ljava/lang/String" indicates that the field type is a String. We will get back to this later in this chapter. Tip  The field IDs can be cached in order to improve application performance. Always cache the most frequently used field ids.   Getting an Instance Field GetField function Use the GetStaticField function for static fields, as shown in Listing 3-25. Listing 3-25.  Getting a Static Field jstring staticField; staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId); In case of a memory overflow, both of these functions can return NULL, and the native code should not continue to execute. Tip  Getting the value of a single field takes two or three JNI function calls. Native code reaching back to Java to obtain values of each individual field adds extra overhead to the application and leads to poorer performance. It is strongly recommended to pass all needed parameters to native method calls instead of having the native code reach back to Java. Calling Methods As with fields, there are two types of methods in Java: instance methods and static methods. The JNI provides functions to access both types. Listing 3-26 shows a Java class that contains one static method and one instance method. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 83 Listing 3-26.  Java Class with Both Instance and Static Methods public class JavaClass { /** * Instance method. */ private String instanceMethod() { return "Instance Method"; } /** * Static method. */ private static String staticMethod() { return "Static Method"; }   ... } Getting the Method ID The JNI provides access to both types of methods through method IDs. You can obtain method IDs through the class object for the given instance. Use the GetMethodID function to obtain the method ID of an instance method, as shown in Listing 3-27. Listing 3-27.  Getting the Method ID of an Instance Method jmethodID instanceMethodId; instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;"); Use the GetStaticMethodID function to get the method ID of a static field, as shown in Listing 3-28. Both functions return the method ID as a jmethodID type. Listing 3-28.  Getting the Method ID of a Static Method jmethodID staticMethodId; staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;"); Like the field ID getter methods, the last parameter of both functions takes the method descriptor. It represents the method signature in Java. Tip  The method IDs can be cached in order to improve application performance. Always cache the most frequently used method ids. Download at http://www.pin5i.com/ 84 CHAPTER 3: Communicating with Native Code using JNI Calling the Method Using the method ID, you can call the actual method through the CallMethod function for instance methods, as shown in Listing 3-29. Listing 3-29. Calling an Instance Method jstring instanceMethodResult; instanceMethodResult = (*env)->CallStringMethod(env, instance, instanceMethodId); Use the CallStaticField function for static methods, as shown in Listing 3-30. clazz, staticMethodId); NULL and the native code should Tip  Transitions between Java and native code is a costly operation. It is strongly recommended that you take this into account when deciding to split between Java and native code. Minimizing these transitions can benefit the application performance greatly. Field and Method Descriptors As mentioned in the previous two sections, getting both the field ID and the method ID requires the field and method descriptors. Both the field and the method descriptors can be generated by using the Java type signature mapping shown in Table 3-4. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 85 Table 3-4.  Java Type Signature Mapping Java Type Boolean Byte Char Short Int Long Float Double fully-qualified-class type[] method type Signature Z B C S I J F D Lfully-qualified-class; [type (arg-type)ret-type Manually producing the field and method descriptors by using the type signature mapping and keeping them in sync with the Java code can be a cumbersome task. Java Class File Disassembler: javap JDK comes with a command line Java class file disassembler called javap. This tool can be used to extract the field and method descriptors from the compiled class files. Running from Command Line Using the command line, change the directory to /com.example.hellojni. HelloJni, where the HelloJni project is imported. The javap tool operates on compiled Java class files. Invoke the javap tool with the location of compiled class files and the name of the Java class to disassemble, like so: javap –classpath bin/classes –p –s com.example.hellojni.HelloJni The javap tool will disassemble the com.example.hellojni.HelloJni class file and will output the field and method signatures, as shown in Figure 3-4. Download at http://www.pin5i.com/ 86 CHAPTER 3: Communicating with Native Code using JNI   The javap tool output Run ➤ External Tools Configurations… from the top menu bar. Using the External Tool Configurations dialog, select Program, and then click the New launch configuration button. Using the Main tab, fill in the tool information as follows and as shown in Figure 3-5:  Name: Java Class File Disassembler  Location: ${system_path:javap}  Working Directory: ${project_loc}  Arguments: -classpath "${project_classpath};${env_var:ANDROID_SDK_HOME}/ platforms/android-14/android.jar" –p –s ${java_type_name} Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 87 Figure 3-5.  The javap external tool configuration You will need to replace the semicolon symbol with colon symbol on Mac OS X and Linux platforms. Switch to the Common tab, and put a checkmark next to the External Tools under the “Display in favorites menu” group, as described earlier. Click the OK button to save the external tool configuration. In order to test the new configuration, using the Project Explorer view, select the HelloJni class, then choose Run ➤ External Tools ➤ Java Class File Disassembler. The console view will show the output of the javah tool, as shown in Figure 3-6. Download at http://www.pin5i.com/ 88 CHAPTER 3: Communicating with Native Code using JNI   Console showing the javap tool output called catching an exception. The virtual machine clears the exception and transfers the control to the exception handler block. In contrast, the JNI requires developers to explicitly implement the exception handling flow after an exception has occurred. Catching Exceptions The JNIEnv interface provides a set of functions related to exceptions. To see these functions in action, use the Java class, shown in Listing 3-31, as an example. Listing 3-31.  Java Example That Throws an Exception public class JavaClass { /** * Throwing method. */ private void throwingMethod() throws NullPointerException { throw new NullPointerException("Null pointer"); } /** * Access methods native method. */ private native void accessMethods(); } Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 89 The accessMethods native method needs to explicitly do the exception handling while calling the throwingMethod method. The JNI provides the ExceptionOccurred function to query the virtual machine if there is a pending exception. The exception handler needs to explicitly clear the exception using the ExceptionClear function after it finishes with it, as shown in Listing 3-32. Listing 3-32. Exception Handling in Native Code jthrowable ex; ... (*env)->CallVoidMethod(env, instance, throwingMethodId); ex = (*env)->ExceptionOccurred(env); if (0 != ex) { (*env)->ExceptionClear(env); /* Exception handler. */ } Throwing Exceptions The JNI allows the native code to throw exceptions as well. Since exceptions are Java classes, the exception class should be obtained first using the FindClass function. The ThrowNew function can be used to initiate and throw the new exception, as shown in Listing 3-33. Listing 3-33. Throwing an Exception from Native Code jclass clazz; ... clazz = (*env)->FindClass(env, "java/lang/NullPointerException"); if (0 ! = clazz) { (*env)->ThrowNew(env, clazz, "Exception message."); } As the code execution of native functions are not under the control of the virtual machine, throwing an exception does not stop the execution of the native function and transfer control to the exception handler. Upon throwing an exception, the native function should free any allocated native resources, such as the memory, and properly return. The references obtained through the JNIEnv interface are local references and they get freed automatically by the virtual machine as soon as the native function returns. Local and Global References References play an important role in Java programming. The virtual machine manages the lifetime of class instances by tracking their references and garbage-collecting the ones that are no longer referenced. Since native code is not a managed environment, the JNI provides a set of functions to allow native code to explicitly manage the object references and lifetimes. The JNI supports three type kinds of references: local references, global references, and weak global references, as described in the following sections. Download at http://www.pin5i.com/ 90 CHAPTER 3: Communicating with Native Code using JNI Local References Most JNI functions return local references. Local references cannot be cached and reused in subsequent invocations since their lifetime is limited to the native method. Local references are freed once the native function returns. For example, the FindClass function returns a local reference; it is freed automatically when the native method returns. Native code can also be freed explicitly through the DeleteLocalRef function, as shown in Listing 3-34. Listing 3-34.  Deleting a Local Reference jclass clazz; clazz = (*env)->FindClass(env, "java/lang/String"); EnsureLocalCapacity method to request more local reference slots from the Global references remain valid across subsequent invocations of the native methods until they are explicitly freed by the native code. New Global Reference Global references can be initiated from local references through the NewGlobalRef function, as shown in Listing 3-35. Listing 3-35.  New Global Reference from a Given Local Reference jclass localClazz; jclass globalClazz; ... localClazz = (*env)->FindClass(env, "java/lang/String"); globalClazz = (*env)->NewGlobalRef(env, localClazz); ... (*env)->DeleteLocalRef(env, localClazz); Deleting a Global Reference When a global reference is no longer needed by the native code, you can free it at any time through the DeleteGlobalRef function, as shown in Listing 3-36. Download at http://www.pin5i.com/ Listing 3-36.  Deleting a Global Reference (*env)->DeleteGlobalRef(env, globalClazz); CHAPTER 3: Communicating with Native Code using JNI 91 Weak Global References Another flavor of global reference is the weak global reference. Like global references, weak global references remain valid across subsequent invocations of the native methods. Unlike global references, weak global references do not prevent the underlying object from being garbage-collected. New Weak Global Reference Weak global references can be initiated using the NewWeakGlobalRef function, as shown in Listing 3-37. Listing 3-37.  New Weak Global Reference from a Given Local Reference jclass weakGlobalClazz; weakGlobalClazz = (*env)->NewWeakGlobalRef(env, localClazz); Validating a Weak Global Reference To determine if the weak global reference is still pointing to a live class instance, you can use the IsSameObject function, as shown in Listing 3-38. Listing 3-38.  Checking if Weak Global Reference is Still Valid if (JNI_FALSE == (*env)->IsSameObject(env, weakGlobalClazz, NULL)) { /* Object is still live and can be used. */ } else { /* Object is garbage collected and cannot be used. */ } Deleting a Weak Global Reference Weak global references can be freed at any time using the DeleteWeakGlobalRef function, as shown in Listing 3-39. Listing 3-39.  Deleting a Weak Global Reference (*env)->DeleteWeakGlobalRef(env, weakGlobalClazz); Until they get explicitly freed, the global references remains valid, and they can be used by other native function calls as well as the native threads. Download at http://www.pin5i.com/ 92 CHAPTER 3: Communicating with Native Code using JNI Threading The virtual machine supports running native code as a part of the multithreaded environment. There are certain constraints of JNI technology to keep in mind while developing native components:  Local references are valid only during the execution of the native method and in the thread context that is executing the native method. Local references cannot be shared among multiple threads. Only global references can be shared by multiple threads.  The JNIEnv interface pointer that is passed into each native method call is also valid in the thread associated with the method call. It cannot be cached and used by other threads.   Java Synchronized Code Block synchronized(obj) { /* Synchronized thread-safe code block. */ } In the native code, the same level of synchronization can be achieved using the JNI’s monitor methods, as shown in Listing 3-41. Listing 3-41.  Native Equivalent of Java Synchronized Code Block if (JNI_OK == (*env)->MonitorEnter(env, obj)) { /* Error handling. */ }   /* Synchronized thread-safe code block. */   if (JNI_OK == (*env)->MonitorExit(env, obj)) { /* Error handling. */ } Caution  The call to the MonitorEnter function should be matched with a call to MonitorExit in order to prevent deadlocks in the code. Download at http://www.pin5i.com/ CHAPTER 3: Communicating with Native Code using JNI 93 Native Threads These native components may use native threads in order to execute certain tasks in parallel. Since the native threads are not known to the virtual machine, they cannot directly communicate with the Java components. Native threads should be attached to the virtual machine first in order to interact with the remaining portion of the application. The JNI provides the AttachCurrentThread function, through the JavaVM interface pointer, to allow native code to attach native threads to the virtual machine, as shown in Listing 3-42. The JavaVM interface pointer should be cached earlier since it cannot be obtained otherwise. Listing 3-42.  Attaching and Detaching the Current Thread to the Virtual Machine JavaVM* cachedJvm; ... JNIEnv* env; ... /* Attach the current thread to virtual machine. */ (*cachedJvm)->AttachCurrentThread(cachedJvm, &env, NULL);   /* Thread can communicate with the Java application using the JNIEnv interface. */   /* Detach the current thread from virtual machine. */ (*cachedJvm)->DetachCurrentThread(cachedJvm); The call to the AttachCurrentThread function allows the application to obtain a JNIEnv interface pointer that is valid for the current thread. There is no side effect of attaching an already attached native thread. When the native thread completes, it can be detached from the virtual machine by using the DetachCurrentThread function. Summary In this chapter, you learned how to communicate between the Java application and the native code using the JNI technology. More information on the JNI technology and available JNI APIs can be found in Oracle’s JNI documentation at http://docs.oracle.com/javase/1.5.0/docs/guide/jni/ spec/jniTOC.html. As you may have noticed, doing any operation in JNI takes two or three function calls. Implementing a large number of native methods and keeping them in sync with the Java classes can easily become a cumbersome task. In the next chapter, you will evaluate some open source solutions that can automatically generate the JNI code for you based on the existing native code interfaces. Download at http://www.pin5i.com/ 4 Chapter 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:  Defining a SWIG interface for native code.  Generating JNI code based on the interface.  Integrating SWIG into the Android Build process.  Wrapping C and C++ code.  Exception handling.  Using memory management.  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 95 Download at http://www.pin5i.com/ 96 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 generalpurpose 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 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 Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 97 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 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 If the installation was successful, you will see the SWIG version number, as shown in Figure 4-4. Figure 4-4.  Validating SWIG installation Download at http://www.pin5i.com/ 98 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 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 commaDndowlinneload at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 99 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 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 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 automaticDaollwy.nload at http://www.pin5i.com/ 100 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 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 theDPoOwSnIloXaOd SatAhPttIpg:/e/twuwidwf.upinnc5tii.oconm. A/s a platform-independent CHAPTER 4: Auto-Generate JNI Code Using SWIG 101 programming language, Java does not provide access to these functions. As a part of this example application, you will be  Writing a SWIG interface file to expose the getuid function.  Integrating SWIG into the Android build process.  Adding SWIG-generated source files into the Android.mk build file.  Use the SWIG-generated proxy classes to query the getuid.  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 intDerofawcnelfoilaed at http://www.pin5i.com/ 102 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 103 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 
     portion can take following values:  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.  runtime: Places the code block next to SWIG’s internal type-checking and other support functions.  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.  wrapper: Places the code block next to generated wrapper functions.  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. Download at http://www.pin5i.com/ 104 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 105 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 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)) Download at http://www.pin5i.com/ 106 CHAPTER 4: Auto-Generate JNI Code Using SWIG # 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:  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.  MY_SWIG_INTERFACES: Lists the SWIG interface file that should be processed. In your example, this will be the Unix.i file.  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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 107 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 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 HellDooJwnniloaacdtivaittyhtttop:s/h/wowwwit.spirne5tiu.cronmv/alue on the display. Using the 108 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 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:  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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 109 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; }  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(); }  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 failDdouwrninlogacdoamt hptitlep:-/ti/mwew.w.pin5i.com/ 110 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 111 /* Constant using %constant directive. */ %constant int MAX_HEIGHT = 320; SWIG generates a Java interface called 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. Download at http://www.pin5i.com/ 112 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 113 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 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 compiletime 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()); Download at http://www.pin5i.com/ 114 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 115 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. Download at http://www.pin5i.com/ 116 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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); } } Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 117 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. Download at http://www.pin5i.com/ 118 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 119 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 { . . . Download at http://www.pin5i.com/ 120 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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; Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 121 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. Download at http://www.pin5i.com/ 122 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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. Download at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 123 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. Download at http://www.pin5i.com/ 124 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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 comDpoiwlenr.load at http://www.pin5i.com/ CHAPTER 4: Auto-Generate JNI Code Using SWIG 125 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. Download at http://www.pin5i.com/ 126 CHAPTER 4: Auto-Generate JNI Code Using SWIG 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. Download at http://www.pin5i.com/ 5 Chapter Logging, Debugging, and Troubleshooting In previous chapters, you explored the Android NDK build system and how to connect the native code to the Java application using the JNI technology. Needless to say, learning application development on a new platform involves much experimentation; it takes time to get things right. It is vital to gain the troubleshooting skills pertaining to Android platform before starting to experiment with the native APIs offered, as it can catalyze the learning phase greatly by helping you to spot problems quickly. Your existing troubleshooting skills may not directly apply since the development and execution of Android applications happens on two different machines. In this chapter you will explore logging, debugging, and troubleshooting tools and techniques including:  An introduction to Android Logging framework  Debugging native code through Eclipse and command line  Analyzing stack traces from crashes  Using CheckJNI mode to spot problems earlier  Troubleshooting memory issues using libc and Valgrind  Using strace to monitor native code execution Logging Logging is the most important part of troubleshooting, but it is tricky to achieve, especially on mobile platforms where the development and the execution of the application happen on two different machines. Android has an extensive logging framework that promotes system-wide centralized logging of information from both the Android system itself and the applications. A set of user-level applications is also provided to view and filter these logs, such as the logcat and Dalvik Debug Monitor Server (DDMS) tools. 127 Download at http://www.pin5i.com/ 128 CHAPTER 5: Logging, Debugging, and Troubleshooting Framework The Android logging framework is implemented as a kernel module known as the logger. The amount of information being logged on the platform at any given time makes the viewing and analysis of these log messages very difficult. In order to simplify this procedure, the Android logging framework groups the log messages into four separate log buffers:  Main: Main application log messages  Events: System events  Radio: Radio-related log messages  System: Low-level system debug messages for debugging /dev/log system directory. Since input provides a set of API calls to allow both Java and the native code to easily send log messages to the logger kernel module. The logging API for the native code is exposed through the android/log.h header file. In order to use the logging functions, native code should include this header file first.   #include   In addition to including the proper header file, the Android.mk file needs to be modified dynamically to link the native module with the log library. This is achieved through the use LOCAL_LDLIBS build system variable, as shown in Listing 5-1. This build system variable must be placed before the include statement for the shared library build fragment; otherwise, it will not have any affect. Listing 5-1.  Dynamically Linking the Native Module with Log Library  LOCAL_MODULE := hello-jni ... LOCAL_LDLIBS += −llog ... include $(BUILD_SHARED_LIBRARY)   Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 129 Log Message Each log entry that is dispatched to the logger module through the logging APIs has the following fields:  Priority: Can be verbose, debug, info, warning, error, or fatal to indicate the severity of the log message. Supported log priority levels are declared in the android/log.h header file, as shown in Listing 5-2. Listing 5-2.  Supported Log Priority Levels typedef enum android_LogPriority { ... ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL, ... } android_LogPriority;  Tag: Identifies the component that emits the log message. The logcat and DDMS tools can filter the log messages based on this tag value. The tag value is expected to be reasonably small.  Message: Text payload carrying the actual log message. The newline character gets automatically appended to each log message. Since the circular log buffers are pretty small, it is strongly recommended that the applications keep the size of log message at a reasonable level. Logging Functions The android/log.h header file also declares a set of functions for the native code to emit log messages.  __android_log_write: Can be used to emit a simple string as a log message. It takes log priority, log tag, and a log message, as shown in Listing 5-3. Listing 5-3.  Logging a Simple Message  __android_log_write(ANDROID_LOG_WARN, "hello-jni", "Warning log.");    __android_log_print: Can be used to emit a formatted string as a log message. It takes log priority, log tag, string format, and variable numbers of other parameters as specified in the format, as shown in Listing 5-4. For the syntax of the format string, please refer to ANSI C printf documentation. Download at http://www.pin5i.com/ 130 CHAPTER 5: Logging, Debugging, and Troubleshooting Listing 5-4. Logging a Formatted Message __android_log_print(ANDROID_LOG_ERROR, "hello-jni", "Failed with errno %d", errno);  __android_log_vprint: It behaves exactly as the __android_log_print function except the additional parameters are passed as a va_list instead of a succession of parameters. This is very useful if you are planning to call the logging function with variable number of parameters that are passed to the current function, as shown in Listing 5-5. Listing 5-5. Logging a Message by Using the Variable Number of Parameters That Are Passed In void log_verbose(const char* format, ...) { va_list args; va_start(args, format); __android_log_vprint(ANDROID_LOG_VERBOSE, "hello-jni", format, args); va_end(args); } ... void example() { log_verbose("Errno is now %d", errno); }  __android_log_assert: Can be used to log assertion failures. Compared to other logging functions, it does not take a log priority and always emits logs as fatal, as shown in Listing 5-6. If a debugger is attached, it also SIGTRAP’s the current process to enable further inspection through the debugger. Listing 5-6. Logging an Assertion Failure if (0 != errno) { __android_log_assert("0 != errno", "hello-jni", "There is an error."); } Controlled Logging Like their Java counterparts, the native logging APIs only let you emit log messages to the logger kernel module. In real life, you would neither use asserts nor log at the same granularity in your release and debug builds. Unfortunately, the Android logging API does not provide any capability to suppress log messages based on their priorities. It is not as advanced as other logging frameworks such as Log4J or Log4CXX. The Android logging framework assumes that you will somehow take out the unnecessary logging calls from your release builds. Although this can very easily be done in Java applications by relying on ProgDuoawrdn,lotahderaet histtnpo:/e/waswywr.epcinip5ei.cfoomr /the native code. CHAPTER 5: Logging, Debugging, and Troubleshooting 131 Log Wrapper This section will introduce a preprocessor based solution to this problem. To see it in action, you will modify the hello-jni native project that you imported earlier. Open Eclipse and, using the Project Explorer, right-click on the jni sub-directory. From the context menu, choose New Header File to launch the New Header File dialog. Set the header file name as my_log.h, and click the Finish button to proceed. The content of the my_log.h header file is shown in Listing 5-7. Listing 5-7.  The Content of my_log.h Header File #pragma once   /** * Basic logging framework for NDK. * * @author Onur Cinar */   #include   #define MY_LOG_LEVEL_VERBOSE 1 #define MY_LOG_LEVEL_DEBUG 2 #define MY_LOG_LEVEL_INFO 3 #define MY_LOG_LEVEL_WARNING 4 #define MY_LOG_LEVEL_ERROR 5 #define MY_LOG_LEVEL_FATAL 6 #define MY_LOG_LEVEL_SILENT 7   #ifndef MY_LOG_TAG # define MY_LOG_TAG __FILE__ #endif   #ifndef MY_LOG_LEVEL # define MY_LOG_LEVEL MY_LOG_LEVEL_VERBOSE #endif   #define MY_LOG_NOOP (void) 0   #define MY_LOG_PRINT(level,fmt,...) \ __android_log_print(level, MY_LOG_TAG, "(%s:%u) %s: " fmt, \ __FILE__, __LINE__, __PRETTY_FUNCTION__, ##__VA_ARGS__)   #if MY_LOG_LEVEL_VERBOSE >= MY_LOG_LEVEL # define MY_LOG_VERBOSE(fmt,...) \ MY_LOG_PRINT(ANDROID_LOG_VERBOSE, fmt, ##__VA_ARGS__) #else # define MY_LOG_VERBOSE(...) MY_LOG_NOOP #endif   #if MY_LOG_LEVEL_DEBUG >= MY_LOG_LEVEL # define MY_LOG_DEBUG(fmt,...) \ MY_LOG_PRINT(ANDROID_LOGD_oDwEBnUlGo,adfamtt,ht#tp#:_/_/VwA_wAwRG.pS_in_5)i.com/ 132 CHAPTER 5: Logging, Debugging, and Troubleshooting #else # define MY_LOG_DEBUG(...) MY_LOG_NOOP #endif   #if MY_LOG_LEVEL_INFO >= MY_LOG_LEVEL # define MY_LOG_INFO(fmt,...) \ MY_LOG_PRINT(ANDROID_LOG_INFO, fmt, ##__VA_ARGS__) #else # define MY_LOG_INFO(...) MY_LOG_NOOP #endif   #if MY_LOG_LEVEL_WARNING >= MY_LOG_LEVEL # define MY_LOG_WARNING(fmt,...) \ MY_LOG_PRINT(ANDROID_LOG_WARN, fmt, ##__VA_ARGS__)   MY_LOG_PRINT(ANDROID_LOG_ERROR, fmt, ##__VA_ARGS__)   # define MY_LOG_FATAL(fmt,...) \ MY_LOG_PRINT(ANDROID_LOG_FATAL, fmt, ##__VA_ARGS__) #else # define MY_LOG_FATAL(...) MY_LOG_NOOP #endif   #if MY_LOG_LEVEL_FATAL >= MY_LOG_LEVEL # define MY_LOG_ASSERT(expression, fmt, ...) \ if (!(expression)) \ {\ __android_log_assert(#expression, MY_LOG_TAG, \ fmt, ##__VA_ARGS__); \ } #else # define MY_LOG_ASSERT(...) MY_LOG_NOOP #endif Through a set of preprocessor directives, the my_log.h header file defines a basic logging framework for native code. These preprocessor directives wrap the Android logging functions and allow them to be toggled during the compile time. Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 133 Adding Logging You can now add logging statements into the native code. Using the Project Explorer, double-click the hello-jni.c source file to open it in the Editor view. In order to use the basic logging framework, the my_log.h header file needs to be included first. There is no need to include the android/log.h anymore, since it is already included through my_log.h.  #include "my_log.h"  You can now add the logging statements into the native function, as shown in Listing 5-8. Listing 5-8.  Adding Logging Statements into Native Function jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { MY_LOG_VERBOSE("The stringFromJNI is called.");   MY_LOG_DEBUG("env=%p thiz=%p", env, thiz);   MY_LOG_ASSERT(0 != env, "JNIEnv cannot be NULL.");   MY_LOG_INFO("Returning a new string.");   return (*env)->NewStringUTF(env, "Hello from JNI !"); } Updating Android.mk You can now update the Android.mk file to tune the basic logging framework. Using the Project Explorer, double-click on the Android.mk source file to open it in the Editor view. Log Tag As mentioned, each log message contains a log tag identifying the component that is emitting the log message. The log tag for the module can be defined in the Android.mk file, as shown in Listing 5-9. Listing 5-9.  Defining the Log Tag Through MY_LOG_TAG Build Variable LOCAL_MODULE := hello-jni ... # Define the log tag MY_LOG_TAG := \"hello-jni\" Download at http://www.pin5i.com/ 134 CHAPTER 5: Logging, Debugging, and Troubleshooting Log Level The main advantage of the basic logging framework is the ability to define a log level. As you would not log at the same granularity in your release and debug builds, the Android.mk file can be modified to define different log levels for debug and release builds, as shown in Listing 5-10. Listing 5-10.  Defining the Default Logging Levels LOCAL_MODULE := hello-jni ... # Define the log tag MY_LOG_TAG := \"hello-jni\"   MY_LOG_LEVEL := MY_LOG_LEVEL_ERROR MY_LOG_LEVEL := MY_LOG_LEVEL_VERBOSE   APP_OPTIM build system variable indicates whether the build type APP_OPTIM, the value of MY_LOG_LEVEL can be set to the Applying the Logging Configuration Upon defining the MY_LOG_TAG and MY_LOG_LEVEL build system variables, the logging system configuration can be applied to the module, as shown in Listing 5-11. Listing 5-11.  Applying the Logging Configuration to the Module LOCAL_MODULE := hello-jni ... # Define the log tag MY_LOG_TAG := hello-jni   # Define the default logging level based build type ifeq ($(APP_OPTIM),release) MY_LOG_LEVEL := MY_LOG_LEVEL_ERROR else MY_LOG_LEVEL := MY_LOG_LEVEL_VERBOSE endif   # Appending the compiler flags LOCAL_CFLAGS += −DMY_LOG_TAG=$(MY_LOG_TAG) LOCAL_CFLAGS += −DMY_LOG_LEVEL=$(MY_LOG_LEVEL)   # Dynamically linking with the log library LOCAL_LDLIBS += −llog Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 135 Observing Log Messages Through Logcat Upon executing the hello-jni application, the log messages can be observed through the Logcat view, as shown in Figure 5-1. Figure 5-1.  Log messages from the native code Console Logging When integrating third party libraries and legacy modules into an Android application project, changing their logging mechanism to Android-specific logging may not be possible. Most logging mechanisms either log messages to a file or directly to the console. The console file descriptors, STDOUT and STDERR, are not visible by default on the Android platform. To redirect these log messages to the Android system log, open a command prompt or a Terminal window and execute the ADB commands shown in Listing 5-12. Listing 5-12.  Redirecting Console Log to Android System Log adb shell stop adb shell setprop log.redirect-stdio true adb shell start Upon restarting the application, the console log messages will be visible through the Logcat view, as shown in Figure 5-2. Figure 5-2.  Log messages re-directed from STDOUT and STDERR descriptors Download at http://www.pin5i.com/ 136 CHAPTER 5: Logging, Debugging, and Troubleshooting The system retains this setting until the device reboots. If you want to make these settings the default, add them to the /data/local.prop file on the device or emulator. Debugging Logging allows you to output messages from a running application, exposing its current state. When troubleshooting problems, the granularity of the log messages from the concerned portion of the code may not be sufficient. New log messages can be implanted into the code to expose more information about its current state but this simply slows down the troubleshooting process. Using a debugger to properly observe the application state is the most convenient way of troubleshooting. Android NDK supports debugging of native code through the GNU Debugger (GDB).  Native code should be compiled either through ndk-build command from the command line, or through the Eclipse IDE using Android Development Tools. The NDK build system generates a set of files during the build process to remote debugging possible.  The application should be set as debuggable in its AndroidManifest.xml file through the android:debuggable attribute of the application tag, as shown in Listing 5-13. Listing 5-13.  Declaring the Application as Debuggable ... ...  The device or the emulator should be running Android version 2.2 or higher. Native code debugging is not supported in earlier versions. The ndk-gdb script handles many of the error conditions and outputs informative error messages to let you know if any of these conditions have not been met. Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 137 Debug Session Setup The ndk-gdb script that takes care of setting up the debug session on behalf of the developer but knows the sequence of events happening during the debug session setup, which is very beneficial to understanding the caveats of debugging native code on Android. The complete sequence of events during the debug session setup is shown in Figure 5-3. Figure 5-3.  Debug session setup sequence diagram The ndk-gdb script launches the target application by using the application manager through ADB. The application manager simply relays the request to Zygote process. Zygote, also known as the “app process,” is one of the core processes started when the Android system boots. Its role within the Android platform is to start the Dalvik virtual machine and initialize all core Android services. As a mobile operating system, Android needs to keep the startup time of applications as small as possible in order to provide a highly responsive user experience. In order to achieve that, instead of starting a new process from scratch for the applications, Zygote simply relies on forking. In computing, forking is the operation to clone an existing process. The new process has an exact copy of all memory segments of the parent process, although both processes execute independently. At this point in time, the application is started and is executing code. As you may have noticed, the debug session is not established yet at this point. Note  Due to the way Zygote works, the GDB cannot start the application, but it can simply attach to an already running application process. If you want to prevent your application from executing code prior to when GDB attaches, you need to use the Java Debugger to set a breakpoint at a proper position in the code. Download at http://www.pin5i.com/ 138 CHAPTER 5: Logging, Debugging, and Troubleshooting Upon obtaining the process ID of the application, the ndk-gdb script starts the GDB Server on Android and has it attach to the running application. The ndk-gdb script configures port forwarding using ADB to make the GDB Server accessible from the host machine. Later, it copies the binaries for Zygote and the shared libraries to the host machine prior starting the GDB Client. After the binaries are copied, the ndk-gdb script starts the GDB Client and the debug session becomes active. After this point, you can start debugging the application. Setting up the Example for Debugging In order to see the native code debugging in action, you will be using the hello-jni sample project. To simplify the debug process, you will make a slight change in the HelloJni activity’s onCreate HelloJni activity in the Editor View, as described earlier. Modify onCreate method as shown in Listing 5-14.   Modified onCreate Method to Delay the Native Call { super.onCreate(savedInstanceState);   Button button = new Button(this); button.setText("Call Native"); button.setOnClickListener(new OnClickListener() { public void onClick(View button) { ((Button) button).setText(stringFromJNI()); } }); setContentView(button); }   From the menu bar, choose Source ➤ Organize Imports to get Eclipse to add the necessary import statements to the source file. For the OnClickListener class, Eclipse will propose more than one alternative to import. Select android.view.View.OnClickListener and proceed. The modified onCreate method places a button to the display. Clicking that button will initiate the native call. This will let you to make sure that the native call is initiated after the debug session is properly set up. Starting the Debugger Debugging of native code can be done through both the command line and from within Eclipse. This section will demonstrate both methods. Fix for Windows Users On Windows platform, there is a known bug in the Android NDK that prevents the GDB from locating the binaries properly. The ndk-gdb script configures the GDB Client using a GDB script file. On the Windows platform, this script file geDtsowgennloeardataetdhtwtpit:h//ewxwtrwa.pcianr5riia.cgoemr/eturns, causing this issue. CHAPTER 5: Logging, Debugging, and Troubleshooting 139 In order to fix it, using Eclipse, open the /ndk-gdb script in the Editor view. Go to the end of the file, and add fix as shown in Listing 5-15. Listing 5-15.  Fixing the GDB Setup Script Generation  # Fix the line endings. sed -i 's/\r\r//' 'native_path $GDBSETUP'   $GDBCLIENT -x 'native_path $GDBSETUP'   Using Eclipse Like running applications, Eclipse requires having a debug configuration defined in order establish a debug session. 1. From the menu bar, choose Run ➤ Debug Configurations to launch the Debug Configurations dialog, as shown in Figure 5-4. Figure 5-4.  New Android native application configuration 2. From the right panel, select Android Native Application. 3. Click the new configuration icon on the dialog toolbar. 4. As shown in Figure 5-5, using the right panel, use the Browse button to select the current project. Download at http://www.pin5i.com/ 140 CHAPTER 5: Logging, Debugging, and Troubleshooting Defining the native debug configuration 5. Click the Apply button to store the debug configuration. 6. Close the debug configurations dialog and go back to Eclipse workbench. 1. Open up the hello-jni.c source file in Editor view, as described earlier. 2. Go into the native function, and right-click on the marker area, the left border of the Editor view. 3. As shown in Figure 5-6, choose from the context menu to place a breakpoint. A blue point will be placed on the marker bar indicating the breakpoint. Figure 5-6. Toggle breakpoint Download at http://www.pin5i.com/ n CHAPTER 5: Logging, Debugging, and Troubleshooting 141 Tip  Using the same context menu, you can also place a conditional breakpoint as well as enable and disable existing breakpoints. 4. Now that the breakpoint is placed, using the top menu bar, choose Run ➤ Debug Configurations to launch the Debug Configurations dialog. 5. Select the debug configuration that you defined earlier. 6. Click the Debug button. 7. Eclipse supports different perspectives, workbench layouts, for different tasks. Upon clicking the Debug button, Eclipse will ask you if you would like to switch to the Debug perspective, as shown in Figure 5-7. Click Yes to proceed. Figure 5-7.  Switching to the debug perspective 8. Using the Android device or the emulator, click the Call Native button to invoke the native function. As soon as the native code hits the breakpoint, the application will stop and give the control to the debugger, as shown in Figure 5-8. Download at http://www.pin5i.com/ 142 CHAPTER 5: Logging, Debugging, and Troubleshooting Figure 5-8.  Eclipse debug perspective in action The debug perspective gives you a full snapshot of the native code’s current state. On the top left, the Debug view shows the list of running threads and the function that they are currently running. On the top right corner, the Variables view gives you access to the native variables and lets you to inspect their current values. In the center area, the native source code is shown in the Editor view, and an arrow is shown on the marker bar next to the line that will be executed next. As shown in Figure 5-9, using the debug toolbar, you can control the execution of the application. Figure 5-9.  Debug toolbar The following actions are provided through the debug toolbar:  Skip All Breakpoints: Allows you to disable all breakpoints.  Resume: Resumes the execution of the native code until the next breakpoint. Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 143  Suspend: Suspends the execution of native code by sending the SIGINT interrupt signal to the process, which allows you to investigate the current state of the native code.  Step Into: Follows the next native call by going into it.  Step Over: Executes the next native call and then stops.  Step Return: Executes until the native function returns.  Terminate: Terminates the debug session. Debugging of native applications is not only possible through Eclipse. The same level of debugging functionality can also be achieved through the command line as well. The Command Line Native code can be debugged using the ndk-gdb script from the command line. Currently the ndk-gdb script requires a UNIX shell to run. On the Windows platform, you will use Cygwin instead of the command prompt for debugging. First, open Cygwin or the Terminal window, based on your platform. You will use the hello-jni sample project for this experiment. 1. Make sure that Eclipse is no longer running in order to prevent any conflicts. 2. Change the current directory to the hello-jni project directory. 3. Delete any leftover files from the Eclipse by issuing rm –rf bin obj libs. 4. Compile the native module by issuing ndk-build on the command line. 5. In order to compile and package the application from command line, make sure that the ANT build script build.xml file exists in project directory. If this is the first time you are building this project from the command line, issue android update project –p to generate the necessary build files. If you are using Cygwin, use android.bat instead of android. 6. Compile and package the project in debug mode by issuing ant debug on the command line. 7. Deploy the application to the device or the emulator by issuing ant installd on the command line. 8. By default, the ndk-gdb script searches for an already running application process; however, you can use the –-start or –-launch= arguments to automatically start the application before the debugging session. Start the debugging session by issuing ndk-gdb --start from the command. When GDB successfully attaches to the hello-jni application, it will show the GDB prompt. 9. Add a breakpoint to the hello-jni.c souce file at line 30 by issuing b hello-jni.c:30 on the GDB prompt. Download at http://www.pin5i.com/ 144 CHAPTER 5: Logging, Debugging, and Troubleshooting 10. Now that the breakpoint is defined, issue c on the GDB prompt to continue the execution of the native application. 11. Using the Android device or emulator, click the Native Call button to invoke the native function. Note  It is normal to see a long list of error messages saying that GDB cannot be able locate various system library files. You can safely ignore these messages since symbol/debug versions of these libraries are not available. 5-10. Figure 5-10.  Command line debug session Useful GDB Commands Here is a list of useful GDB commands that you can use through the GDB prompt to debug the native code:  break : Places a breakpoint to the location specified. The location can be a function name, or a file name and a line number such as file.c:10.  enable/disable/delete <#>: Enables, disables, or deletes the breakpoint with the given number.  clear: Clears all breakpoints.  next: Goes to the next instruction.  continue: Continues execution of the native code.  backtrace: Shows the call stack.  backtrace full: Shows the call stack including the local variables in each frame. Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 145  print : Prints the content of the variable, expression, memory address, or register.  display : Same as the print, but continues printing the value after each step instruction.  what is : Shows the type of the variable.  info threads: Lists all running threads.  thread : Operates on the selected thread.  help: Help screen to get a list of all commands.  quit: Terminates the debug session. Note  The debugged application will be stopped when quitting the GDB prompt. This is a known limitation. For more information on GDB, please check the GDB documentation at www.gnu.org/software/gdb/documentation/. Troubleshooting During the development phase, logging allows you to decide and expose the information about the application’s state that will be beneficial in solving problems later. Debugging comes into play when the information exposed through logging is simply not enough, but you have an idea of where the problem could be. When you are facing the unexpected, troubleshooting skills becomes a life saver. Knowing the right tools and techniques enables you to rapidly resolve problems. In this section, you will briefly explore some of them. Stack Trace Analysis In order to observe stack trace analysis in action, you will implant a bug into the hello-jni sample application that will cause a crash. Using Eclipse, open up the hello-jni.c source file. Modify the content of the native function as shown in Listing 5-16. Listing 5-16.  Bug Injected into the Native Function static jstring func1( JNIEnv* env ) { /* BUG BEGIN */ env = 0; /* BUG END */   return (*env)->NewStringUTF(env, "Hello from JNI !"); }   Download at http://www.pin5i.com/ 146 CHAPTER 5: Logging, Debugging, and Troubleshooting jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { return func1(env); }   By setting the value of the JNIEnv interface pointer to zero, you will trigger the crash. Now build and run the application. When the application starts, click the Call Native method to invoke the native function. The application will crash, and a stack trace will be displayed in logcat, as shown in Figure 5-11. Figure 5-11.  Logcat displaying the stack trace after the crash The lines starting with the hash sign indicates the call stack. The first line that starts with #00 is where the crash occurred; the next line, #01, is the previous function call, and so on. The number following the pc is the code’s address. As seen in the stack trace, the native code crashed at address 00000c3c, and the previous function call was the stringFromJNI native function. The address 00000c3c itself may not tell you much, but using the right tools this address can be used to find the actual file and line number that the crash occurred. Android NDK comes with a tool called ndk-stack that can translate the stack trace to the actual file names and line numbers. On the command line, go into the project root directory, and issue adb logcat | ndk-stack –sym obj/local/armeabi The ndk-stack tool will translate the stack trace, as shown in Figure 5-12. The address got translated to jni/hello-jni.c in source file line 33. Having this information makes the troubleshooting much easier. By simply putting a breakpoint at this address you can stop the application and inspect the application state. Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 147 Figure 5-12.  Ndk-stack translates the code address. Extended Checking of JNI By default, JNI functions do a very little error checking. Errors usually result in a crash. Android provides an extended checking mode for JNI calls, known as CheckJNI. When enabled, JavaVM and JNIEnv interface pointers gets switched to tables of functions that perform an extended level of error checking before calling the actual implementation. CheckJNI can detect the following problems:  Attempt to allocate a negative-sized array  Bad or NULL pointers passed to JNI functions  Syntax errors while passing class names  Making JNI calls while in critical section  Bad arguments passed to NewDirectByeBuffer  Making JNI calls when an exception is pending  JNIEnv interface pointer used in wrong thread  Field type and SetField function mismatch  Method type and CallMethod function mismatch  DeleteGlobalRef/DeleteLocalRef called with wrong reference type  Bad release mode passed to ReleaseArrayElement function  Incompatible type returned from native method  Invalid UTF-8 sequence passed to a JNI call By default, the CheckJNI mode is only enabled in the emulator, not on the regular Android devices, due to its effect on the overall pDerofwornmloaandcaet hotftpth:/e/wsywswte.mpin. 5i.com/ 148 CHAPTER 5: Logging, Debugging, and Troubleshooting Enabling CheckJNI On a regular device, using the command line, you can enable the CheckJNI mode by issuing the following: adb shell setprop debug.checkjni 1 This won’t affect the running applications but any application launched afterwards will have CheckJNI enabled. CheckJNI status is also displayed in the logcat, as shown in Figure 5-13.   CheckJNI status displayed in logcat Listing 5-17.  Creating an Array with Native Size jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { jintArray javaArray = (*env)->NewIntArray(env, -1);   return (*env)->NewStringUTF(env, "Hello from JNI !"); }  You will be creating a new integer array with a negative size. Build and run the application on the emulator. When the application starts, click the Call Native button to invoke the native function. As shown in Figure 5-14, CheckJNI will display a warning message on logcat and abort the execution. Figure 5-14.  JNI warning about negative-sizeDd oarwranyload at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 149 Memory Issues Memory issues are very hard to troubleshoot in the absence of right tools. In this section you will briefly explore two methods for analyzing the memory issues. Using Libc Debug Mode Using the emulator, the libc debug mode can be enabled to troubleshoot memory issues. In order to enable libc debug mode, using the commands as shown in Listing 5-18. Listing 5-18.  Enabling libc debug mode adb shell setprop libc.debug.malloc 1 adb shell stop adb shell start Supported libc debug mode values are  1: Perform leak detection.  5: Fill allocated memory to detect overruns.  10: Fill memory and add sentinel to detect overruns. In order to see Libc debug mode in action, using Eclipse, open up hello-jni.c source code. Modify the native function as shown in Listing 5-19. Listing 5-19.  Modifying a Memory Beyond the Allocated Buffer  jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) { char* buffer; size_t i;   buffer = (char*) malloc(1024); for (i = 0; i < 1025; i++) { buffer[i] = 'a'; }   free(buffer);   return (*env)->NewStringUTF(env, "Hello from JNI !"); }   You will be allocating 1024 bytes, but the code will be modifying an extra byte beyond the allocated size, causing a memory corruption. Enable the libc debug mode by issuing the commands shown in Listing 5-20. Download at http://www.pin5i.com/ 150 CHAPTER 5: Logging, Debugging, and Troubleshooting Listing 5-20. Enable libc Debug Mode for Memory Corruption Detection adb shell setprop libc.debug.malloc 10 adb shell stop adb shell start Build and run the application on the emulator. When the application starts, click the Call Native button to invoke the native function. As shown in Figure 5-15, libc debug mode will display a warning message about the memory corruption on logcat and abort the execution. Libc debug mode displaying memory corruption error for more advanced memory analysis. It is an open source tool for memory debugging, memory leak detection, and profiling. For this experiment, you can either download the prebuilt Valgrind binaries from book’s web site or you can build it on your machine. If you would like build it, skip to the “Building from Source Code” section. Using the Prebuilt Binaries Using your web browser, download the Valgrind binaries for ARM emulator as a zip file from http://zdo.com/valgrind-arm-emulator-3.8.0.zip. Extract the content of the zip file and take a note of its location. You can now skip to the “Installing to Emulator” section. Building from Source Code In order to properly build Valgrind for Android from the source code, you will need a Linux host system. Official distribution of Valgrind now comes with Android support. Download the latest version of Valgrind from http://valgrind.org/downloads/current.html. At the time of this writing, the latest version of Valgrind was 3.8.0. and it comes as a BZip2 compressed TAR archive. Using the command line, extract it by issuing tar jxvf valgrind-3.8.0.tar.bz2 Upon extracting the Valgrind source code, using your editor, open up README.android file for the up to date build instructions. Since you will be using Valgrind in an Android emulator, please make sure to set HWKIND to emulator by issuing export HWKIND=emulator Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 151 Upon properly building Valgrind, the binaries and the other necessary components will be placed in Inst sub-directory. Deploying Valgrind to Emulator Valgrind needs to be deployed into the emulator first before it can be used. In order to do so, open up Cygwin or a Terminal window, and go in to the root directory where you have extracted the zip file if you are using the prebuilt binaries or to the root directory of Valgrind source code, and issue the following on the command line: adb push Inst / This will deploy the Valgrind files to /data/local/Inst directory on the emulator. Upon deploying the files to the device, the execution bits should be fixed. In order to do so, issue the following command: adb shell chmod 755 \ $(find Inst -type f -exec file {} \; | \ grep executable | \ sed -n -e 's/^Inst\([^:]*\).*$/\1/gp' | \ xargs) Valgrind Wrapper In addition to Valgrind binaries, a helper script is also needed. Using Eclipse or your favorite editor, create new file called valgrind_wrapper.sh with the content shown in Listing 5-21. Listing 5-21.  Valgrind Wrapper Shell Script #!/system/bin/sh   export TMPDIR=/sdcard exec /data/local/Inst/bin/valgrind --error-limit=no $* Fix the wrapper script’s line ending, deploy it to the Emulator, and grant executable permission by issuing the commands shown in Listing 5-22. Listing 5-22.  Deploying the Valgrind Wrapper Script dos2unix.exe valgrind_wrapper.sh adb push valgrind_wrapper.sh /data/local/Inst/bin adb shell chmod 755 /data/local/Inst/bin/valgrind_wrapper.sh Running Valgrind In order to run the application under Valgrind, inject the wrapper script into the startup sequence by issuing the command shown in Listing 5-23. Download at http://www.pin5i.com/ 152 CHAPTER 5: Logging, Debugging, and Troubleshooting Listing 5-23.  Injecting Valgrind Wrapper into Startup Sequence adb shell setprop wrap.com.example.hellojni \ "logwrapper /data/local/Inst/bin/valgrind_wrapper.sh" The format for the property key is wrap.. To run your other applications under Valgrind, simply substitute the package name with the proper value. Stop and restart the application. Valgrind messages will be displayed on logcat, as shown in Figure 5-16.   Logcat displaying Valgrind messages Note  Running application under Valgrind will slow down the application at a very high rate. Android may complain about process not responding. Please click Wait button to give Valgrind more time in such cases. Strace In certain cases you may want to monitor every activity of your application without attaching a debugger or adding numerous log messages. The strace tool can be used to easily achieve that. It is a useful diagnostic tool because it intercepts and records the system calls that are called by the application and the signals that are received. The name of each system call, its arguments, and its return value are printed. Note that strace comes with the Android emulator. In order to see strace in action, using Eclipse, open the hello-jni.c source code. Modify the source file as shown in Listing 5-24. Listing 5-24.  Native Source Code with Two System Calls Added #include ... jstring Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env, jobject thiz ) Download at http://www.pin5i.com/ CHAPTER 5: Logging, Debugging, and Troubleshooting 153 { getpid(); getuid();   return (*env)->NewStringUTF(env, "Hello from JNI !"); }   Build and run the application on the emulator. Open up Cygwin or a Terminal window. When the application starts, issue the following command to obtain the process ID of the application: adb shell ps | grep com.example.hellojni The process ID is the number on the third column, as shown in Figure 5-17. Figure 5-17.  Getting the process ID of the application Issue the following command to attach strace to the running application process by substituting the process ID: adb shell strace –v –p As you can see, strace will attach to the application process and it will intercept and print the system calls with their parameters and return values. Click the Call Native button to invoke the native function, and strace will display two system calls that you have introduced into the native code, as shown in Figure 5-18. Figure 5-18.  Strace printing the system calls Strace is a very useful tool for troubleshooting both open and closed code applications. Download at http://www.pin5i.com/ 154 CHAPTER 5: Logging, Debugging, and Troubleshooting Summary In this chapter, you explored the tools and the techniques for effective logging, debugging, and troubleshooting on Android platform. The concepts presented in this chapter will be highly beneficial when experimenting with the native APIs offered by the Android platform, as you will see in the following chapters. Download at http://www.pin5i.com/ 6 Chapter Bionic API Primer In previous chapter, you explored the logging, debugging, and troubleshooting tools and techniques pertaining to Android native application development. Starting with this chapter, you will be exploring the native APIs provided by the Android NDK. Bionic is the POSIX standard C library that is provided by the Android platform for native application development using C and C++ programming languages. Bionic is a derivation of BSD standard C library by Google for the Android operating system. The name “Bionic” comes from the fact that it consists of a mixture of BSD C library pieces with custom Linux-specific bits for handling threads, processes, and signals. Bionic is a highly vital subject for native application development, since it provides the minimal set of constructs that are needed to develop any type of functional native code on Android platform. In the following chapters, you will be relying heavily on the functionality provided by the Bionic. Before getting into Bionic specifics, let’s quickly review standard libraries in general. Reviewing Standard Libraries A standard library for a programming language provides frequently needed constructs, algorithms, data structures, and an abstract interface to tasks that would normally depend heavily on the hardware and operating system, such as network access, multi-threading, memory management, and file I/O. Depending on the philosophy behind the programming language itself, the scope of the standard library varies greatly. It can either be fairly minimal with only a set of constructs for vital tasks, or in contrast, it can be highly extensive. In all cases, the standard library is conventionally made available in every implementation of the programming language in order to provide a consistent base for application development. There is a standard library for almost every programming language. The Java platform comes with the Java Class Library (JCL), a standard library for Java programming language that contains a comprehensive set of standard class libraries for common operations such as sorting, string manipulation, and an abstract interface to underlying operating system services such as the stream I/O for interacting with the files and the network. The Android framework extends the JCL by incorporating additional constructs that are specific to Android application development. 155 Download at http://www.pin5i.com/ 156 CHAPTER 6: Bionic API Primer For the C programming language, the ANSI C standard defines the scope of the standard library. This standard library is known as C standard library, or simply as libc. Implementations of the C programming language also accompanied with an implementation of the C standard library. On top of the standard C library specification, the POSIX C library specification declares the additional constructs that should be included in such standard library on POSIX compliant systems. Yet Another C Library? Google’s motivation behind creating a new C library instead of reusing the existing GNU C Library (glibc) or the C Library for Embedded Linux (uClibc) can be summarized under the three main goals  License: Both the glibc and uClibc are available under GNU Lesser General Public License (LGPL), thus restricting the way they can be used by proprietary applications. Instead, Bionic is published under the BSD license, a highly permissive license that does not set any restriction on the use of the library.  Speed: Bionic is specifically crafted for mobile computing. It is tailored to work efficiently despite the limited CPU cycles and memory available on the mobile devices.  Size: Bionic is designed with the core philosophy of keeping it simple. It provides lightweight wrappers around kernel facilities and a lesser set of APIs, making it smaller compared to other alternatives. This chapter will cover these APIs. Binary Compatibility Even though it is a C standard library, Bionic is not in any way binary-compatible with other C libraries. Object files and static libraries that are produced against other C libraries should not be dynamically linked with Bionic. Doing so will usually result in the inability to link or execute your native applications properly. Besides that, any application that is generated by statically linking with other C libraries and not mixed with Bionic can run on the Android platform without any issues, unless it is dynamically loading any other system library during runtime. What is Provided? Bionic provides C standard library macros, type definitions, functions, and small number of Androidspecific features that can be itemized under these functionality domains:  Memory Management  File Input and Output  String Manipulation  Mathematics Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 157  Date and Time  Process Control  Signal Handling  Socket Networking  Multithreading  Users and Groups  System Configuration  Name Service Switch What is Missing? As mentioned, Bionic is specifically designed for Android platform and tuned for mobile computing. Not every function in the standard C library is supported by Bionic. Android NDK documentation does provide a full list of missing functionality; however, such information is available within the actual header files itself. Bionic header files can be located platforms/android- / arch- /usr/include under the ANDROID_NDK_HOME directory. Each header file in this directory contains a section clearly marking the list of missing functions. As an example, the section listing the missing functions in stdio.h header file is shown in Listing 6-1. Listing 6-1.  Missing Functions in Bionic Implementation #if 0 /* MISSING FROM BIONIC */ char *ctermid(char *); char *cuserid(char *); #endif /* MISSING */ The pre-processor if statement is used to disable these lines in the header file, and the associated comment indicates that the section contains the list of missing functions. In addition to this list, the Android NDK documentation also cites the functions that are exposed through Bionic but implemented as a stub only, without any or minimal functionality. Memory Management Memory is the most basic resource available to a process. For Java applications, the memory is managed by the virtual machine. Memory gets allocated as new objects are created, and through the garbage collector, the unused memory automatically gets returned to the system. However, in the native space, the applications are expected to manage their own memory explicitly. Managing the memory properly is vital in native application development since failure to do so will result in exhausting available system memory and will deeply impact the stability of the application as well as the system in general. Download at http://www.pin5i.com/ 158 CHAPTER 6: Bionic API Primer Memory Allocation There are three types of memory allocation that are supported by the C/C++ programming language:  Static allocation: For each static and global variable that is defined in the code, static allocation happens automatically when the application starts.  Automatic allocation: For each function argument and local variable, automatic allocation happens when the compound statement containing the declaration is entered; it’s freed automatically when compound statement is exited.  Dynamic allocation: Both static and automatic allocation assumes that the required memory size and its scope are fixed and defined during the compiletime. Dynamic allocation comes into play when the size and the scope of memory allocation depends on runtime factors that are not known in advance. In the C programming language, dynamic memory can be allocated during runtime using the standard C library function malloc. void* malloc(size_t size); In order to use this function, the stdlib.h standard C library header file should be included first. As shown in Listing 6-2, malloc takes a single argument, the size of memory to be allocated as number of bytes, and returns a pointer to the newly allocated memory. Listing 6-2.  Dynamic Memory Allocation in C Code Using malloc /* Include standard C library header. */ #include < stdlib.h> ... /* Allocate an integer array of 16 elements. */ int* dynamicIntArray = (int*) malloc(sizeof(int) * 16); if (NULL == dynamicIntArray) { /* Unable to allocate enough memory. */ ... } else { /* Use the memory through the integer pointer. */ *dynamicIntArray = 0; dynamicIntArray[8] = 8; ... Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 159 /* Free the memory allocation. */ free(dynamicIntArray); dynamicIntArray = NULL; } Tip  Since malloc takes the size of memory as number of bytes, the C keyword sizeof can be used to extract the size of a data types. If the requested memory size is not achievable, malloc returns NULL to indicate that. Applications should check the value returned from malloc prior using it. Once allocated, the dynamic memory can be used by ordinary C code through the pointers, until it gets freed. Freeing Dynamic Memory in C Dynamic memory should be explicitly freed by the application when it is no longer needed. The standard C library function free is used to release the dynamic memory. void free(void* memory); The free function takes a pointer the previously allocated dynamic memory and releases it, as shown in Listing 6-3. Listing 6-3.  Releasing the Dynamic Memory in C Code Using free int* dynamicIntArray = (int*) malloc(sizeof(int) * 16); ... /* Use the allocated memory. */ ... free(dynamicIntArray); dynamicIntArray = NULL; Note that the pointer’s value does not change after this function call even though the memory that it is pointing to got released. Any attempt to use this invalid pointer results in segmentation violation. It is a good practice to set the pointer to NULL immediately after freeing it in order to prevent accidental use of the invalid pointers. Changing Dynamic Memory Allocation in C Once the memory is allocated, its size can be changed through the realloc function that is provided by the standard C library. void* realloc(void* memory, size_t size); The size of dynamic memory allocation gets either expanded or reduced based on its new size. The realloc function takes the original dynamic memory allocation as its first argument and the new size as the second argument, as shoDwonwinnloLaidstaint ght6tp-:4/./www.pin5i.com/ 160 CHAPTER 6: Bionic API Primer Listing 6-4. Reallocating Dynamic Memory Allocation Using realloc int* newDynamicIntArray = (int*) realloc( dynamicIntArray, sizeof(int) * 32); if (NULL == newDynamicIntArray) { /* Unable to reallocate enough memory. */ ... } else { /* Update the memory pointer. */ dynamicIntArray = newDynamicIntArray; ... realloc function returns the pointer to reallocated dynamic memory. The function may move the NULL. new and delete keywords can be used to manage dynamic memory allocation instead of When dealing with C++ objects, it is highly recommended to use these C++ keywords instead of the functions provided through the standard C library. Unlike the standard C library functions, the C++ dynamic memory management keywords are type-aware, and they support C++ object lifecycle. In addition to allocating memory, the new keyword also invokes the class’ constructor; likewise, the delete keyword invokes the class’ destructor prior releasing the memory. Allocating Dynamic Memory in C++ Memory is allocated using the new keyword followed by the data type, as shown in Listing 6-5. Listing 6-5. Dynamic Memory Allocation for Single Element in C++ Code int* dynamicInt = new int; if (NULL == dynamicInt) { /* Unable to allocate enough memory. */ ... } else { /* Use the allocated memory. */ *dynamicInt = 0; ... } If an array of elements needs to be allocated, the number of elements is specified using the brackets, as shown in Listing 6-6. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 161 Listing 6-6.  Dynamic Memory Allocation for Multiple Elements in C++ Code int* dynamicIntArray = new int[16]; if (NULL == dynamicIntArray) { /* Unable to allocate enough memory. */ ... } else { /* Use the allocated memory. */ dynamicIntArray[8] = 8; ... } Freeing Dynamic Memory in C++ Dynamic memory should be explicitly freed using the C++ delete keyword by the application when it is no longer needed, as shown in Listing 6-7. Listing 6-7.  Freeing Single Element Dynamic Memory Using the delete Keyword delete dynamicInt; dynamicInt = 0; If an array of elements needs to be freed, the C++ delete[] keyword should be used instead, as shown in Listing 6-8. Listing 6-8.  Freeing Array Dynamic Memory Using delete[ ] delete[] dynamicIntArray; dynamicIntArray = 0; Take care to use the proper delete keyword; failure to do so will result in memory leaks in the native application. Changing Dynamic Memory Allocation in C++ The C++ programming language does not have built-in support for reallocating dynamic memory. The memory allocation is done based on the size of the data type and the number of elements. If the application logic requires increasing or decreasing the number of elements during runtime, it is highly recommended to use the suitable Standard Template Library (STL) container classes. Mixing the Memory Functions and the Keywords Developers must use the proper function and keyword pairs when dealing with dynamic memory. Memory blocks that are allocated through malloc must be released through the free keyword; likewise, memory blocks that are allocated through new keyword must be released with the delete keyword accordingly. Failure to do so will result in unknown application behavior. Download at http://www.pin5i.com/ 162 CHAPTER 6: Bionic API Primer Standard File I/O Native applications can interact with the file system through the Standard File I/O (stdio) functions that are provided by the standard C library. Two flavors of file I/O are provided through the standard C library:  Low-level I/O: Primitive I/O functions with finer grade of control over the data source.  Stream I/O: Higher-level, buffered I/O functions more suitable for dealing with data streams. The stream based I/O is more flexible and convenient when dealing with regular files. This section  stdin: Standard input stream for the application  stdout: Standard output stream for the application  stderr: Standard error stream for the application As the native application on Android runs as a module behind the graphical user interface (GUI), these streams are not very useful. While integrating legacy code, you should make sure that any use of these standard streams is properly handled through the GUI. As explained in the “Console Logging” section in Chapter 5, the stdout and stderr streams can be directed to Android system log by setting the log.redirect-stdio system property prior starting the application. Using the Stream I/O Stream I/O constructs and functions are defined in the stdio.h standard C library header file. In order to use stream I/O in native applications, this header file should be included in advance, as shown in Listing 6-9. Listing 6-9.  Including Standard I/O Header File to Use the Stream I/O #include  For historical reasons, the type of data structure representing a stream is called FILE, not a stream, in the standard C library. A FILE object holds all of the internal state information for the stream I/O connection. The FILE object is created and maintained by the stream I/O functions and is not expected to be directly manipulated by the application code. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 163 Opening Streams A new stream to a new or an existing file can be opened through the stream I/O fopen function. The fopen function takes the name of the file, and the open type as arguments, and returns a pointer to the stream. FILE* fopen(const char* filename, const char* opentype); The second argument to fopen function, the opentype, is a string that controls how the file is opened. It should begin with one of the following open types:  r: Opens an existing file as read-only.  w: Opens the file as write-only. If the file already exists, it gets truncated to zero length.  a: Opens the file in append mode. File content is preserved, and the new output gets appended to the end of the file. If the file does not exist, a new file is opened.  r+: Opens the file in read-write mode.  w+: Opens the file in read-write mode. If the file already exists, it gets truncated to zero length.  a+: Opens the file for reading and appending. The initial file position is set to the beginning for reading and to the end of the file for appending. Note  The buffers should be flushed using the fflush function prior to switching between reading and writing if the file is opened in dual-mode with either r+, w+, or a + . If the file could not be opened with the requested mode, the fopen function returns a NULL pointer. In case of success, a stream pointer, a FILE pointer, is returned for communicating with the stream, as shown in Listing 6-10. Listing 6-10.  Opening a Stream in Write-Only Mode #include  ... FILE* stream = fopen("/data/data/com.example.hellojni/test.txt", "w"); if (NULL == stream) { /* File could not be opened for writing. */ } else { /* Use the stream. */ /* Close the stream. */ } Once the stream is opened, it caDnowbnelouasdedatfhotrtpre:/a/dwinwgwa.pnidn5wi.criotmin/g until it gets closed. 164 CHAPTER 6: Bionic API Primer Writing to Streams Stream I/O provides four functions for writing to a stream. This section will briefly explore these functions. Writing Block of Data to Streams The fwrite function can be used for writing blocks of data to the streams. size_t fwrite(const void* data, size_t size, size_t count, FILE* stream); fwrite function writes count number of elements of size size from the data to given stream stream.   Writing Block of Data to Stream Using fwrite  = fwrite(data, sizeof(char), count, stream)) { /* Error occured while writing to stream. */ It returns the number of elements actually written to the stream. In case of success, the returned value should be equal to the value given as the count; otherwise, it indicates an error in writing. Writing Character Sequences to Streams Sequence of null-terminated characters can be written to a stream using the fputs function. int fputs(const char* data, FILE* stream); As shown in Listing 6-12, the fputs function writes the given character sequence data to the given stream, named stream. Listing 6-12.  Writing Character Sequence to the Stream Using fputs /* Writing character sequence to stream. */ if (EOF == fputs("hello\n", stream)) { /* Error occured while writing to the stream. */ } If the character sequence cannot be written to the stream, fputs function returns EOF. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 165 Writing a Single Character to Streams A single character or byte can be written to a stream using the fputc function. int fputc(int c, FILE* stream); As shown in Listing 6-13, the fputc function takes the single character c as an integer and converts it to a unsigned char prior writing to the given stream, named stream. Listing 6-13.  Writing a Single Character to Stream Using fputc char c = 'c'; /* Writing a single character to stream. */ if (c ! = fputc(c, stream)) { /* Error occured while writing character to string. } If the character cannot be written to the stream, fputc function returns EOF; otherwise it returns the character itself. Writing Formatted Data to Streams The fprintf function can be used to format and output variable number of arguments to the given stream. int fprintf(FILE* stream, const char* format, ...); It takes a pointer to the stream, the format string, and variable number of arguments that are referenced in the format. The format string consists of a mix of ordinary characters and format specifiers. Ordinary characters in the format string are passed unchanged into the stream. Format specifiers cause the fprintf function to format and write the given arguments to the stream accordingly. The most frequently used specifiers are  %d, %i: Formats the integer argument as signed decimal.  %u: Formats the unsigned integer as unsigned decimal.  %o: Formats the unsigned integer argument as octal.  %x: Formats the unsigned integer argument as hexadecimal.  %c: Formats the integer argument as a single character.  %f: Formats the double precision argument as floating point number.  %e: Formats the double precision argument in fixed format.  %s: Prints the given NULL-terminated character array.  %p: Print the given pointer as memory address.  %%: Writes a % charDaocwtenrl.oad at http://www.pin5i.com/ 166 CHAPTER 6: Bionic API Primer As shown in Listing 6-14, the order and the type of the provided arguments to fprintf function should match the specifiers in the format string. Listing 6-14.  Writing Formatted Data to the Stream /* Writes the formatted data. */ if (0 > fprintf(stream, "The %s is %d.", "number", 2)) { /* Error occurred while writing formatted data. */ } The fprintf function returns the number of characters written to the stream. In case of an error, it fprintf manual page at .  Normal termination of the application.  When a newline is written in case of line buffering.  When the buffer is full.  When the stream is closed. Stream I/O also provides the fflush function to enable applications to manually flush the buffer as needed. int fflush(FILE* stream); As shown in Listing 6-15, the fflush function takes the stream pointer and flushes the output buffer. Listing 6-15.  Flushing the Buffer Using fflush Function char data[] = { 'h', 'e', 'l', 'l', 'o', '\n' }; size_t count = sizeof(data) / sizeof(data[0]); /* Write data to stream. */ fwrite(data, sizeof(char), count, stream); /* Flush the output buffer. */ if (EOF == fflush(stream)) { /* Error occured while flushing the buffer. */ } Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 167 If the buffer cannot be written to the actual file, fflush function returns EOF; otherwise, it returns zero. Reading from Streams Similar to writing, stream I/O provides four functions for reading from a stream. Reading Block of Data from Streams The fread function can be used for reading blocks of data from the stream. size_t fread(void* data, size_t size, size_t count, FILE* stream); As shown in Listing 6-16, the fread function reads count number of elements of size (size) into the buffer data from the given stream, named stream. It returns the number of elements actually read. Listing 6-16.  Reading Block Data of Four Characters from the Stream char buffer[5]; size_t count = 4; /* Read 4 characters from the stream. */ if (count ! = fread(buffer, sizeof(char), count, stream)) { /* Error occured while reading from the stream. */ } else { /* Null terminate. */ buffer[4] = NULL; /* Output buffer. */ MY_LOG_INFO("read: %s", buffer); } In the case of success, the returned number of elements should be equal to the value passed as count. Reading Character Sequences from Streams The fgets function can be used to read a newline-terminated character sequence from the given stream. char* fgets(char* buffer, int count, FILE* stream); As shown in Listing 6-17, the fgets function reads at most count-1 characters up to and including the newline character into the character array buffer from the given stream, named stream. Download at http://www.pin5i.com/ 168 CHAPTER 6: Bionic API Primer Listing 6-17.  Reading a Newline-Terminated Character Sequence char buffer[1024]; /* Read newline terminated character sequence from the stream. */ if (NULL == fgets(buffer, 1024, stream)) { /* Error occured while reading the stream. */ } else { MY_LOG_INFO("read: %s", buffer); NULL pointer. fgetc function can be used to read a single unsigned char from the streams. fgetc functions reads a single character from the stream and returns it Listing 6-18.  Reading a Single Character from the Stream unsigned char ch; int result; /* Read a single character from the stream. */ result = fgetc(stream); if (EOF == result) { /* Error occured while reading from the stream. */ } else { /* Get the actual character. */ ch = (unsigned char) result; } If end-of-file indicator for the stream is set, it returns EOF. Reading Formatted Data from Streams The fscanf function can be used to read formatted data from the streams. It works in a way similar to the fprintf function, except that it reads the data based on the given format into the provided arguments. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 169 int fscanf(FILE* stream, const char* format, ...); It takes a pointer to the stream, the format string, and variable number of arguments that are referenced in the format. The format string consists of a mix of ordinary characters and format specifiers. Ordinary characters in the format string are used to specify characters that must be present in the input. Format specifiers cause the fscanf function to read and place the data into the given arguments. The most frequently used specifiers are  %d, %i: Reads a signed decimal.  %u: Reads an unsigned decimal.  %o: Reads an octal number as unsigned integer.  %x: Reads a hexadecimal number as unsigned integer.  %c: Reads a single character.  %f: Reads a floating point number.  %e: Reads a fixed format floating point number.  %s: Scans a string.  %%: Escapes the % character. As shown in Listing 6-19, the order and the type of the provided arguments to fscanf function should match the specifiers in the format string. Listing 6-19.  Reading Formatted Data from the Stream char s[5]; int i; /* Stream has "The number is 2" */ /* Reads the formatted data. */ if (2 ! = fscanf(stream, "The %s is %d", s, &i)) { /* Error occured while reading formatted data. */ } On success, the fscanf function returns the number of items read. In case of an error, EOF is returned. More information on the format string, including the full list of specifiers and other modifiers, can be found in fscanf manual page at http://pubs.opengroup.org/onlinepubs/009695399/functions/fscanf.html. Checking for End of File When reading from a stream, the feof function can be used to check if the end-of-file indicator for the stream is set. int feof(FILE* stream); Download at http://www.pin5i.com/ 170 CHAPTER 6: Bionic API Primer As shown in Listing 6-20, the feof function takes the stream pointer as an argument and returns a non-zero value if the end of file is reached; otherwise, it returns zero if more data can be read from the stream. Listing 6-20. Reading Strings from stream Until the End of the File char buffer[1024]; /* Until the end of the file. */ while (0 == feof(stream)) { /* Read and output string. */ fgets(buffer, 1024, stream); MY_LOG_INFO("read: %s", buffer); fseek function. fseek function uses the stream pointer, the relative offset, and the whence as the reference point  SEEK_SET: Offset is relative to the beginning of stream.  SEEK_CUR: Offset is relative to current position.  SEEK_END: Offset is relative to the end of the stream. The example code, shown in Listing 6-21, writes four characters, rewinds back the stream 4 bytes, and overwrites them with a different set of characters. Listing 6-21. Rewinding the Stream for 4 Bytes /* Write to the stream. */ fputs("abcd", stream); /* Rewind for 4 bytes. */ fseek(stream, -4, SEEK_CUR); /* Overwrite abcd with efgh. */ fputs("efgh", stream); Error checking is omitted in the example code. The fseek function returns zero if the operation is successful; otherwise a non-zero value indicates the failure. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 171 Checking Errors Most stream I/O functions returns EOF to indicate both the errors as well as to report end-of-file. The ferror function can be used to check if an error has occurred on a previous operation. int ferror(FILE* stream); As shown in Listing 6-22, the ferror function returns a non-zero value if the error flag is set for the given stream. Listing 6-22.  Checking for the Errors /* Check for the errors. */ if (0 ! = ferror(stream)) { /* Error occured on the previous request. */ } Closing Streams Streams can be closed using the fclose function. Any buffered output gets written to the stream, and any buffered input is discarded. int fclose(FILE* stream); The fclose function takes the stream pointer as argument. It returns zero in case of success and EOF if an error is occurred while closing the stream, as shown in Listing 6-23. Listing 6-23.  Closing a Stream Using fclose Function if (0 ! = fclose(stream)) { /* Error occured while closing the stream. */ } The error may indicate that the buffered output could not be written to the stream due to insufficient space on the disk. It is always a good practice to check the return value of the fclose function. Interacting with Processes Bionic enables native applications to start and interact with other native processes. Native code can execute shell commands; it can execute a process in the background and communicate to it. This section will briefly mention some of the key functions. Download at http://www.pin5i.com/ 172 CHAPTER 6: Bionic API Primer Executing a Shell Command The system function can be used to pass a command to the shell. In order to use this function, the stdlib.h header file should be included first. #include  As shown in Listing 6-24, the function blocks the native code until the command finishes executing. Listing 6-24.  Executing a Shell Command Using the System Function int result; = system("mkdir /data/data/com.example.hellojni/temp"); { /* Execution of the shell failed. */ system command does not provide a communication channel for the native application to either waits until the command finishes executing. In certain cases, having a communication channel between the native code and the executed process is needed. The popen function can be used to open a bidirectional pipe between the parent process and the child process. In order o use this function, the stdio.h standard header file should be included first. FILE *popen(const char* command, const char* type); The popen function takes the command to be executed and the type of the requested communication channel as arguments and returns a stream pointer. In case of an error, it returns NULL. As shown in Listing 6-25, the stream I/O functions that you explorer earlier in this chapter can be used to communicate with the child process as interacting with a file. Listing 6-25.  Opening a Channel to ls Command and Printing the Output #include  ... FILE* stream; /* Opening a read-only channel to ls command. */ stream = popen("ls", "r"); if (NULL == stream) { MY_LOG_ERROR("Unable to execute the command."); } Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 173 else { char buffer[1024]; int status; /* Read each line from command output. */ while (NULL ! = fgets(buffer, 1024, stream)) { MY_LOG_INFO("read: %s", buffer); } /* Close the channel and get the status. */ status = pclose(stream); MY_LOG_INFO("process exited with status %d", status); } Note  The popen streams are fully buffered by default. You will need to use fflush function to flush the buffer as needed. When the child process finishes executing, the stream should be closed using the pclose function. int pclose(FILE* stream); It takes the stream pointer as the argument and waits for the child process to terminate and returns the exit status. System Configuration The Android platform holds the system properties as a simple key-value pair. Bionic provides a set of functions to enable native applications to query the system properties. In order to use these functions, the system properties header file should be included first. #include  The system properties header file declares the necessary structures and functions. Each system property consists of a maximum of PROP_NAME_MAX character long name for the property and a maximum of PROP_VALUE_MAX characters long value. Getting a System Property Value by Name The __system_property_get function can be used to look up a system property by name. int __system_property_get(const char* name, char* value); Download at http://www.pin5i.com/ 174 CHAPTER 6: Bionic API Primer As shown in Listing 6-26, it copies the null-terminated property value to the provided value pointer and returns the size of the value. The total bytes copied will not be greater than PROP_VALUE_MAX. Listing 6-26.  Getting a System Property Value by Name char value[PROP_VALUE_MAX]; /* Gets the product model system property. */ if (0 == __system_property_get("ro.product.model", value)) { /* System property is not found or it has an empty value. */ } { MY_LOG_INFO("product model: %s", value); __system_property_find function can be used to get a direct pointer to the system property. It searches the system property by name and returns a pointer to it if it is found; otherwise it returns NULL. The returned pointer remains valid for the lifetime of the system, and it can be cached to avoid future lookups. As shown in Listing 6-27, the __system_property_read function can be used to obtain the property value from this pointer. Listing 6-27.  Getting a System Property by Name const prop_info* property; /* Gets the product model system property. */ property = __system_property_find("ro.product.model"); if (NULL == property) { /* System property is not found. */ } else { char name[PROP_NAME_MAX]; char value[PROP_VALUE_MAX]; /* Get the system property name and value. */ if (0 == __system_property_read(property, name, value)) Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 175 { MY_LOG_INFO("%s is empty."); } else { MY_LOG_INFO("%s: %s", name, value); } } The __system_property_read function takes pointers to the system property and two other character array pointers to return the system property name and value. int __system_property_read(const prop_info* pi, char* name, char* value); It copies the null-terminated property value to the provided value pointer, and returns the size of the value. The total characters copied will not be greater than PROP_VALUE_MAX. The name argument is optional; if a character array is supplied, it copies the system property name to the given value pointer. The total characters copied will not be greater than PROP_NAME_MAX. Users and Groups The Linux kernel is designed for multiuser platforms. Although Android is meant to be used by a single handset user, it still takes advantage of the user-based permission model.  Android runs the applications within a virtual machine sandbox and treats them as different users on the system. By simply relying on the user-based permission model, Android easily secures the system by preventing the applications from accessing other applications’ data and memory.  Services and hardware resources are also protected through the user-based permission model. Each of these resources has its own protection group. During application deployment, the application requests access to those resources. The application won’t be allowed to access any additional resources if it is not a member of the proper resource group. Bionic provides basic support for the user and group information functions, and most of these functions are only stubs with minimal or no functionality. This section covers the key ones. In order to use these functions, the unistd.h standard header file needs to be included first. #include  Getting the Application User and Group IDs Each installed application gets its own user and group ID starting from 10000. The lower IDs are used for system services. The user ID for the current application can be obtained using the getuid function, as shown in Listing 6-28. Download at http://www.pin5i.com/ 176 CHAPTER 6: Bionic API Primer Listing 6-28.  Getting the Application User ID Using the getuid Function uid_t uid; /* Get the application user ID. */ uid = getuid(); MY_LOG_INFO("Application User ID is %u", uid); Similar to the user ID, the group ID for the current application can be obtained through the getgid function, as shown in Listing 6-29.   Getting the Application Group ID Using the getgid Function = getgid(); application number. For example, the user name for application with user ID 10040 will be app_40. The user name be obtained through the getlogin function, as shown in Listing 6-30. Listing 6-30.  Getting the Application User Name Using the getlogin Function char* username; /* Get the application user name. */ username = getlogin(); MY_LOG_INFO("Application user name is %s", username); Inter-Process Communication Bionic does not provide support for System V inter-process communication (IPC), in order to avoid denial-of-service attacks and kernel resource leakage. Although System V IPC is not supported, the Android platform architecture makes heavy use of IPC using its own flavor known as Binder. Android applications communicate with the system, services, and each other through the Binder interface. At the time of this writing, Bionic does not provide any official APIs to enable native applications to interact with the Binder interface. Currently, the Binder interface is only accessible through the Android Java APIs. Download at http://www.pin5i.com/ CHAPTER 6: Bionic API Primer 177 Summary In this chapter, you started exploring Bionic, a derivation of the BSD standard C library by Google for the Android operating system. You studied the standard C library functions that are exposed to the native applications through Bionic, such as memory management, standard I/O, process control, system configuration, plus user and group management functions. Beside the APIs mentioned, Bionic also provides multi-threading and networking APIs for the native applications. You will explore these APIs separately in individual chapters. Download at http://www.pin5i.com/ 7 Chapter Native Threads A thread is a mechanism enabling a single process to perform multiple tasks concurrently. Threads are lightweight processes sharing the same memory and resources of the same parent process. A single process can contain multiple threads executing in parallel. As part of the same process, threads can communicate with each other and share data. Android supports threads in both Java and the native code. In this chapter, you will be exploring different strategies and APIs that can be used for concurrent programming pertaining to native code. The following key topics are covered in this chapter:  Java vs. POSIX Threads  Thread synchronization  Controlling the thread lifecycle  Thread priorities and scheduling strategies  Interacting with Java from within native threads Creating the Threads Example Project Before going into the details of having multithreading in native code, you will create a simple example application that will act as a testbed. The example application will provide the following:  An Android application project with native code support.  A simple GUI to define the number of threads, the number of iterations per worker, a button to start threads, and a text view showing the progress messages from the native workers during runtime.  A native worker function mimicking a long-lasting task. While working through the chapter, you will expand this example application to demonstrate different techniques and APIs pertaining to multithreading in native code. 179 Download at http://www.pin5i.com/ 180 CHAPTER 7: Native Threads Creating the Android Project Start by creating a new Android application project. 1. Open the Eclipse IDE and choose File ➤ New ➤ Other from the top menu bar to launch the New dialog, as shown in Figure 7-1.   New dialog 2. From the list of wizards, expand the Android category. 3. Choose Android Application Project from the sub-list. 4. Click the Next button to launch the New Android App wizard, as shown in Figure 7-2. Figure 7-2.  New Android App dialog Download at http://www.pin5i.com/ CHAPTER 7: Native Threads 181 5. Set Application Name to Threads. 6. Set Project Name to Threads. 7. Set Package Name to com.apress.threads. 8. Set Build SDK to Android 4.0. 9. Set Minimum Required SDK to API 8. 10. Click the Next button to proceed. 11. Keep the default settings for the launcher icon by clicking the Next button. 12. Select the Create activity. 13. Choose Blank Activity from the template list. 14. Click the Next button to proceed. 15. In the New Blank Activity step, accept the default values by clicking the Finish button. Adding the Native Support Native support should be added to the new Android project in order to use native code. Using the Project Explorer view, right-click the Threads project, and choose Android Tools ➤ Add Native Support from the context menu. As shown in Figure 7-3, the Add Android Native Support dialog will be launched. Figure 7-3. Add Android Native Support dialog Set the Library Name to Threads and click the Finish button. Native code support will be added to the project. Declaring the String Resources The application’s user interface will be referring to a set of string resources. Using the Project Explorer view, expand the res directory for resources. Expand the values subdirectory, and doubleclick on strings.xml to open the string resources in the editor. Replace the content as shown in Listing 7-1. Download at http://www.pin5i.com/ 182 CHAPTER 7: Native Threads Listing 7-1.  Content of res/values/strings.xml File Threads Settings Threads Thread Count Iteration Count Start Threads 7-4). Figure 7-4.  Simple user interface for the example application Using the Project Explorer view, expand the layout subdirectory under the res directory. Doubleclick the activity_main.xml layout file to open it in the editor. Replace the content as shown in Listing 7-2. Listing 7-2.  Content of res/layout/activity_main.xml File

    Top_arrow
    回到顶部
    EEWORLD下载中心所有资源均来自网友分享,如有侵权,请发送举报邮件到客服邮箱bbs_service@eeworld.com.cn 或通过站内短信息或QQ:273568022联系管理员 高进,我们会尽快处理。