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”).
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, 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.
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 as an example.
There are two ways of handling XML directly in Rhodes. The Rexml library and the much faster RhoXML library.
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
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:
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 : <rhodes>\spec\phone_spec\app\spec\xml_spec.rb
(“should stream parse” spec) and rexml stream parser documentation
Add to build.yml:
extensions: ["Barcode"]
See details here.
Add to build.yml:
extensions: ["net-http", "thread", "timeout", "uri"]
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
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.
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’)
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"]
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.
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):
<rhodes-clone>/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.
Once again, it should be mentioned that not all libraries are guaranteed to work with Rhodes. |
digest, digest-sha1, digest-md5
Add to build.yml:
extensions: ["digest", "digest-sha1", "digest-md5"]
digest should be included in extensions list to use digest-base 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" ]
openssl.so is native c-library and should be included in extensions list to use openssl-base libraries |
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).
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 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:
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.
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:
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.
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.
<h3>Yourextension Extension Test</h3> <div> <%= link_to '[Run Yourextension test]', { :action => :run_test }%><br/> </div>
The extensions folder has the following contents.
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). <yourextension> - 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.
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.
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 <yourextension_wrap.c - wrapper code for *.i file generated by SWIG src - folder with native shared native code yourextension.c - C file with main extension initialization function (native entry point) - also execute Ruby wrapper initialization code.
You need to:
Rewrite the Interface file, yourextension.i, by changing the generated native extension function names to your native extension function names.
Regenerate the wrapper file, yourextension_wrap.c, by running the SWIG command on the rewritten Interface file: swig -ruby yourextension.i
.
Here is the yourextension.i file that is generated by the rhogen extension command. You would replace the calc_summ and native_string_process interfaces with interfaces for your native extension functions.
/* yourextension.i */ %module Yourextension %{ #include "ruby/ext/rho/rhoruby.h" extern VALUE yourextension_native_process_string(const char* str); extern int yourextension_calc_summ(int x, int y); #define native_process_string yourextension_native_process_string #define calc_summ yourextension_calc_summ %} extern VALUE native_process_string(const char* str); extern int calc_summ(int x, int y);
Suppose you generated code for a native extension that you named Accelerometer. You would have a file named Accelerometer.i that contains the references to the process_string and calc_summ functions that are generated by rhogen; you would replace these references with the references to your native extension code for Accelerometer. You would start with this code:
/* accelerometer.i */ %module Accelerometer %{ #include "ruby/ext/rho/rhoruby.h" extern VALUE accelerometer_native_process_string(const char* str); extern int accelerometer_calc_summ(int x, int y); #define native_process_string accelerometer_native_process_string #define calc_summ accelerometer_calc_summ %} extern VALUE native_process_string(const char* str); extern int calc_summ(int x, int y);
In this Accelerometer example, the native extensions are a start function and a get_readings function. You would rewrite the accelerometer.i as follows, replacing the generated functions of calc_summ and native_process_string with your start and get_readings functions.
/* accelerometer.i */ %module Accelerometer %{ #include "ruby/ext/rho/rhoruby.h" extern void accelerometer_get_readings(double *x, double *y, double *z); extern void accelerometer_start(void); #define get_readings accelerometer_get_readings #define start accelerometer_start %} extern void get_readings(double *OUTPUT, double *OUTPUT, double *OUTPUT); extern void start(void);
The iPhone generated native extension code is contained in the following structure.
iphone - folder with iPhone native extension code Rakefile - The build script for the iPhone platform. yourextension.xcodeproj - The Xcode project file of iPhone specific code. Classes - a folder with the iPhone native extension source code. yourextension.h - header file yourextension.m - iPhone specific code (implementation of native function declared in Ruby wrapper).
To create native extensions for the iPhone platform:
If you generate an Accelerometer native extension, you get the following header file (accelerometer.h) for the iPhone.
#import <UIKit/UIKit.h> #import <Foundation/Foundation.h>
You could rewrite this header file to look like this for an accelerometer.
#import <UIKit/UIKit.h> @interface Accel : UIViewController<UIAccelerometerDelegate> { } - (void)start; @end
If you generate an Accelerometer native extension, you get the following manifest file (accelerometer.m) for the iPhone.
#import "Accelerometer.h" #include "ruby/ext/rho/rhoruby.h" VALUE accelerometer_native_process_string(const char* str) { NSString* os_string = @"<iOS>"; 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.
#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; }
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.
The Android generated native extension code is contained in the following structure.
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
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.
package com.yourextension; public class Yourextension { public static String processString(String str) { return "<Android>" + str + "<Android>"; } }
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.
#include <rhodes.h> #include "rubyext/WebView.h" #include <stdlib.h> #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); }
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:
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:
src_files << $yourextensiondir + '/ext/yourextension/platform/android/jni/src/yournewfile.cpp'
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.
ext/yourextension/platform/android/src/com/yourextension/Yourextension.java
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:
.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
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
There are two common levels where additional definitions can be injected into AndroidManifest.xml.
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:
<receiver android:name="com.motsolutions.rhomobile.services.ans.test3.ANSBroadcastReceiver" android:permission="com.motsolutions.cto.services.ans.permission.RECEIVE" android:enabled="true"> <!-- Receive actual messages --> <intent-filter> <action android:name="com.motsolutions.cto.services.ans.action.RECEIVE" /> <category android:name='<%=@appPackageName%>' /> </intent-filter> <!-- Receive registration ids --> <intent-filter> <action android:name="com.motsolutions.cto.services.ans.action.REGISTRATION" /> <category android:name='<%=@appPackageName%>' /> </intent-filter> </receiver>
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.
generator.permissions["#{generator.appPackageName}.permission.ANS"] = 'signature' generator.usesPermissions << "#{generator.appPackageName}.permission.ANS" generator.usesPermissions << 'com.motsolutions.cto.services.ans.permission.REGISTER'
The following generator fields may be accessed from erb templates or scripts.
For more details about the values for the generator fields, refer to Android Developer Documentation.
You may also look in your Rhodes installation under /platform/android/Rhodes/AndroidManifest.xml.erb to study how these values are used.
The Windows Mobile generated native extension code is contained in the following structure.
wm - folder with Windows Mobile specific code Rakefile - build script for WM platform <yourextension>.vcproj - Microsoft Visual Studio project file for the Windows Mobile native extension src - folder with WM specific sources <yourextension>_wm.h - header file <yourextension>_wm.cpp - native code with implementation of native function declared in Ruby wrapper
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:
#include <common/RhodesApp.h> #include <logging/RhoLogConf.h> #include <stdlib.h> #include <windows.h> #include <commctrl.h> #include <RhoNativeViewManager.h> #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[] = "<WM>"; 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); }
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.
The Blackberry generated native extension code is contained in the following structure.
bb - folder with Blackberry specific code Rakefile - build script for BB platform <yourextension>.files - list of Java files to be compiled <yourextension>.jdp - Eclipse project (not required for a rake script build) src - folder with BB specific Java sources com <yourextension> <yourextension>.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.
First, rewrite the Public functions, renaming them and changing the arguments for your native extension.
public String doProcessString( String str ) { return "<BB>" + str + "<BB>"; } 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().
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.”
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:
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
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.
**
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. |
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 |
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:
For Android build you may use next android build helpers:
For other platforms, you can use the Rakefile in the digest extension, which is located in your Rhodes folder: <rhodes>/lib/extensions/digest/ext/Rakefile
.
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"
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"]
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.
#import <UIKit/UIKit.h> @interface Accel : UIViewController<UIAccelerometerDelegate> { } - (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.
#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; }
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.
%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.
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.
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.
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.
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)
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.
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:
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).
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.
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
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.
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 } }
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.
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.
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:
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.
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); };
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); };
See 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 and /app/NativeView/index.erb for details how to call native view from your controller.
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 for implementation of the “rainbow” native view.
See how to register your view type with Rhodes here: RainbowViewFactoryRegister.cpp
See implementation of native view factory here: RainbowViewFactory.mm
See sample implementation of native view object here: RainbowView.h and RainbowView.m.
This sample extension uses functionality provided by Rhodes framework and therefore include few framework header files:
Make sure the following folders are added to your compiler include path:
Examples of how to use the url_for_nativeview method:
url_for_nativeview :name => 'rainbow_view', :param => 'red' ==> rainbow_view:red
Please see the documentation at LineaSDK as native extension in Rhodes applications.
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