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.

Using Native Add-ins

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

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.

Locating the Native Add-in's .PRC File

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. 

Entry Function

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.

Startup and Shutdown

Two special function indexes are defined for startup and shutdown of the add-in - NATIVE_STARTUP and NATIVE_SHUTDOWN.

Project Settings

In order for a native add-in to be compatible with OrbC, it must follow certain guidelines.

Additional Resources

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. 

OrbC Declarations

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.

Values

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.

Strings

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.

Function calls

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

Method calls

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

Returning objects

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

Properties

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)

Object/Struct Memory Layout

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

Native Struct IDs

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.

Reference Counting

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.

Creating a Native Struct

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.

Native Gadget IDs

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.

Associated Data

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.

Drawing and Bounds

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.

Retrieving Event Data

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.

Calling Custom Event Handlers

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.

Memory Iteration

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)

Specifier
strFormat
Primitive Type
type
Data Size
size
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);
};

Virtual Machine

Values

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. 

Native Struct Functions

Gadget Functions

System Event Functions

Type String Functions