Rhomobile | Extending the Rhodes Framework

butterbeanspipeΛογισμικό & κατασκευή λογ/κού

14 Ιουλ 2012 (πριν από 4 χρόνια και 9 μήνες)

545 εμφανίσεις

Docs Home
Discussions
Blogs
Google Group
Search
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.
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
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 for your extension, such as
accel/extensions/accelerometer
, create a
file named ext.yml.
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
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, add the following parameters to your ext.yml file.
android_rhodes_activity_listener – Java class name implemented interface
com.rhomobile.rhodes.RhodesActivityListener (used for attach to main Rhodes application
activity)
android_manifest_changes – filename with Android manifest items (in format of Android
manifest) 0 this file will join to main AndroidManifest.xml of application
android_resources_addons – folder with any additional files for add to main application
build folder (can contain any resources, layouts, etc.)
android_additional_sources_list – filename with list of *.java files for building with main
Rhodes application package.
android_rhodes_activity_listener
Java class name implemented interface
com.rhomobile.rhodes.RhodesActivityListener (used for
attach to main Rhodes application activity)
android_manifest_changes
filename with Android manifest items (in format of
Android manifest) 0 this file will join to main
AndroidManifest.xml of application
android_resources_addons
folder with any additional files for add to main application
build folder (can contain any resources, layouts, etc.)
android_additional_sources_list
filename with list of *.java files for building 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
android_rhodes_activity_listener: com.rhomobile.nfc.Nfc
android_manifest_changes: ext/nfc/platform/android/AndroidManifest.xml
android_resources_addons: ext/nfc/platform/android/additional_files
android_additional_sources_list: ext/nfc/platform/android/ext_build.files
libraries: ["Nfc"]
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.)
When running on Linux and Macintosh, make sure the build files are executable.
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 fot 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 temporay 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 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'
# *** 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
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 in Native Extension
If your native extension uses Java code, your build script should create file ext_build.files in the
TARGET_TEMP_DIR. This file should contain java source file names (full names, with path) – one
by line. Rhodes will include all of them to the final build.
**
We recommend you use new android_additional_sources_list parameter from ext.yml
config – just prepare file with list of your java files for build and set up that parameter.
Using Prebuilt Libraries (jars)
If your native extension uses prebuilt libraries (jars), your build script must 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:
$(RHO_ROOT)/platform/shared/common/RhoNativeViewManager.h
$(RHO_ROOT)/platform/shared/common/RhodesApp.h
$(RHO_ROOT)/platform/shared/rubyext/WebView.h
Make sure the following folders are added to your compiler include path:
$(RHO_ROOT)/platform/shared/rubyext
$(RHO_ROOT)/platform/shared/ruby/include
$(RHO_ROOT)/platform/shared
$(RHO_ROOT)/platform/shared/ruby/iphone
$(RHO_ROOT)/platform/shared/common
url_for_nativeview
Examples of how to use the url_for_nativeview method:
url_for_nativeview :name => 'rainbow_view', :param => 'red'
==> rainbow_view:red
Using LineaSDK in Rhodes (with using special Linea
native extension)
Please see the documentation at
LineaSDK as native extension in Rhodes applications
.
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