# Extending the Rhodes Framework ## Introduction There are three ways to extend Rhodes. You can add to the Ruby gems supported by Rhodes ("Rhodes extensions"). You can create new "native extensions" in the underlying SDK for a given smartphone operating system. You can extend the types of views available in Rhodes ("native extensions"). ## Ruby Extensions ### Supported extensions and libraries To keep Rhodes lightweight we left out some libraries. Our C/C++ implementation is based on original Ruby C code, 1.9 release. Our Java implementation is based on [XRuby](http://xruby.com), which supports Ruby 1.8 (We didn't use JRuby because it is substantially bigger and required version of java which is not available on most of the target mobile platforms). Both implementations support such core classes and module as: BasicObject, Object, Module, Class, Integer, Float, Numeric, Bignum, Rational, Complex, Math, String, StringScanner, StringIO, Array, Hash, Struct, Regexp, RegexpError, MatchData, Data, NilClass, TrueClass, FalseClass, Comparable, Enumerable, Enumerator, Converter, Marshal, IO, Dir, Time, Date, Signal, Mutex, Thread, ThreadGroup, Process, Fiber, FiberError, Method, UnboundMethod, Binding, RubyVM, GC, Exception, SystemExit, fatal, SignalException, Interrupt, StandardError, TypeError, ArgumentError, IndexError, KeyError, RangeError, ScriptError, SyntaxError, LoadError, NotImplementedError, NameError, NoMethodError, RuntimeError, SecurityError, NoMemoryError, EncodingError, CompatibilityError, SystemCallError, Errno, ZeroDivisionError, FloatDomainError, IOError, EOFError, ThreadError We are using Rubinius specs to test Ruby compatibility across different platforms. ### JSON library support For parsing use Rho::JSON.parse, no extension required. Ruby code example: :::ruby parsed = Rho::JSON.parse("[{\"count\":10}]") For generate use JSON extension. Add to build.yml: extensions: ["json"] In case of several extensions, insert space after extension name and comma: extensions: ["json", "net-http"] Ruby code example: :::ruby require 'json' json_data = ::JSON.generate(some_object) See JSON tests in [Rhodes System API Samples application](http://github.com/rhomobile/rhodes-system-api-samples/tree/master/app/JsonTest/controller.rb) as an example. ### XML handling There are two ways of handling XML directly in Rhodes. The Rexml library and the much faster RhoXML library. #### Rexml Add to build.yml: extensions: ["rexml", "set"] Ruby code example: :::ruby require 'rexml/document' file = File.new("bibliography.xml") doc = REXML::Document.new(file) puts doc #### RhoXML This is a reduced version of rexml. Rhoxml has the same syntax as rexml, but smaller in size and faster. For Blackberry this is the only choice, because rexml is too slow. Change rexml to rhoxml in build.yml: extensions: ["rhoxml"] No more changes required. Rhoxml limitations: 1. Decoding xml text is not fully implemented. See document.rb line 503 (Text::unnormalize). Need to implement non regular expression decoding. 2. No DTD, validation and formatters support 3. Support only elements and attributes. No cdata, comments, etc. #### XML Stream parser To process xml faster (without building DOM xml tree in memory) you can use StreamParser: :::ruby class MyStreamListener def initialize(events) @events = events end def tag_start name, attrs #puts "tag_start: #{name}; #{attrs}" @events << attrs if name == 'event' end def tag_end name #puts "tag_end: #{name}" end def text text #puts "text: #{text}" end def instruction name, instruction end def comment comment end def doctype name, pub_sys, long_name, uri end def doctype_end end def attlistdecl element_name, attributes, raw_content end def elementdecl content end def entitydecl content end def notationdecl content end def entity content end def cdata content #puts "cdata: #{content}" end def xmldecl version, encoding, standalone end end def parse_xml(str_xml) @events = [] list = MyStreamListener.new(@events) REXML::Document.parse_stream(str_xml, list) ... It supported in RhoXml and Rexml extensions. For example see : [`\spec\phone_spec\app\spec\xml_spec.rb`](https://github.com/rhomobile/rhodes/blob/master/spec/phone_spec/app/spec/xml_spec.rb) ("should stream parse" spec) and rexml stream parser documentation ### Barcode Add to build.yml: extensions: ["Barcode"] See details [here](device-caps#barcode). ### net/http Add to build.yml: extensions: ["net-http", "thread", "timeout", "uri"] ### hmac Add to build.yml: extensions: ["hmac", "digest", "digest-sha1"] Example: :::ruby require 'base64' require 'hmac-sha1' def test_hmac key = '1234' signature = 'abcdef' hmac = HMAC::SHA1.new(key) hmac.update(signature) puts Rho::RhoSupport.url_encode(Base64.encode64("#{hmac.digest}\n")) end ### FileUtils Add to build.yml: extensions: ["fileutils"] DryRun, NoWrite and Verbose are commented out modules since they using `eval` function. Blackberry is not supported.
Use Ruby class `Dir` whenever possible. ### Notes on Ruby standard library support For iPhone the Date class is supported :::ruby require 'date' puts Date.today.to_s For Blackberry Date is still not supported. Use this instead: :::ruby require 'time' Time.now.strftime('%Y-%m-%d') ### Adding Ruby Extension Libraries to Your Rhodes Application Create folder 'extensions' under application root. Copy folder with Ruby library to 'extensions' folder. (This will work for "pure ruby" extensions. Extensions which implemented in c/c++ or such you will have to compile for the target platform and link with Rhodes.) Add extension with folder library name to build.yml: extensions: ["myext"] This library will be available for require: :::ruby require 'myext' Using this technique you can easily remove extension from application or include some extension for particular platform: iphone: extensions: ["mspec", "fileutils"] wm: extensions: ["json"] ### Adding Libraries to Your Rhodes Application During the course of your app development you might need to add an external ruby library with extra features that the rhodes framework doesn't provide. While we don't guarantee that all ruby libraries will work on the mobile device, you can follow the steps below to add and test libraries as needed. In Rhodes, the require path is relative to the "app" subdirectory, since this is what gets bundled with the rhodes client. Assuming your application is called "mynewapp", create a directory under app called lib (or whatever you wish to call it): :::term $ cd mynewapp $ mkdir app/lib Add your ruby files to this directory: :::ruby $ cp /path/to/my_lib.rb app/lib/my_lib.rb Now, in your application (controller.rb for example), you can load this library like the following: :::ruby require 'rho/rhocontroller' require 'lib/my_lib' class TicketController < Rho::RhoController def use_lib @a = MyLib.new ... end end Please note that "rubygems" are not loaded on the device Ruby VM because of size constraints, therefore all third-party ruby library source files must be put into the lib directory as described above. ### Adding Libraries to Rhodes Framework There are two ways to add Ruby libraries to the Rhodes framework, essentially dependent upon how you choose to build your Rhodes application. If you are using Rhodes via the RubyGems installation, you must add external Ruby libraries to your RubyGems installation directory for the 'rhodes-framework' gem. Your RubyGems installation directory can be found with `gem env` in a terminal. For example, a user on Linux might place additional libraries in the following directory: /usr/local/lib/ruby/gems/1.8/gems/rhodes-x.x.x/lib/framework Similarly, a user on Mac OSX 10.5 might place them here: /Library/Ruby/Gems/1.8/gems/rhodes-x.x.x/lib/framework For Windows, this location might be: C:/ruby/lib/ruby/gems/1.8/gems/rhodes-x.x.x/lib/framework If you are using a clone of the Rhodes Git repository, you can put additional libraries in the following directory (preferably on your own github fork): /lib/framework Including the library into your application is simple once the library is in the proper directory. Assuming the library has been added to the correct location, require the library from a controller in your Rhodes application: :::ruby require 'libname' You can now use the added library to access additional functionality not provided by the Rhodes framework. NOTE: Once again, it should be mentioned that not all libraries are guaranteed to work with Rhodes. ## Encryption libraries ### digest - based extensions digest, digest-sha1, digest-md5 Add to build.yml: extensions: ["digest", "digest-sha1", "digest-md5"] NOTE: digest should be included in extensions list to use digest-base libraries ### OpenSSL - based libraries openssl, ezcrypto Add to build.yml: extensions: ["openssl.so", "openssl", "digest-sha2", "ezcrypto"] digest-sha2 Add to build.yml: extensions: ["openssl.so", "openssl", "digest", "digest-sha2" ] NOTE: openssl.so is native c-library and should be included in extensions list to use openssl-base libraries ## Native Extensions Starting from 2.0, Rhodes supports native extensions for Android, iPhone, WM and BlackBerry platforms. Native extensions are extensions written in the native language for the platform (C/C++/ObjC for iPhone, C/C++/Java for Android, C/C++ for WM, Java for Blackberry). ## Generating a Native Extension Template The rhogen extension command allows you to create a native extension template for your Rhodes application. This template contains build scripts, projects, and native source code for the functions calc_summ and native_process_string, which you can use as templates to create your own native extension functions. Before you use the rhogen extension command, you must have or you must create a Rhodes application from which you will call the native extension. You can [generate a Rhodes application](generator) from the command line or from RhoStudio. Then, on the command line, navigate to the main folder in your Rhodes application. Run the following command: :::term rhogen extension yourextension where `yourextension` is the name you want to use for your native extension. This command will create two folders in your application folder: * `yourextensionTest`, which contains a test controller and page for the generated native extension. * `extensions`, which contains the generated native extension source code for the iPhone, Android, Windows Mobile, and Blackberry platforms. ### Understanding the Generated Native Extension Test Implementation In your applications `app` folder is a folder named `yourextensionTest`. It contains two files that have a test implementation of the generated native extension example functions: * controller.rb - An example of application controller code with execution of the generated extension code. * index.erb - A page that executes the application controller. The controller.rb is a simple Ruby controller file that calls the generated native extension functions of calc_summ and process_string. It has a function, run_test, that calls the calc_summ native function to calculate a sum, then uses an Alert popup to show the sum via the process_string native function. :::ruby require 'rho/rhocontroller' require 'yourextension' class YourextensionTestController < Rho::RhoController @layout = :simplelayout def index render :back => '/app' end def run_test sum = Yourextension.calc_summ(3,7) Alert.show_popup YourextensionModule::YourextensionStringHelper::process_string('test')+sum.to_s render :action => :index, :back => '/app' end end The index.erb provides a page where you can click a link to run the run_test function. :::html

Yourextension Extension Test

<%= link_to '[Run Yourextension test]', { :action => :run_test }%>
## Understanding the Generated Native Extension Code The extensions folder has the following contents. :::term yourextension - A folder named for your extension. ext.yml - The native extension configuration file. It contains the library name, java entry point (class name for BB), C entry point, etc. yourextension.rb - The extension Ruby code. This file contains Ruby classes that you wish to execute from your native code; it has the generated Ruby class YourextensionStringHelper. All files named in this folder will be added to build extent "ext" folder and file. ext - The folder with the extension sources for the native build. build - A unix based build script (for Mac OS). build.bat - A Windows based build script (for MS Windows). - The folder containing the shared and platform folders. shared - The folder with the shared code (this is used by all platforms except Blackberry). Contains the interface for the native extension. platform - The folder with the platform dependent code, This contains the native extension code for your platform, which you will rewrite for your native extension. To create your native extension, you will rewrite code within: * the `shared` folder, which contains the interface for your native extension. * the `platform` folder, which contains the source code for your native extension. ## Rewriting the Generated Native Extension Interface Code The `shared` folder, within `extensions/yourextension/ext/yourextension`, contains the interface code for your native extension that is used by all platforms except for Blackberry. It contains the following folders and files. :::term ruby - The folder with the native-Ruby code wrapper. We use SWIG to generate the C wrapper code from the Ruby interface file. yourextension.i - The Ruby interface declaration for native code #import You could rewrite this header file to look like this for an accelerometer. :::cplusplus #import @interface Accel : UIViewController { } - (void)start; @end ### Rewriting the Native Extension Objective C Manifest File If you generate an Accelerometer native extension, you get the following manifest file (accelerometer.m) for the iPhone. :::cplusplus #import "Accelerometer.h" #include "ruby/ext/rho/rhoruby.h" VALUE accelerometer_native_process_string(const char* str) { NSString* os_string = @""; NSString* result_string = [os_string stringByAppendingString:[NSString stringWithUTF8String:str]]; result_string = [result_string stringByAppendingString:os_string]; VALUE result = rho_ruby_create_string([result_string UTF8String]); return result; } int accelerometer_calc_summ(int x, int y) { return (x+y); } You can rewrite the accelerometer.m file as follows to access the iPhone accelerometer functionality. Replace the stub code for accelerometer_native_process_string and accelerometer_calc_summ with your code: in this case, with native functions start and get_readings. :::cplusplus #import "Accelerometer.h" // store the acceleration values double gx,gy,gz; // Reference this Objective C class. Accel *accel; @implementation Accel -(void)start { // Initialize the accelerometer variables. gx = gy = gz = 0; // Set the update interval. [[UIAccelerometer sharedAccelerometer] setUpdateInterval:0.025]; // Set the delegate to this class, so the iPhone accelerometer will // call you back on the delegate method. [[UIAccelerometer sharedAccelerometer] setDelegate:self]; } -(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration // Get the accelerometer values from the iPhone. { gx = acceleration.x; gy = acceleration.y; gz = acceleration.z; } @end // This C function interfaces with the Objective C start function. void accelerometer_start(void) { // make sure we can only start this method once static bool started = false; if(!started) { // Initialize the Objective C accelerometer class. accel = [[Accel alloc] init]; // Start the accelerometer readings. [accel start]; started = true; } } // This C function allows us to get the readings from the accelerometer so // they can be returned to the Ruby code. void accelerometer_get_readings(double *x, double *y, double *z) { *x = gx; *y = gy; *z = gz; } ### Adding New Files to the Xcode Project If you create any new files for your native extension, you must add them to the Xcode project. In Xcode, open yourextension.xcodeproj. Then add the new files to this project. ## Rewriting the Generated Native Extension Source Code for Android The Android generated native extension code is contained in the following structure. :::term android - folder with Android specific code Rakefile - build script for Android platform ext_build.files - list of java files should be compiled jni - folder with Android native code src yourextension.cpp - native Android code with implementation of native function declared in Ruby wrapper src - folder with Java Android code com yourextension yourextension.java - Android java code ### Rewrite the Generated Android Native Extension Java File The folder in extensions/yourextension/ext/yourextension/platform/android/src contains a generated Java file, yourextension.java. This file contains the native extension code for the process_string function. Rewrite this code to reference your native extension function. :::java package com.yourextension; public class Yourextension { public static String processString(String str) { return "" + str + ""; } } ### Rewrite the Generated Android C++ Native Extension Rewrite the C++ file for your native extension. This is where you implement the C function that is declared in the Interface file. You would also implement any Android native C++ code here. The generated C++ file for Android, yourextension.cpp (within the folder extensions/yourextension/ext/yourextension/platform/android/jni/src) contains the C interfaces for the native_process_string function and the calc_summ functions. Rewrite that code for the interfaces for your native extension functions: rename the functions to match yours, redo their argument lists, etc. :::cplusplus #include #include "rubyext/WebView.h" #include #include "ruby/ext/rho/rhoruby.h" extern "C" VALUE yourextension_native_process_string(const char* str) { JNIEnv *env = jnienv(); jclass cls = rho_find_class(env, "com/yourextension/Yourextension"); if (!cls) return rho_ruby_get_NIL();; jmethodID mid = env->GetStaticMethodID( cls, "processString", "(Ljava/lang/String;)Ljava/lang/String;"); if (!mid) return rho_ruby_get_NIL();; jstring objStr = env->NewStringUTF(str); jstring jstr = (jstring)env->CallStaticObjectMethod(cls, mid, objStr); env->DeleteLocalRef(objStr); const char* buf = env->GetStringUTFChars(jstr,0); VALUE result = rho_ruby_create_string(buf); env->ReleaseStringUTFChars(jstr, buf); return result; } extern "C" int yourextension_calc_summ(int x, int y) { return (x+y); } ### Add New C++ Files to Rakefile You can code new C++ files; place them in the extensions/yourextension/ext/yourextension/platform/android/jni/src/ folder (the same folder containing yourextension.cpp). If you add new C++ files, add their file names to the list of C++ files in the Rakefile, which is in the folder extensions/yourextension/ext/yourextension/platform/android. In the Rakefile, there is a section coded as follows: :::term task :all => :config do src_files = [] src_files << $yourextensiondir + '/ext/yourextension/platform/android/jni/src/yourextension.cpp' src_files << $yourextensiondir + '/ext/yourextension/shared/ruby/yourextension_wrap.c' src_files << $yourextensiondir + '/ext/yourextension/shared/src/yourextension.c' # build native part build_extension('Yourextension', $arch, src_files) # java part will be built automatically (java files should be listed in ext_build.files and ext.yml should be configured) end Add a src_files line for every new .cpp file that you add to your native extension project, such as: :::term src_files << $yourextensiondir + '/ext/yourextension/platform/android/jni/src/yournewfile.cpp' ### Add New Java Files to ext_build.files You can code new Java files for your native extension; if you do so, place them in the extensions/yourextension/ext/yourextension/platform/android/src folder (the same folder containing yourextension.java). Then add that file to the list of Java files in ext_build.files, which is in the folder extensions/yourextension/ext/yourextension/platform/android. This sample listing of ext_build.files shows that it already lists the generated native extenstion Java file. :::term ext/yourextension/platform/android/src/com/yourextension/Yourextension.java ### Make changes to application AndroidManifest.xml You can specify changes to AndroidManifest.xml in the ext.yml file as a path to the files with the changes. There are three formats recognized by the build system, depending on the file extension: * .xml - xml file with common AndroidManifest.xml format; its tags will be merged into the final manifest. * .erb - ruby templates which will be injected into the final manifest. * .rb - ruby script which will run by manifest erb generator. android: manifest_changes: - ext/yourextension/platform/android/AndroidManifestAdds.xml - ext/yourextension/platform/android/ApplicationTagAdds1.erb - ext/yourextension/platform/android/ApplicationTagAdds2.erb - ext/yourextension/platform/android/ManifestTagAdds.erb - ext/yourextension/platform/android/AndroidManifestScript.rb #### XML This is the simplest way, if you know how your manifest has to look. Add the final AndroidManifest.xml to the extension and specify it in ext.yml. The build system will try to merge all the tags from the file into the final AndroidManifest.xml. Tags which exist in both Rhodes and extension manifest will be overwritten from the extension manifest. android: manifest_changes: ext/yourextension/platform/android/AndroidManifest.xml #### ERB template There are two common levels where additional definitions can be injected into AndroidManifest.xml. * Application tag * Manifest tag To add additional definitions to the Application tag, the template name must fit the file name mask of `Application*.erb`, such as `ApplicationManifestAdds.erb`. To add additional definitions to Manifest tag, the template name must fit the file name mask of `Manifest*.erb`. There may be a number of templates of each type. In the template, you may access manifest generator fields which holds common values used to generate the manifest. Below is example of a broadcast receiver definition added to 'application' tag by rhoconnect-push extension: :::xml #### RB script In case the methods listed above are not enough, you can write your own script that will change the values used to generate the manifest. You can hove a single script per extension. In the script, you may access the ERB generator instance as a local variable. :::ruby generator.permissions["#{generator.appPackageName}.permission.ANS"] = 'signature' generator.usesPermissions << "#{generator.appPackageName}.permission.ANS" generator.usesPermissions << 'com.motsolutions.cto.services.ans.permission.REGISTER' #### ERB Manifest Generator The following generator fields may be accessed from erb templates or scripts. * javaPackageName - read-only string * appPackageName - read-only string * versionName - read-write string * versionCode - read-write string * installLocation - read-write string * minSdkVer - read-write string * maxSdkVer - read-write string * permissions - hash of permission name/protectionLevel pairs * usesPermissions - array of permission names * usesLibraries - hash of library name/isRequired pairs * screenOrientation - read-write string * debuggable - read-write string (allows two values: 'true' or 'false') * rhodesActivityIntentFilters - array of hashes with filter values. Each hash can contain next keys: ** :act - string, intent action name ** :cat - array of strings with category names ** :data - hash with data tag attributes (name/value pairs) * manifestManifestAdds - array of strings with full paths to erb templates for 'manifest' tag * applicationManifestAdds - array of strings with full paths to erb templates for 'application' tag For more details about the values for the generator fields, refer to [Android Developer Documentation](http://developer.android.com/guide/topics/manifest/manifest-intro.html). You may also look in your Rhodes installation under /platform/android/Rhodes/AndroidManifest.xml.erb to study how these values are used. ## Rewriting the Generated Native Extension Source Code for Windows Mobile The Windows Mobile generated native extension code is contained in the following structure. :::term wm - folder with Windows Mobile specific code Rakefile - build script for WM platform .vcproj - Microsoft Visual Studio project file for the Windows Mobile native extension src - folder with WM specific sources _wm.h - header file _wm.cpp - native code with implementation of native function declared in Ruby wrapper ### Rewrite the Generated Native Extension C++ Files You will find the implementations of the Windows Mobile version of the native_process_string and the calc_summ functions in the files yourextension_wm.h and yourextension_wm.cpp, which are in the folder extensions/yourextension/ext/yourextension/platform/wm/src. Rewrite these files for your native extension functions: rename the functions to match yours, redo the argument lists, implement your native extension functionality, etc. Sample listing of yourextension_wm.cpp: :::cplusplus #include #include #include #include #include #include #include "rubyext/WebView.h" #include "ruby/ext/rho/rhoruby.h" #include "yourextension_wm.h" extern "C" VALUE yourextension_native_process_string(const char* str) { const char block[] = ""; char* buf = NULL; buf = (char*)malloc(strlen(str) + strlen(block)*2 + 1); strcpy(buf, block); strcat(buf, str); strcat(buf, block); VALUE result = rho_ruby_create_string(buf); free(buf); return result; } extern "C" int yourextension_calc_summ(int x, int y) { return (x+y); } ### Adding New Files to the Visual Studio Project If you create any new C++ files for your native extension, save them in the same location as the generated C++ file yourextension_wm.cpp, in extensions/yourextension/ext/yourextension/platform/wm/src. Open the Visual Studio project for your native extension: yourextension_wm.vcproj in extensions/yourextension/ext/yourextension/platform/wm. Add any new C++ files to this project. ## Rewriting the Generated Native Extension Source Code for Blackberry The Blackberry generated native extension code is contained in the following structure. :::term bb - folder with Blackberry specific code Rakefile - build script for BB platform .files - list of Java files to be compiled .jdp - Eclipse project (not required for a rake script build) src - folder with BB specific Java sources com .java - BB platform entry point class with implementation and registration of Ruby functions Note: You do not use the Interface file or SWIG with Blackberry. But you will rewrite the generated native extension Java file. ### Rewrite the Generated Native Extension Java File First, rewrite the Public functions, renaming them and changing the arguments for your native extension. :::java public String doProcessString( String str ) { return "" + str + ""; } public int doCalcSumm( int x, int y ) { return (x+y); } Then, in the void run() method, rewrite the native_process_string and the calc_summ methods declared in the YourextensionClass.getSingletonClass(). ## Creating Your Native Extension Without the Generator You can also create a native extension without using the rhogen extension command. This means you will need to write the entire extension (source code, interface file, build scripts, etc.) from scratch. In your Rhodes application folder, create a folder called "extensions". This is the folder that will contain all your native extesion code for your Rhodes application. Within the "extensions" folder, create a folder named for the extension that you are creating, such as "accelerometer". An example path for a Rhodes application named "accel" would be `accel/extensions/accelerometer`. And in that folder, create a folder called "ext." ### Create Your Extensions Configuration File In the folder named according to your extension name, such as `accel/extensions/accelerometer`, create `ext.yml` file. In this file, you have two lines to define your initialization function and your libraries. Here is an example for an accelerometer extension. entry: Init_Accelerometer libraries: [ "accelerometer" ] The first line names the function that is the entry point into your extension; the function is named Init_Your-extension-name. This function is called when your Rhodes application starts, so it will contain all your initialization code for your extension. The second line is the name of your library extension that holds your binary extensions that are compiled and integrated into your Ruby code. It is named after the name of your extension folder; in this example, it is named accelerometer. You can have more that one library, in which case you would separate the libraries with commas: libraries: [ "extension1", "extension2" ] For Android you may add additional extension parameters within android section: * rhodes_listener - Java class name which implements com.rhomobile.rhodes.extmanager.IRhoListener interface (used to handle Rhodes application UI events) * manifest_changes - filename with Android manifest items (in form of common AndroidManifest.xml), this file will be joined with main AndroidManifest.xml * adds - folder with any additional files for add to main application build folder (may contain resources, assets and shared libraries) * source_list - filename with list of *.java files to build with main Rhodes application package. Here is an example of an ext.yml file for an NFC extension. This example is taken from the Rhodes installation, located at [Rhodes root]/lib/extensions/Nfc. entry: Init_Nfc libraries: ["Nfc"] android: rhodes_listener: com.rhomobile.nfc.Nfc manifest_changes: ext/nfc/platform/android/AndroidManifest.xml adds: ext/nfc/platform/android/additional_files source_list: ext/nfc/platform/android/ext_build.files ### Create Your Build Script In the "ext" folder, such as `accel/extensions/accelerometer/ext`, you will create your build scripts and your native extension code. Create a build script file. Name this file `build` for Macintosh and `build.bat` for Windows. This file executes a rake file that contains all your compiling commands for your extension. (You can instead put your compiling commands in the build file, but this chapter uses the Rakefile method.) For the Macintosh, the build file contains: #!/bin/sh rake For Windows, the build.bat file contains: rake --trace The above build scripts execute a Rakefile script which you create: this Rakefile compiles your extension code and produces the libraries. When Rhodes creates the final binary for your application, it will search for and link with the library that was generated for your extension. For an extension called `accelerometer`, the build script for iPhone and Android should be name the library `libaccelerometer.a`; for Windows, it should be named `accelerometer.lib`. The build script (in this case, in the Rakefile) should place the library in the folder named by TARGET_TEMP_DIR. **NOTE: You do not need to use a rakefile. You can put your compiling commands in the build or build.bat scripts. The only requirement is that you have a file named build and that it is executable. ## Environment Variables Used in the Build Script Before you build the build script (in this case, a Rakefile), you should be aware of the environmental variables that you can use in the script. These variables give you the path to the Rhodes root and information about your application platform. In the example used here, the Rakefile for the iPhone accelerometer uses several of these variables. The following environment variables are used by all the platforms.
TARGET_TEMP_DIR Location to put your compiled library
RHO_PLATFORM mobile platform for this application build. Possible values are 'iphone', 'android' and 'wm'
RHO_ROOT point to the Rhodes installation root folder (the directory where rhobuild.yml is located)
TEMP_FILES_DIR used for temporary files like object files
The following environment variables are available for Android.
ANDROID_NDK path to the Android NDK used by Rhodes
ANDROID_API_LEVEL Android API level used by Rhodes
The following environment variables are available for Windows Mobile.
VCBUILD path to the vcbuild application in the Microsoft Visual Studio installation
The following environment variables are available for iPhone.
PLATFORM_DEVELOPER_BIN_DIR path to the platform developer bin directory
SDKROOT path to the root of the used SDK - 3.0, 3.1
ARCHS path to the platform developer bin directory
XCODEBUILD contains the full name of the xcodebuild
CONFIGURATION "Debug" or "Release"
SDK_NAME name of SDK - need for build of xcodeproject
The following environment variables are available for Blackberry.
JAVA_EXE java.exe full path
JAVAC_EXE javac.exe full path
JDE_HOME JDE home full path
JAR_EXE jar.exe full path
RUBYVM_JAR full name of RubyVM.jar file (needed for compilation)
BB_SDK_VERSION version of Blackberry SDK
### Creating the Rakefile Script The code listing below is a Rakefile for an iPhone accelerometer native extension. Most of this code you can use for all platforms. Comments are included to tell you where you would substitute code for your specific platform. The sections that you write specifically for your platform are: * Clean file types specific to your platform. * Set platform-specific arguments when compiling the .c files. * Compile code specific for your platform (such as objective C for iphone). * Build the linking command for your platform. For Android build you may use next android build helpers: * setup_ndk(NDK_path, android_API_level) - in order to use cc_compile and cc_ar * cc_compile(filename, objdir, params) - compile source file * cc_ar(libname, objects) - create archive library For other platforms, you can use the Rakefile in the digest extension, which is located in your Rhodes folder: `/lib/extensions/digest/ext/Rakefile`. :::term require 'fileutils' def build_extension(name, arch) objects = [] mkdir_p $tempdir unless File.exists? $tempdir Dir.glob("*.o").each { |f| rm_rf f } # *** IPHONE SPECIFIC: remove any leftover .a files Dir.glob("*.a").each { |f| rm_rf f } rm_rf "accelerometer_wrap.c" #swig Dir.glob("*.i").each do |f| puts "swig -ruby #{f}" puts `swig -ruby #{f}` end # compile the .c files Dir.glob("*.c").each do |f| objname = File.join( $tempdir, File.basename( f.gsub(/\.c$/, '.o') ) ) objects << objname args = [] args << "-I." args << "-I#{$rootdir}/platform/shared/ruby/include" args << "-I#{$rootdir}/platform/shared" if ENV['RHO_PLATFORM'] == 'android' require File.join($rootdir, 'platform/android/build/androidcommon.rb') setup_ndk(ENV['ANDROID_NDK'],ENV['ANDROID_API_LEVEL']) # ... # *** IPHONE SPECIFIC: set arguments (and execute the compile command) elsif ENV['RHO_PLATFORM'] == 'iphone' # get iPhone specific ruby files args << "-I#{$rootdir}/platform/shared/ruby/iphone" # set some flags for Xcode args << "-D_XOPEN_SOURCE" args << "-D_DARWIN_C_SOURCE" # isysroot sets the root sdk of where the gcc compiler will pick up # the libraries, such as the UIKit library args << "-isysroot #{$sdkroot}" args << "-fno-common" # set the architecture and optimization level args << "-arch #{arch}" args << "-O2" # give the object name args << "-o #{objname}" args << "-c" args << f # build up the command cmdline = $gccbin + ' ' + args.join(' ') puts cmdline # and execute the command puts `#{cmdline}` exit unless $? == 0 end end # *** IPHONE SPECIFIC: compile all the .m objective c files if ENV['RHO_PLATFORM'] == 'iphone' # add all the .m files to the list of objects Dir.glob("*.m").each do |f| objname = File.join( $tempdir, File.basename( f.gsub(/\.m$/, '.o') ) ) objects << objname args = [] args << "-x objective-c" # add the specific flags needed to build for iPhone args << "-arch #{arch}" args << "-pipe -std=c99 -Wno-trigraphs -fpascal-strings -O0 -Wreturn-type -Wunused-variable" args << "-isysroot #{$sdkroot}" args << "-D__IPHONE_OS_VERSION_MIN_REQUIRED=30200 " args << "-fvisibility=hidden -mmacosx-version-min=10.6 -gdwarf-2 -fobjc-abi-version=2 -fobjc-legacy-dispatch" args << "-I." args << "-o #{objname}" args << "-c" args << f cmdline = $gccbin + ' ' + args.join(' ') puts cmdline puts `#{cmdline}` exit unless $? == 0 end end mkdir_p $targetdir unless File.exist? $targetdir if ENV['RHO_PLATFORM'] == 'android' # *** IPHONE SPECIFIC: check to see that the linking code exists elsif ENV['RHO_PLATFORM'] == 'iphone' args = [] # build up the command line args << 'rcs' # put the output into targetdir and call it libaccelerometer.a args << File.join( $targetdir, 'lib' + name + '.a' ) # give it list of objects compiles in previous steps args += objects cmdline = $arbin + ' ' + args.join(' ') # execute the linking command puts cmdline puts `#{cmdline}` exit unless $? == 0 elsif ENV['RHO_PLATFORM'] == 'wm' end end namespace "build" do task :config do $targetdir = ENV['TARGET_TEMP_DIR'] raise "TARGET_TEMP_DIR is not set" if $targetdir.nil? $tempdir = ENV['TEMP_FILES_DIR'] raise "TEMP_FILES_DIR is not set" if $tempdir.nil? $rootdir = ENV['RHO_ROOT'] raise "RHO_ROOT is not set" if $rootdir.nil? if ENV['RHO_PLATFORM'] == 'android' elsif ENV['RHO_PLATFORM'] == 'wm' # *** IPHONE SPECIFIC: set the iPhone specific environment variables elsif ENV['RHO_PLATFORM'] == 'iphone' $bindir = ENV['PLATFORM_DEVELOPER_BIN_DIR'] raise "PLATFORM_DEVELOPER_BIN_DIR is not set" if $bindir.nil? $sdkroot = ENV['SDKROOT'] raise "SDKROOT is not set" if $sdkroot.nil? $arch = ENV['ARCHS'] raise "ARCHS is not set" if $arch.nil? $gccbin = $bindir + '/gcc-4.2' $arbin = $bindir + '/ar' end end task :all => :config do build_extension('accelerometer', $arch) end end task :default => "build:all" ### Adding Your Extension to build.yml Edit your application's build.yml to add the extension name to the list of extensions. For example, if your extension is named accelerometer, have this line in your build.yml file: extensions: ["accelerometer"] ### Writing Your Extension Code In the folder named for your extension, such as `accel/extensions/accelerometer`, in the ext folder, create your native extension code. For your iPhone extension, write your header and manifest files in Objectve C. For the accelerometer example, the header file, Accel.h, imports the iOS libraries your extension needs (in the case of the iPhone accelerometer, the UIKit library). And it defines the class to perform the extension's functionality. (The documentation to perform the accelerometer functionality can be found on the iPhone developer portal.) In this case, we have a class named Accel that implements the UIViewController delegate, and the class has one method: start. :::cplusplus #import @interface Accel : UIViewController { } - (void)start; @end The manifest file, Accel.m, contains the Objective C code to perform the accelerometer functionality. Because Rhodes is written in C, and the Ruby implementation is in C, this manifest file must also expose a C interface to the Objective C code. This accelerometer example has the start and get_reading C functions to do that. :::cplusplus #import "accel.h" // store the acceleration values double gx,gy,gz; // Reference this Objective C class. Accel *accel; @implementation Accel -(void)start { // Initialize the accelerometer variables. gx = gy = gz = 0; // Set the update interval. [[UIAccelerometer sharedAccelerometer] setUpdateInterval:0.025]; // Set the delegate to this class, so the iPhone accelerometer will // call you back on the delegate method. [[UIAccelerometer sharedAccelerometer] setDelegate:self]; } -(void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration // Get the accelerometer values from the iPhone. { gx = acceleration.x; gy = acceleration.y; gz = acceleration.z; } @end // This C function interfaces with the Objective C start function. void start(void) { // make sure we can only start this method once static bool started = false; if(!started) { // Initialize the Objective C accelerometer class. accel = [[Accel alloc] init]; // Start the accelerometer readings. [accel start]; started = true; } } // This C function allows us to get the readings from the accelerometer so // they can be returned to the Ruby code. void get_readings(double *x, double *y, double *z) { *x = gx; *y = gy; *z = gz; } ### Writing the Interface File Now we need to have an interface file that SWIG uses to create a Ruby object which will make the functions in the platform-specific code be available to the Ruby virtual machine. As is referenced in the Rakefile, this is done with SWIG. You write an interface (.i) file which has the same name as the extension. This will interface with the C functions in the platform-specific code, and get the proper ruby . Here is the code for the iPhone accelerometer example. :::ruby %module Accelerometer %{ extern void get_readings(double *x, double *y, double *z); extern void start(void); %} extern void start(void); extern void get_readings(double *OUTPUT, double *OUTPUT, double *OUTPUT); Remember that the iPhone .m Objective C file has C functions to get readings from the accelerometer: void get_readings. It also has a start function to initialize the accelerometer class. These are the C function referenced in the interface file. ### Building the Application Build your application as usual. It will call the build script for your extension and, if this script finishes successfully, produce the needed libraries into TARGET_TEMP_DIR and link extension libraries into the final binary. ## Platform Notes ### Using Resources If you want to use any resources in your code, use com.rhomobile.rhodes.R instead of just R. This will make all resources (include your additonal resources) accessible from this R file. ### Calling JNI Functions at Android If you need to call JNI functions from your native code, you need to retrieve the `JNIEnv *env` variable. To get it, include the file `RHO_ROOT/platform/android/Rhodes/jni/include/rhodes.h` in your C/C++ files. The global function `JNIEnv *jnienv()` is defined in this file, so use it anywhere when `JNIEnv *` needed. ### Using Java Code at Android Prepare a text file with list of your java files to build (one file with path per line) and set relative path to the file in ext.yml. android: source_list: ext/rhoconnect-push/platform/android/ext_build.files Also it is possible to prebuild jar library yourself and include it to build. See [Using Prebuilt Libraries (jars)](#using-prebuilt-libraries-jars) ### Using Prebuilt Libraries (jars) If your native extension uses prebuilt libraries (jars), your build script has to copy all such jar files to the TARGET_TEMP_DIR. The jar files must have the extension '.jar'. Rhodes will include these files in the final build. ### Creating Native Threads If your native extension creates a native thread (using pthread_create, for example), this thread should be attached to the JVM so that it can call Java methods from its context. Do this by using rho_nativethread_start/rho_nativethread_end functions, called at the start/end of your thread routine. Example: :::cpp void *thread_routine(void *arg) { void *q = rho_nativethread_start(); ..... rho_nativethread_end(q); return NULL; } Otherwise, if the thread was not attached to the JVM, no JNI calls should be performed in its context (it will cause your application to crash). ### Providing Additional DLLs for Windows Mobile If your application needs additional DLLs, put them in the TARGET_TEMP_DIR. The Rhodes build scripts will detect them and include them in the final binary automatically. ### Registering Your Classes and Methods with Blackberry As with the other platforms, you create a build script (build.bat) Similar to other platfrom you should prepare build.bat where you should prepare .jar file and place it to TARGET_TEMP_DIR In your "ext.yml" file, add a parameter with the full name of your class supported runnable interface. For example, you might use this for a Barcode extension: entry: Init_Barcode javaentry: com.rho.rubyext.BarcodeRecognizer libraries: ["Barcode"] In the run() method of this class, register your classes and methods in RubyVM. Here is an example of registering for a barcode extension. :::java package com.rho.rubyext; import com.xruby.runtime.builtin.ObjectFactory; import com.xruby.runtime.builtin.RubyString; import com.xruby.runtime.lang.*; public class BarcodeRecognizer implements Runnable { public static RubyClass BarcodeClass; public void run() { // register Ruby class BarcodeClass = RubyAPI.defineClass("Barcode", RubyRuntime.ObjectClass); // register Ruby method BarcodeClass.getSingletonClass(). defineMethod("barcode_recognize", this); } protected RubyValue run( RubyValue receiver, RubyValue arg0, RubyBlock block) { //some code for Barcode.barcode_recognize() ruby method } } ## Other Examples You can use teh Barcode extension as an example of native extension for all teh supported platforms. The code for the Barcode native extension is [here on Github](https://github.com/rhomobile/rhodes/tree/master/lib/extensions/barcode/). Another example of the native extension is the Rainbow native extension in the Rhodes System Api Samples. The code for the Rainbow native extension is [here on Github](https://github.com/rhomobile/rhodes-system-api-samples/tree/master/extensions/rainbow/). ## Native View Extensions The Native View interface allows developers to implement a custom native view and seamlessly integrate it into the Rhodes framework. (This is currently only supported on iPhone; Android, WM and Blackberry is coming soon). To access implemented view navigate to a url where url schema is the register type name of your view: view_type_name:path?query_string#anchor Example: :::ruby WebView.navigate("my_video_view:/app/files/barak_obama_0123.mpg") When Rhodes application navigates to a native view it will replace current view (WebView in most cases) with requested native view and pass path?query_string#anchor to created native view. If application navigate to that view again new instance of the view will not be created but the rest of url will be passed to the view. To provide custom native view native extension should implement NativeViewFactory interface and register it with Rhodes framework using `RhoNativeViewManager::registerViewType(const char* viewType, NativeViewFactory* factory)` call (or similar call on BB, see definition below). Rhodes framework will use registered factory to create and display view of given type. ### Native view manager, factory, and view interface definitions on iPhone, Android, Windows Mobile :::cpp class NativeView { public: // that function must return native object provided view functionality : // UIView* for iPhone // jobject for Android - jobject must be android.view.View class type // HWND for Windows Mobile virtual void* getView() = 0; // Used by Rhodes to pass path?query_string#anchor to the view virtual void navigate(const char* url) = 0; }; class NativeViewFactory { public: virtual NativeView* getNativeView(const char* viewType) = 0; virtual void destroyNativeView(NativeView* nativeView) = 0; }; class RhoNativeViewManager { public: static void registerViewType(const char* viewType, NativeViewFactory* factory); static void unregisterViewType(const char* viewType); }; ### Native view manager, factory, and view interface definitions on Blackberry :::java interface NativeView { net.rim.device.api.ui.Field getView(); void navigate(String url); } interface NativeViewFactory { NativeView getNativeView(String viewType); }; class RhoNativeViewManager extends Object{ public: static void registerViewType(String viewType, NativeViewFactory factory); static void unregisterViewType(String viewType); }; ### Sample See [Rhodes-System-Api-Samples](http://github.com/rhomobile/rhodes-system-api-samples) for details of how to implement and use the native view interface. This sample implements a "rainbow_view" native view; you should add rainbow to the list of extensions to include it to the application. See [/app/NativeView/controller.rb](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/app/NativeView/controller.rb) and [/app/NativeView/index.erb](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/app/NativeView/index.erb) for details how to call native view from your controller. ** NOTE: Windows Mobile: Visual Studio 2008 has issues with long paths. If you have problems with building rainbow extension, move your rhodes folder to a shorter path. ** * To navigate to rainbow view in your controller, call `WebView.navigate('rainbow_view:red')`. In your url schema indicates view type you want to open and rest of the url (red) passed to the after it was created. * To pass parameters to created view you may call WebView.navigate again: `WebView.navigate('rainbow_view:green')`. In your native code you may pass parameters to the native view by calling `pNativeView->navigate(url)` where pNativeView is an instance of native view created by the `RhoNativeViewManager` using registered factory. * To close the view you created, navigate to any other url. See [/extensions/rainbow](http://github.com/rhomobile/rhodes-system-api-samples/tree/master/extensions/rainbow/) for implementation of the "rainbow" native view. * See how to register your view type with Rhodes here: [RainbowViewFactoryRegister.cpp](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/extensions/rainbow/ext/rainbow/platform/iphone/Classes/RainbowViewFactoryRegister.cpp) * See implementation of native view factory here: [RainbowViewFactory.mm](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/extensions/rainbow/ext/rainbow/platform/iphone/Classes/RainbowViewFactory.mm) * See sample implementation of native view object here: [RainbowView.h](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/extensions/rainbow/ext/rainbow/platform/iphone/Classes/RainbowView.h) and [RainbowView.m](http://github.com/rhomobile/rhodes-system-api-samples/blob/master/extensions/rainbow/ext/rainbow/platform/iphone/Classes/RainbowView.m). * In the "rainbow" view you can see several buttons: * [Red], [Green], [Blue] buttons change color by calling controller action using rho_net_request(url). Controller in turn execute WebView.navigate("rainbow_view:color") on the same view to change color. * [Stop] and [Play] buttons execute native code inside native view object. * [Close Native View] button return you to the web view by executing rho_webview_navigate(url, tab_index). This sample extension uses functionality provided by Rhodes framework and therefore include few framework header files: * [$(RHO_ROOT)/platform/shared/common/RhoNativeViewManager.h](http://github.com/rhomobile/rhodes/blob/master/platform/shared/common/RhoNativeViewManager.h) * [$(RHO_ROOT)/platform/shared/common/RhodesApp.h](http://github.com/rhomobile/rhodes/blob/master/platform/shared/common/RhodesApp.h) * [$(RHO_ROOT)/platform/shared/rubyext/WebView.h](http://github.com/rhomobile/rhodes/blob/master/platform/shared/rubyext/WebView.h) Make sure the following folders are added to your compiler include path: * [$(RHO_ROOT)/platform/shared/rubyext](http://github.com/rhomobile/rhodes/tree/master/platform/shared/rubyext/) * [$(RHO_ROOT)/platform/shared/ruby/include](http://github.com/rhomobile/rhodes/tree/master/platform/shared/ruby/include/) * [$(RHO_ROOT)/platform/shared](http://github.com/rhomobile/rhodes/tree/master/platform/shared/) * [$(RHO_ROOT)/platform/shared/ruby/iphone](http://github.com/rhomobile/rhodes/tree/master/platform/shared/ruby/iphone/) * [$(RHO_ROOT)/platform/shared/common](http://github.com/rhomobile/rhodes/tree/master/platform/shared/common/) ### url_for_nativeview Examples of how to use the url_for_nativeview method: url_for_nativeview :name => 'rainbow_view', :param => 'red' ==> rainbow_view:red ## Using LineaSDK in Rhodes (with using special Linea native extension) Please see the documentation at [LineaSDK as native extension in Rhodes applications](linea). ## Add registry setting in cab file om Windows Mobile/Windows CE In ext.yml file you should add rgeistry sections regkeys: - key1 - key2 Every key should be created as wrote in MSDN http://msdn.microsoft.com/en-us/library/ms933191.aspx