-
Notifications
You must be signed in to change notification settings - Fork 40
Native methods
TotalCross has a certain syntax for native methods to be written
The Java method can be either marked as native or have a Java implementation and the annotation
@ReplacedByNativeOnDeploy
static String getMyString() {
...
the annotation exists so we can provide a java implementation when running on the Launcher
, using the JDK itself, and a native implementation when running on the device, without resorting to template methods, polymorphism or whatever.
The syntax of the C function that is mapped to the java method is the following:
<first-letter-of-each-package-in-lowercase><camel-case-characters-of-class>_<method-name>_<first-letter-of-each-type-argument>(NMParams p)
for example:
package totalcross.ui.image;
...
public class Image {
...
@ReplacedByNativeOnDeploy
final public void applyColor(int color) {
...
becomes:
tuiI_applyColor_i(NMParams p);
There are some details about method arguments:
Type | Variable notation | Array notation |
---|---|---|
int | i | I |
long | l | L |
char | c | C |
double | d | D |
boolean | z | Z |
byte | b | B |
String | s | S |
Object | o | O |
Therefore:
- Upper case for arrays;
- Functions without arguments doesn't need the last
_
term; - TotalCross don't support native float, but you can workaround with double;
- Methods with
f
liketsC_getBreakPos_fsiib
:are just object references;getBreakPos(FontMetrics fm, StringBuffer sb, int start, int width, boolean doWordWrap)
- The class name is formed by camel case, so
NotificationManager
becomesNM
,NativeDB
becomesNDB
, etc.
Look:
TC_API void tdsNDB_shared_cache_b(NMParams p) // totalcross/db/sqlite/NativeDB native int shared_cache(boolean enable);
{
TRACE("tdsNDB_shared_cache_b")
bool enable = p->i32[0];
LOCKDB
p->retI = sqlite3_enable_shared_cache(enable);
UNLOCKDB
}
In this section we can see the access to the values in p
. They are accessed as an array, if there was one more integer in return it could be obtained as follows:
int oneMore = p->i32[1];
To return an integer to Java use p->retI = something
you can change the last letter following the convention to return other types.
Those functions are all defined at compile time and depending on the platform, they are loaded using dlsym or using a C hashtable that contains a pointer for each function mapped to their name. Since they all share the same signature, it's always the same function call:
void tsC_getBreakPos_fsiib(NMParams p)
That's how we do it without ASM: NMParams
is used to pass the actual arguments and store the return value.
Ok, but how is the actual method selected from the hash table?
If you look at tcvm.c, around line 636, there's a label nativeMethodCall
, when this flag is set:
if (method->flags.isNative)
The code jumps to this label and finds the correct function using newMethod->nativeSignature
depending on the platform, the function findProcAddress
is either a dlsym
or a hashtable.get
using this, it later calls newMethod->boundNM(nmp)
.
You need:
- Create your Java interface;
- Create your native implementation in a .c or .cpp file and pay attention to include
tcvm.h
; - Add your native file to the build list:
totalcross/TotalCrossVM/CMakeLists.txt
; - Add your interfaces to the
TotalCrossVM/src/nm/NativeMethods.txt
file:totalcross/sys/Vm|native public static void alert(String s); <path-to-your-java-file>|native <your function>;
Then just generate the new files, both VM and Java SDK (API).
Sometimes you have to add your native interfaces to the
TotalCrossVM/src/nm/NativeMethodsPrototypes.txt
file too!