Warning Older Docs! - You are viewing documentation for a previous released version of RhoMobile Suite.

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, 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:

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:

require 'json'
json_data = ::JSON.generate(some_object)

See JSON tests in Rhodes System API Samples application 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:

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:

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

Barcode

Add to build.yml:

extensions: ["Barcode"]

See details here.

net/http

Add to build.yml:

extensions: ["net-http", "thread", "timeout", "uri"]

hmac

Add to build.yml:

extensions: ["hmac", "digest", "digest-sha1"]

Example:

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

require 'date'
puts Date.today.to_s

For Blackberry Date is still not supported. Use this instead:

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:

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):

$ cd mynewapp
$ mkdir app/lib

Add your ruby files to this directory:

$ 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:

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):

<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:

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.

Encryption libraries

digest – based extensions

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 – 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" ]
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 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.

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.

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>

Understanding the Generated Native Extension Code

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.

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.

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);

Rewriting the Generated Native Extension Source Code for iPhone

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:

  • Rewrite the generated Objective C files in the Classes folder, yourextension.h and yourextension.M, to implement your native extension.
  • Add any new Objective C files that you create to the Xcode project for this native extension.

Rewriting the Native Extension Objective C Header File

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

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.

#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;
}

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.

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.

package com.yourextension;

public class Yourextension {

    public static String processString(String str) {
        return "<Android>" + str + "<Android>";
    }
}

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.

#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);
}

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:

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'

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.

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:

<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>

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.

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.

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.

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

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:

#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);
}

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.

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.

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().

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.

**

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: <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"

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.

#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;
}

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.

%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)

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:

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.

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.

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.

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:

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

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

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 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.

    • 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:

Make sure the following folders are added to your compiler include path:

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.

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
Back to Top