Contents > Native Add-ins |
Native Add-ins
Add-in Overview | Overview and usage of native add-ins. | |
Creating Add-ins | Creating native add-ins. | |
VM Architecture | OrbC virtual machine architecture. | |
Native Structs | Implementing native structs. | |
Native Gadgets | Implementing native gadgets. | |
System Events | Intercepting system events. | |
Type Strings | Processing type string. | |
Add-in Reference | OrbC native interface reference. |
Contents > Native Add-ins > Add-in Overview |
Add-in Overview
Native add-ins provide developers with the ability to extend the functionality of OrbC, and to write performance critical functionality in native code. A native add-in is a code resource in the form of a .prc file. When an application that uses a native add-in is built standalone, the add-in is also embedded into the application. This way, a standalone application is completely self-contained, even when using native add-ins. A native add-in can also contain additional resources, such as forms or bitmaps. These will also be embedded in the application.
In order to use the functions and structs implemented in a native library, you must include the add-in's declaration file. To do this, add a line to the beginning of you primary source file such as this:
#include "addin.oc"
This declaration file may be in the project directory, or you must supply a relative path from the current project OR from the compiler root. For example, to include the OrbSort native library located in the compiler's "add-ins" directory:
#include "add-ins/OrbSort.oc"
Using a native gadget is the same as using any other gadget. Simply add the gadget declaration file to the project and create a gadget in your form.
When building a standalone application, the compiler will look for the native add-in's .prc file so that its contents can be embedded in the application. To find this, the compiler searches in this order:
Contents > Native Add-ins > Creating Add-ins |
Creating Add-ins
A native add-in is implemented as a code resource. As a code resource, the add-in only has one exported function (as opposed to a system library, which can have numerous entries). The exported function, the entry function, is called to startup, shutdown, and execute code in the add-in.
This documentation is not a step-by-step tutorial for creating a native add-in. The easiest way to create a native add-in is to copy one of the sample add-ins, modifying it for your needs.
The entry function, which must be named __Startup__ to satisfy the linker, takes 3 parameters:
void* __Startup__(OrbFormsInterface* ofi, void* data, UInt16 iFunc)
The first parameter is a structure embodying the OrbC interface which contains a number of functions to access the virtual machine and other functionality. The second parameter, data, is a pointer to the add-in's global data. The final parameter is the function number that is to be executed.
Two special function indexes are defined for startup and shutdown of the add-in - NATIVE_STARTUP and NATIVE_SHUTDOWN.
In order for a native add-in to be compatible with OrbC, it must follow certain guidelines.
If your native add-in requires additional resources, such as forms or bitmaps, you can add these to the .prc file. When a standalone application is built, these additional resources will be copied over.
In order to use a native function in OrbC, you must declare it as specified below. You should create a .oc file with these declarations and include it in the main source file of your application. If you are creating a native gadget, you should add the .oc file with the declarations to the project as a gadget declaration file.
A native function is declared like a normal function, with additional syntax that specifies the name of the add-in (without the .prc) and the function index that implements it:
int add(int x, int y) @ library("AddInName", 1); string find(string) @ library("AddInName", 2); struct MyStruct { void doSomething(float) @ library("AddInName", 3); };
A native struct can expose properties, which can be readable, writeable, or both. A property is exposed as a function call and has an index associated with the getting and setting of it, as shown below.
struct MyStruct { // 4 is get, 5 is set int read_write_property @ library("AddInName", 4:5); string read_only_property @ library("AddInName", 6); char write_only_property @ library("AddInName", :7); };
Contents > Native Add-ins > VM Architecture |
VM Architecture
The OrbC runtime is built around a stack-based virtual machine. The virtual machine's memory is represented by Values, as defined in OrbNative.h. The following text describes the architectural aspects that are important to a native add-in.
A Value is a union of the supported basic data types - int, float, string, and char. ints, floats, and chars are stored directly in the Value structure. Strings, however, are stored as ref-counted handles.
Other types supported by the language, such as pointers and bools, are treated as ints by the virtual machine.
There are two types of strings supported by the virtual machine: constant and shared. A constant string is just a pointer to some constant data, usually a string literal in C++ code. A shared string is a handle to a ref-counted structure containing the string's data. This structure is not exposed directly to the native add-ins. Instead, functions are provided to create and modify these strings.
In order to implement a function in a native add-in, your code must pop all the arguments off the stack, do something useful, and then (optionally) set the return value. If you declare your function with a return type, you MUST set a return value.
The calling convention for functions is quite simple. Each argument is pushed
onto the stack from left to right, which means that the last argument will be on top of
the stack. For example, an add function declared as "int add(int x, int y)
"
looks like this:
void add(OrbFormsInterface* ofi) { Value* y = ofi->vm_pop(); // pop the last arg Value* x = ofi->vm_pop(); // pop the first arg ofi->vm_retVal->type = vtInt; // set the return type ofi->vm_retVal->iVal = x->iVal + y->iVal; }
Note: If your function takes an object (or structure) as a parameter, this is
converted to a pointer by the compiler. So, a function declared as "void
box(Point top_left, Point bottom_right)
" will be implemented as if it were
"void box(Point* top_left, Point* bottom_right)
".
A method call is the same as function call, but the compiler inserts a
parameter to the beginning of the parameter list which is a pointer to the
object. For example, "void Point.add(int x, int y)
" will be
implemented as "void Point_add(Point* this, int x, int y)"
.
If a function returns an object (or structure), the compiler adds a pointer to the return location as the first parameter. For a method that returns an object (or structure), the "this" pointer gets moved to the second parameter. In addition to filling in the structure passed as the first parameter, a function or method must also set the virtual machine's return value to type point to this same structure.
struct Point { int x, int y; }; Point makePt(int x, int y) @ library("AddInName", 0); void makePt(OrbFormsInterface* ofi) { Value* y = ofi->vm_pop(); Value* x = ofi->vm_pop(); Value* optr = ofi->vm_pop(); // pointer to return struct Value* o_x = ofi->vm_deref(optr->iVal); // dereference the struct pointer to Value* o_y = ofi->vm_deref(optr->iVal + 1); // get pointers to the two fields // fill in the return struct o_x->iVal = x->iVal; o_y->iVal = y->iVal; // return the pointer as well! ofi->vm_retVal->type = vtInt; ofi->vm_retVal->iVal = optr->iVal; }
A property is similar to a field in an object (or structure) - except that it is not actually stored in the object's memory. Instead, the value of the property is get and set by calling a native function. A property may only be a simple type, not an object or structure.
Getting and setting a property is implemented as a method call. A
"get" method takes a pointer to the object and returns the property
value. A "set" method takes a pointer to the object and the new property
value, returning a copy of the new property value. For example, if you implemented Point
as a native
struct with x
as a
read-write property:
struct Point { int x @ library("AddInName", 0:1); };
the methods that get and set the properties would be implemented like this:
int Point_get_x(Point* this) int Point_set_x(Point* this, int new_x)
All the fields in an object and structure are laid out in memory in the same order as they are declared. Methods and properties require no space in the struct. For example:
struct Point { int x; // location 0 int y; // location 1 }; struct NativeStruct { int id; // location 0 void method(); // no space string prop @ library("AddInName", 1:2); // no space int state; // location 1 Point pt; // locations 2 and 3 };
The memory layout for an object is slightly different than a struct. The first memory location in an object is an object type id used by the runtime engine. For example:
object Point { // location 0 is object type id int x; // location 1 int y; // location 2 void setPoint(int x, int y); // no space };
Contents > Native Add-ins > Native Structs |
Native Structs
A native struct can be implemented in many ways. The documentation below explains the model that the OrbC native interface supports through its helper functions. In addition to the text below, it would be informative to read through the sample add-ins. The SampleAddIn (samples\Add-ins\SampleAddIn) implements SampleSerial, a native struct that demonstrates accessing the serial port.
Note: Currently native objects are not supported. What were previously called native objects are actually native structs. All the previous features are still supported, but with the current version of the compiler a struct must be used (because inheritance and virtual methods are not supported).
Each native struct in the application has a unique id. This id is stored in
an internal table which maps it to a native struct pointer. In the OrbC
declaration, the struct defines only one field: this id - all other state and data of the struct are
retrieved and set through properties and methods. To make coding easy, the
OrbC native interface provides a function (nobj_pop
) to pop the
struct pointer from the stack, dereference it to retrieve the id, and map the id to the
native struct pointer.
Native structs are reference counted by the runtime environment. When a
struct is created or copied, its reference count is increased automatically.
When a struct goes out of scope or is deleted, the reference count is
decremented. When the reference count reaches zero, the native struct's delete
function is called. To support this reference counting mechanism, your native
struct must declare a _copy
method and a _destroy
method that call the built in
functions (80 and 89) as specified below:
struct Native { int id; void _init() @ library("AddInName", 0); void _destroy() @ 80; void _copy(Native) @ 89; }
In addition to the reference counting handled by the runtime, you may add explicit references to a struct. A common reason to do this is to ensure that the lifetime of a container struct is greater than the lifetime of a struct it owns. For example, a database owns the database records, so the database struct must live longer than the record. To ensure this, the database record struct adds an explicit reference to the database struct when it is created, and removes the reference when it is destroyed.
Another advantage to using the ref-counted native structs is that the runtime environment will be able to clean up all resources that the application neglected, even if the application is stopping due to an error.
To create a native struct, you must allocate the state needed by the struct and call the supplied OrbC native interface function nobj_create
from your struct's _init
method. This function pops the struct's
pointer from the stack, so you must NOT do this before hand. As a parameter to nobj_create
,
you must specify the native struct's delete function. This function will be
called automatically by the runtime when the struct's reference count reaches 0.
Contents > Native Add-ins > Native Gadgets |
Native Gadgets
Native gadgets are an easy way to create rich, responsive user interfaces. The following documentation explains the rules for creating them, but reading the sample code will be extremely useful. The SampleAddIn (samples\Add-ins\SampleAddIn) implements a native gadget called the TargetGadget. The NativeGadget sample uses this gadget.
Gadgets are implemented as structs in OrbC, so the methods of a gadget have the struct pointer as the first parameter. Similar to native structs, a gadget implemented natively has a first field in OrbC memory which is its id. Unlike a native struct, this id is based on the resource id of the Palm OS control.
Each instance of a native gadget can have native data associated
with it. This data should be allocated in the onopen handler and set using the
OrbC native interface function gadget_set_data
. For other
method calls, this data can be retrieved by popping the struct pointer,
dereferencing it to retrieve the id, and calling gadget_get_data
.
The data should be freed in the onclose handler, and the data pointer should be
set to null.
When drawing in a gadget, the native code must honor the gadget's bounds which are relative to the containing form. When a gadget's handler is called, the active drawing window will be the form containing the gadget. In order to draw in the gadget, you must add it's x and y coordinates to the points you plan to draw (see the sample for an example). Optionally, you may want to set a clipping rectangle to the location and size of the gadget to prevent accidentally drawing on the form.
When the gadget's native handler is called, you may need to retrieve the event data (such as event.x in OrbC code). To do this, call the Event struct's property method (find the index in orbc.sys). See the sample code for an example.
Custom event handlers are stored as code pointers in the gadget struct. The
handler is stored as an int which is 0 if the custom event is not handled by the
instance, or the address of the method if it is handled. If implemented, the
handler should be called by pushing the pointer to the gadget onto the stack
and using vm_call
to call the method.
The location of the event handler's address is determined by the location of
the handler declaration. For example, if the gadget's first member is a UIGadget
(which is must be), and the second member is "handler customEvent
",
then the offset of the handler is 1. For example:
struct NativeGadget { // the UIGadget is required to be the first field. // it contains one member, which is the resource id // of the gadget. UIGadget gadget; // location 0 void someMethod(); // takes no space in the struct handler onCustomEvent; // location 1 int state; // location 2; };
Contents > Native Add-ins > System Events |
System Events
A native add-in can intercept system events by registering an event function
with OrbC using the reg_event_func
function of the OrbC native
interface. To see an example, see the hookcontrast
function implemented in
SampleAddIn.
Event handling in the Palm OS is normally implemented in a loop like this:
do { EvtGetEvent(&event, timeout); if (! SysHandleEvent(&event)) if (! MenuHandleEvent(0, &event, &error)) if (! AppHandleEvent(&event)) FrmDispatchEvent(&event); } while (event.eType != appStopEvent);
This is very similar to the way events are handled in the OrbC runtime environment. When a native add-in registers an event function, it is called before SysHandleEvent. If the add-in's function handles the event, it must return true to prevent the other event functions from processing the event. When more than one native add-in registers an event function, there are no guarantees about which add-in will get the first chance to process the event.
Contents > Native Add-ins > Type Strings |
Type Strings
Many types of native functions and methods have an interest in serializing and de-serializing user data, such as the way many built-in methods do. For example, both DBRecord.write and Preferences.set take a pointer to arbitrary user data and serialize the data according to a user specified type string. To help native functions and methods process these type strings, the OrbC native interface supplies a few helpful functions. See the OrbSerial sample to see these functions in action.
The type string functions operate by iterating over a block of memory where each
Value in the memory block is processed according to the type
string. If the data in the block of memory is not compatible
with the type string, a runtime error will be raised. When using the ts_iterate
function, you provide a NATIVE_TS_ITER
callback function. This
callback function is called once for each Value in memory. Along with a pointer
to the Value in memory, this callback function is also provided with the type
and size as defined in the type string. The following table maps the type string
values to the resulting function parameters:
typedef bool (*NATIVE_TS_ITER)(OrbFormsInterface*, Value* val,
VarType type, int size, void* context)
SpecifierstrFormat |
Primitive Typetype |
Data Sizesize |
Valid Memory Type(s)val->type |
---|---|---|---|
i | int | 4 | vtInt |
w | 2-byte int | 2 | vtInt |
b | 1-byte int | 1 | vtInt or vtChar |
c | char | 1 | vtInt or vtChar |
f | float | 4 | vtFloat |
s | string | 0 | vtString |
l | fixed-length string | len | vtString |
o | object type id | -- | -- |
When an object type id is encountered it is skipped rather than being passed to the native add-in.
Contents > Native Add-ins > Add-in Reference |
Add-in Reference
The OrbC native interface is exposed through a structure of function pointers which is passed to the add-in's entry function. The structure is defined in OrbNative.h (in the add-in directory) as follows:
struct OrbFormsInterface { UInt16 version; // VM interface Value* (*vm_pop)(); void (*vm_push)(Value*); Value* vm_retVal; void (*vm_error)(char*); Value* (*vm_deref)(long ptr); void (*vm_call)(long ptr); void (*vm_callBI)(long index); // Value interface void (*val_release)(Value*); void (*val_acquire)(Value*, Value*); char* (*val_lock)(Value*); void (*val_unlock)(Value*); void (*val_unlockRelease)(Value*); char* (*val_newString)(Value*, int len); char* (*val_newConstString)(Value*, const char*); bool (*val_copyString)(Value*, const char*); // Native struct interface void* (*nobj_pop)(); void* (*nobj_get_data)(long id); long (*nobj_create)(void* obj, NOBJ_DELETE_FUNC delfunc); long (*nobj_addref)(long id); long (*nobj_delref)(long id); // Gadget interface void* (*gadget_get_data)(long id); void (*gadget_set_data)(long id, void*); void (*gadget_get_bounds)(long id, int* pX, int* pY, int* pW, int* pH); // System event interface bool (*event_reg_func)(NATIVE_EVENT_FUNC eventfunc); bool (*event_unreg_func)(NATIVE_EVENT_FUNC eventfunc); // Type string processing long (*ts_iterate)(long addr, char* strFormat, int count, NATIVE_TS_ITER func, void* context); long (*ts_data_size)(long addr, char* strFormat, int count); };
UInt16 version
- the version of the runtime environment that
has loaded the add-in. The top byte is the major version, the bottom byte is
the minor and bug fix version. For example, version 1.1.0 is 0x0110Value* vm_pop()
- retrieves the value from the top of the
stack. If this is value is a string, you must release it when finished. Note: The value returned actually points into the stack memory (instead of a copy). Therefore, any function which writes to the stack will
overwrite it. Also, calling any Palm OS function that could cause an
OrbC handler to run can possibly overwrite the value.void vm_push(Value* val)
- copies the value to the top of the
stack. If this is a string, the ownership is passed to the stack, so you
should not release it.Value* vm_retVal
- the return value register. If your
function is declared to return a value, you must set the type and data of
the value. If you function does not return a value, you must not set it.void vm_error(char* text)
- displays the specified text as an
error message, and immediately terminates the application.Value* vm_deref(long ptr)
- retrieves a pointer to the value
stored at location ptr (either in the OrbC stack or heap). It is not
safe to assume that two contiguous values in OrbC memory are contiguous
in actual memory, so you must call this to retrieve every value. i.e. in:
"Value*
v = ofi->vm_deref(ptr); v++;
" v++ makes is invalid. void vm_call(long ptr)
- call an OrbC function, method,
or gadget custom event handler. This method will modify the stack and modify
vm_retVal
. If the function you call returns a value, you must pop it from
the stack.void vm_callBI(long index)
- call a built in function,
method, or property. index
is the index of the function, which
can be found in the orbc.sys file. This method will modify the stack and modify
vm_retVal
. If the function you call returns a value, you must pop it from
the stack.All of the Value functions operate on string values. If a Value already contains a string, it must be released before assigning any new value. If your function modifies a string, you must make sure that it is a shared string with no other Value referencing it. To do this, first release the string, then allocate a new string to store the modified value.
void val_release(Value* val)
- release the string. If
this is a shared string, the reference count is decremented, and the string
is possibly
freed.void val_acquire(Value* val, Value* src)
- copy the
source string into this Value. If this is a shared string, the reference
count is increased. char* val_lock(Value* val)
- lock the string and
return a pointer to the data. Each call to val_lock
must be
matched by a call to val_unlock
.void val_unlock(Value* val)
- unlock the string.void val_unlockRelease(Value* val)
- unlock and
release the string. This does the same thing as calling val_unlock
followed by val_release
, but is more efficient.char* val_newString(Value* val, int len)
- allocates a
new string of length len
. The length does not include the null
terminator, so val_newString(val, 5)
allocates enough room to
store "apple". Upon return the string is locked, so you must call val_unlock
after you have filled the buffer. Returns null if unable to allocate.char* val_newConstString(Value* val, const char* str)
-
sets the value to point to the given constant string, returning the same
pointer passed in. The pointer must either be a literal string, or must
remain valid until the application exits. Upon return, the value is already
unlocked.bool val_copyString(Value* val, const char* str)
-
Creates a new shared string by copying the supplied string. Upon return the
value is already unlocked. Returns false if unable to allocate.void* nobj_pop()
- pops the pointer to the struct off
the stack, dereferences it to retrieve the id, and returns the associated
native struct pointer.void* nobj_get_data(long id)
- retrieves the
native struct pointer associated with the given id.long nobj_create(void* obj, NOBJ_DELETE_FUNC delfunc)
-
create a new native struct. This should be called from the struct's _init
method. This function pops the struct pointer off the stack (so your
function must not), allocates a new id for it, and stores the id into the
struct's id field. The native struct pointer (obj
) is
associated with this id. When the native struct's reference count goes to 0,
the supplied delete function is called. The struct's assigned id is
returned.long nobj_addref(long id)
- Add an explicit reference
to the struct with the given id, returning the new reference count.long nobj_delref(long id)
- Delete an explicit
reference from the struct with the given id, returning the new reference
count. If the count reaches 0, the struct's delete function will be called
before this function returns.void* gadget_get_data(long id)
- retrieves the data associated with the given gadget id.void gadget_set_data(long id, void*)
- associates the
given data with the gadget id. void gadget_get_bounds(long id, int* pX, int* pY, int* pW, int*
pH)
- retrieves the bounds of the given gadget.bool event_reg_func(NATIVE_EVENT_FUNC eventfunc)
-
registers the given event function to be called on each system event that
occurs. Returns true if the function was able to be registered.bool event_unreg_func(NATIVE_EVENT_FUNC eventfunc)
-
unregisters the given event function. long ts_iterate(long addr, char* strFormat, int count, NATIVE_TS_ITER func, void*
context)
-
iterates over a block of memory starting at address addr
, according to the format specified by strFormat
(repeated count
times). Returns the number of values processed.
For each value in memory, the function func
is called as
described below. The context
parameter is passed to the
callback function. See OrbSerial sample for usage example.typedef bool (*NATIVE_TS_ITER)(OrbFormsInterface*, Value* val, VarType type, int size, void*
context)
- user supplied function called for each value processed
by ts_iterate
. For each value, a pointer to the Value is
provided, along with the type and size specified by the format string. See Native
Type Strings for a table of possible parameter values. Return true to
continue the iteration, or false to force it to end prematurely. long ts_data_size(long addr, char* strFormat, int count)
-
calculates the total size required to serialize a the block of memory
starting at address addr
, based on the format string strFormat
(repeated count
times)