Hello, so I will try to explain the basics of creating and Android plugin. So basically how this works, is that you call lua methods from your Gideros project, which then underneath call C functions, which through JNI call Java functions.

But don't worry, you don't really need to understand that. Here I will try to provide minimal template needed to create your android plugin, all you will need is a bit understanding of C++ and knowing how to create your needed functionality in Java Android.

What we will create is a simple plugin with two methods:

  1. adding hello in the beginning of passed string
  2. Sum two numbers

All this is very basic functionality that can be use inside lua itself, but just to show how you can create plugin, we will perform all this operations in Java.

So let's start from Lua code. Create new Gideros project (I called mine ExamplePlugin), create main.lua file and add something like this:





--require your plugin
require("exampleplugin")

--use your plugin methods

local text = TextField.new(nil, exampleplugin.modifyString(" world"))
text:setPosition(100, 100);
stage:addChild(text)

local text = TextField.new(nil, exampleplugin.addNumbers(10, 20).."")
text:setPosition(100, 200);
stage:addChild(text)

If you run this project of course it won't work. Just simply because this plugin does not exist.

Hmm, so does it mean, that if you created plugin and used it in your project, you can't run your app on Gideros player anymore?

No, not at all. There are two ways to handle this situation:

  1. You can create plugin for Gideros desktop player (and/or compile your Gideros android/ios player with your plugin to test on device)
  2. Or simply make this code platform specific using application:getDeviceInfo()

 

Here is an example of making your code platform specific, so you will still be available to run your app in Gideros player (Thanks to @Scouser):





if application:getDeviceInfo() == "Android" then 
	--require your plugin
	require("exampleplugin")

	--use your plugin methods

	local text = TextField.new(nil, exampleplugin.modifyString(" world"))
	text:setPosition(100, 100);
	stage:addChild(text)

	local text = TextField.new(nil, exampleplugin.addNumbers(21, 22).."")
	text:setPosition(100, 200);
	stage:addChild(text)
end

Now export this project as Android project. Note that now you are exporting full project, but then there will be a lot of modifications in the android project, so next time you modify Gideros lua code, simply export assets only, so your modified android project won't get overwritten.

I myself have a separate folder "exports" where I store all my Gideros exported projects. And this is really come in handy and you will see why. So basically create exports folder and export your project into that, Gideros will automatically create separate folder for your exported project.

Once that done, go to exported project folder, which in my case is in "ExamplePlugin" folder and copy "libs" folder in to your "exports" folder, which should be just one step up if you did what I recommended. You only need to do that once per new Gideros version and all exported projects will be available to use same "libs" when compiling your plugin. That's why it's so handy.

So ending hierarchy should look like this:


 

Now go to the folder where you have GiderosMobile installed. This might depend on your system, but in Windows it is usually in "C:/Program Files/Gideros". Now navigate to the folder "Sdk" and folder "include" and copy all the contents of this folder into "ExamplePlugin/jni". I recommend copying files from your Gideros installation, simply so you would get the latest files (been burned by that).

Now when thats done, we need to create an Android make file. Here is the example file I will use, you can simply replace "exampleplugin" with your own plugin name. And if you used same hierarchy as I did and copied libs folder to be accessible by all projects, you don't need to modify anything else





LOCAL_PATH := $(call my-dir)

###
 
include $(CLEAR_VARS)
 
LOCAL_MODULE            := lua
LOCAL_SRC_FILES         := ../../libs/$(TARGET_ARCH_ABI)/liblua.so
 
include $(PREBUILT_SHARED_LIBRARY)

###
 
include $(CLEAR_VARS)
 
LOCAL_MODULE            := gvfs
LOCAL_SRC_FILES         := ../../libs/$(TARGET_ARCH_ABI)/libgvfs.so
 
include $(PREBUILT_SHARED_LIBRARY)

#
# Gideros Shared Library
# 
include $(CLEAR_VARS)

LOCAL_MODULE            := gideros
LOCAL_SRC_FILES         := ../../libs/$(TARGET_ARCH_ABI)/libgideros.so

include $(PREBUILT_SHARED_LIBRARY)

#
# Lua Socket Library
# 
include $(CLEAR_VARS)

LOCAL_MODULE            := luasocket
LOCAL_SRC_FILES         := ../../libs/$(TARGET_ARCH_ABI)/libluasocket.so

include $(PREBUILT_SHARED_LIBRARY)

#
# Lua File System Library
# 
include $(CLEAR_VARS)

LOCAL_MODULE            := lfs
LOCAL_SRC_FILES         := ../../libs/$(TARGET_ARCH_ABI)/liblfs.so

include $(PREBUILT_SHARED_LIBRARY)

#
# Plugin
#
include $(CLEAR_VARS)

LOCAL_MODULE           := exampleplugin
LOCAL_ARM_MODE         := arm
LOCAL_CFLAGS           := -O2
LOCAL_SRC_FILES        := exampleplugin.cpp
LOCAL_LDLIBS           := -ldl -llog
LOCAL_SHARED_LIBRARIES := gideros

include $(BUILD_SHARED_LIBRARY)

Name this file Android.mk and save it to the same "jni" folder, where you copied other files.

Now as you see from Anroid.mk file, we will copy all Gideros required libs from "exports/libs" to "exports/ExamplePlugin/libs", because in every plugin compilation "exports/ExamplePlugin/libs" folder will be cleared. The files we are using are libgideros.so which is a Gideros library, libluasocket.so - lua socket library and liblfs.so - lua file system library. If there will be any other new libraries included in Gideros release, add them to this makefile in a same way.

And as you see in the end of Android.mk we will compile plugin from "exampleplugin.cpp". So let's create this file using the template below:





#include "gideros.h"
#include "lua.h"
#include "lauxlib.h"
#define LUA_LIB
#include 
//this is for debugginh purpose
//and should be commented out before deployment
//you can log using
//__android_log_print(ANDROID_LOG_DEBUG, "tag", "Output String");
#include 

//some configurations of our plugin
static const char* pluginName = "exampleplugin";
static const char* pluginVersion = "1.0";
static const char* javaClassName = "com/giderosmobile/android/ExamplePlugin";

//Store Java Environment reference
static JNIEnv *ENV;
//Store our main class, what we will use as plugin
static jclass cls;

//example method
static int example()
{
	__android_log_print(ANDROID_LOG_DEBUG, "ExamplePlugin", "test");
	return 1
}

//here we register all functions we could call from lua
//lua function name as key and C function as value
static const struct luaL_Reg funcs[] = {
  { "example",	example },
  { NULL, NULL }//don't forget nulls at the end
};

//here we register all the C functions for lua
//so lua engine would know they exists
LUALIB_API int luaopen_plugin(lua_State *L)
{
  luaL_register(L, pluginName, funcs);
  return 1;
}

//here we do all our stuff needs to be done on initialization
static void g_initializePlugin(lua_State *L)
{
	//get java environment reference
	ENV = g_getJNIEnv();
	
	//get global package object
	lua_getglobal(L, "package");
	lua_getfield(L, -1, "preload");
	
	//put our plugin name inside with a callback to
	//registering C functions
	lua_pushcfunction(L, luaopen_plugin);
	lua_setfield(L, -2, pluginName);

	lua_pop(L, 2);
}

//and here we free everything we need to free
static void g_deinitializePlugin(lua_State *L)
{

}

//register our plugin with Gideros lib
REGISTER_PLUGIN(pluginName, pluginVersion)

Save this file as "exampleplugin.cpp" or whatever you called it in "Android.mk" file and save it in the same "ExamplePlugin/jni" folder.

You can download both "Android.mk" and plugin template here.

As you see this template only has example method, just to show you how you can create a method that will be called by lua. Now let's compile it, just to see if it compiles OK.

First you need to download AndroidNDK to compile SharedObjects for android.

Of course creating plugin requires a lot of debugging, code editing, testing, etc. So recompiling your plugin each time should be as efficient as possible. That is why I recommend setting up an automatic NDK build in Eclipse, so you don't have to worry about it yourself and plugin would be built each time Eclipse builds a project. I really recommend it, saves a lot of time.

Here is a great tutorial on how to do that. Or you can install NDK plugin same way you installed ADT plugin

But before that, you need to import your project in eclipse

Now let's create Java class, which methods we will call from lua. You can use a Plugin.java as a starting template, which is provided in pluginTemplate.zip





package com.giderosmobile.android;

import android.app.Activity;

public class Plugin{
	//this will be the reference to main activity
	static Activity currentActivity;	
	
	/****************
	 * Here we will register to all
	 * Application life cycle events
	 ****************/
	
	//on create event from Gideros
	//receives reference to current activity
	//just in case if you might need it
	public static void onCreate(Activity activity)
	{
		currentActivity = activity;
	}
	
	//on application start event
	public static void onStart()
	{
			
	}

	//on application resume event
	public static void onResume()
	{
		
	}
	
	//on pause event
	public static void onPause()
	{

	}

	//on stop event
	public static void onStop()
	{
	
	}
	
	//on destroy event
	public static void onDestroy()
	{
		
	}
}

Now let's create the methods which we will want to call from lua. First one was to modify string, by adding "Hello " in front of it. It would look something like that:





//modify string method
public static String modifyString(String before)
{
	String after = "Hello "+before;
	return after;
}

And second method to sum two numbers:





//sum two numbers
public static Integer addNumbers(Integer a, Integer b)
{
	return a+b;
}

Now put your created class inside "src/com/giderosmobile/android" folder.

Next thing we need to add





"System.loadLibrary("exampleplugin");"

just right after :





System.loadLibrary("gideros");
System.loadLibrary("luasocket");
System.loadLibrary("lfs");

And add Plugin class inside externalClasses like that:





static private String[] externalClasses = {
	"com.giderosmobile.android.Plugin"
};

If we want the application life cycle events to be called.

So we have these methods in Java and we need to implement them in JNI, so they could be called from Lua.

So open your cpp file, in my case "exampleplugin.cpp" and let's delete example method and add our two methods. First we need to specify which methods will be called from Lua like this:





//here we register all functions we could call from lua
//lua function name as key and C function as value
static const struct luaL_Reg funcs[] = {
  { "modifyString",	modifyString },
  { "addNumbers",	addNumbers },
  { NULL, NULL }//don't forget nulls at the end
};

Next in the beginning of the files, where we defined some variables we will add two more of them. One for storing reference to Java modifyString method, other one for addNumbers method:





//Store Java Environment reference
static JNIEnv *ENV;
//Store our main class, what we will use as plugin
static jclass cls;
//store modifyString method ID
static jmethodID jModifyString;
//store addNumbers method ID
static jmethodID jAddNumbers;

And now let's create modifyString method to call same method in Java:





//modify string method
static int modifyString(lua_State *L)
{
	__android_log_print(ANDROID_LOG_DEBUG, "ExamplePlugin", "modifyString method called");
	
	//if no Java Env, exit
	if(ENV == NULL) return 0;
	
	//if no class, try to retrieve it
	if(cls == NULL)
	{
		cls = ENV->FindClass(javaClassName);
		if(!cls) return 0;
	}
	
	//if we don't have modifyString  method yet, try to retrieve it
	if(jModifyString == NULL)
	{
		/****************
		* 1. argument cls - reference to Java class, where we have this method
		* 2. argument name of the method to get
		* 3. argument what arguments does method accept and what it returns
		****************/
		jModifyString = ENV->GetStaticMethodID(cls, "modifyString", "(Ljava/lang/String;)Ljava/lang/String;");
		if(!jModifyString) return 0;
	}
	
	//next we get 1 argument passed from lua, which we know is a string
	//reference: http://pgl.yoyo.org/luai/i/lua_tostring
	const char *ourString = lua_tostring(L, 1);
	
	//we pass this string to Java and get back new Java string
	jstring jstr = (jstring)ENV->CallStaticObjectMethod(cls, jModifyString, ENV->NewStringUTF(ourString));
	
	//convert Java String to C++ string
	const char *modifiedString = ENV->GetStringUTFChars(jstr, 0);
	
	//return new string back to lua
	//reference http://pgl.yoyo.org/luai/i/lua_pushlstring
	lua_pushlstring(L, modifiedString, strlen(modifiedString));
	
	return 1;
}

And next one is the addNumbers method:





//add numbers method
static int addNumbers(lua_State *L)
{
	__android_log_print(ANDROID_LOG_DEBUG, "ExamplePlugin", "addNumbers method called");
	
	//if no Java Env, exit
	if(ENV == NULL) return 0;
	
	//if no class, try to retrieve it
	if(cls == NULL)
	{
		cls = ENV->FindClass(javaClassName);
		if(!cls) return 0;
	}
	
	//if we don't have modifyString  method yet, try to retrieve it
	if(jAddNumbers == NULL)
	{
		/****************
		* 1. argument cls - reference to Java class, where we have this method
		* 2. argument name of the method to get
		* 3. argument what arguments does method accept and what it returns
		****************/
		jAddNumbers = ENV->GetStaticMethodID(cls, "addNumbers", "(II)I");
		if(!jAddNumbers) return 0;
	}
	
	//next we get first argument passed from lua, which we know is a number
	//reference: http://pgl.yoyo.org/luai/i/lua_tonumber
	int first = lua_tonumber(L, 1);
	
	//then we get second argument passed from lua, which we also know is a number
	//reference: http://pgl.yoyo.org/luai/i/lua_tonumber
	int second = lua_tonumber(L, 2);
	
	//no we call Java method and give it two numbers and get back Integer
	int result = ENV->CallStaticIntMethod(cls, jAddNumbers, first, second);
	
	//return result back to lua
	//reference http://pgl.yoyo.org/luai/i/lua_pushnumber
	lua_pushnumber(L, result);
	
	return 1;
}

And that's it. Try to run it, and you should have a screen with "Hello world" and number 43

Downloadable files: