📜 ⬆️ ⬇️

Calling Lua scripts from java and vice versa

image
I recently encountered such a problem and was extremely surprised that there are very few materials on the network, given the popularity of Lua. As it turned out, there are quite a lot of libraries for working with Lua scripts from java, but they all have their own nuances. Best of all, as it turned out, use the LuaJava library from the same developers that wrote Lua.

Build LuaJava Library

At once I will say that it is better to unpack the archive with the input files and perform the assembly in a special folder, from where you will connect the assembled jar-file to the project. The path to this folder will also need to be registered in ClassPath. If you compile the jar-archive, and then copy it into the folder with the java-project, the program will not start just like that. But this will be lower.
The manual’s page says that happy Linux and OSX users can build a library from source with a simple make command, making minor changes to the config file, config. Initially this file looks like this.
#############################################################
#Linux/BSD/Mac
LUA_DIR= /usr/local/share/lua/5.1.1
LUA_LIBDIR= /usr/local/lib
LUA_INCLUDES= /usr/local/include
JDK= $(JAVA_HOME)
# For Mac OS, comment the above line and uncomment this one
#JDK=/Library/Java/Home

# Full path to Lua static library
LIB_LUA=$(LUA_LIBDIR)/liblua.a

#Linux/BSD
LIB_EXT= .so
#Mac OS
#LIB_EXT= .jnilib

LIB_PREFIX= lib

#Linux/BSD
LIB_OPTION= -shared
#Mac OS
#LIB_OPTION= -dynamiclib -all_load

## On FreeBSD and Mac OS systems, the following line should be commented
DLLIB= -ldl

WARN= -O2 -Wall -fPIC -W -Waggregate-return -Wcast-align -Wmissing-prototypes -Wnested-externs -Wshadow -Wwrite-strings
INCS= -I$(JDK)/include -I$(JDK)/include/linux -I$(LUA_INCLUDES)
CFLAGS= $(WARN) $(INCS)

CC= gcc

#########################################################
VERSION= 1.1
PKG= luajava-$(VERSION)
TAR_FILE= $(PKG).tar.gz
ZIP_FILE= $(PKG).zip
JAR_FILE= $(PKG).jar
SO_FILE= $(LIB_PREFIX)$(PKG)$(LIB_EXT)
DIST_DIR= $(PKG)

# $Id: config,v 1.12 2006/12/22 14:06:40 thiago Exp $

If you immediately try to do make, then nothing good will happen. Here is what you need to do to successfully assemble the library:
1. Either create the JAVA_HOME environment variable, or set it in the configuration file, specifying the path to the folder where java is installed. I added this variable the first line in the configuration file:
JAVA_HOME= /usr/lib/jvm/java-6-sun-1.6.0.26

2. Download Lua binary files from here . We need the Linux archive ### _ lib.tar.gz. After unpacking the archive, the files liblua5.1a and liblua5.1.so need to be copied to the / usr / local / lib folder, and the files from the include folder to the / usr / local / include folder. After that, in the configuration file, replace the line
LIB_LUA=$(LUA_LIBDIR)/liblua.a
on
LIB_LUA=$(LUA_LIBDIR)/liblua5.1.a

3. Gcc must also be installed.

Everything, now it is possible to collect library. After the make command, 2 files appear: luajava-1.1.jar and libluajava-1.1.so. The manual says that .jar needs to be placed in the lib / folder with your project, and the .so file is either in the JRE bin / folder or in the folder where the variable LD_LIBRARY_PATH points to. I put this file in the bin / jre folder.

Java project configuration

In the java project, you need to connect the external library luajava-1.1.jar. Also, running a little ahead, you need to add a string in the VM arguments
-Djava.library.path=[ , LuaJava]

Now you can safely write Lua-scripts and access them from the program. This is done using the org.keplerproject.luajava.LuaState class. I came across an example in which a special class was written to call the Lua functions. Here is an example of a class that I use:
')
 import org.keplerproject.luajava.LuaObject; import org.keplerproject.luajava.LuaState; import org.keplerproject.luajava.LuaStateFactory; public class LuaScriptLoader { private LuaState luaState; public LuaScriptLoader(String fileFullName) { luaState = LuaStateFactory.newLuaState(); luaState.openLibs(); luaState.LdoFile(fileFullName); } public void closeScript() { luaState.close(); } /** * ,  Lua- getRoomDescription,     * * @return    */ public String getRoomDescription() { luaState.getGlobal("getRoomDescription"); luaState.call(0, 1); LuaObject lo = luaState.getLuaObject(1); luaState.pop(1); return lo.getString(); } /** * ,     Lua- * * @param functionName -   * @param adapter -  LuaAdapter,     */ public void runScriptFunction(String functionName, LuaAdapter adapter) { luaState.getGlobal(functionName); luaState.pushJavaObject(adapter); luaState.call(1, 0); } } 


The program will run from the programming environment. If you want to launch a .jar file with your program, you need to do 2 things:
1. When exporting, use a manifest file in which the Class-Path attribute should be specified
Class-Path: [ , LuaJava]/luajava-1.1.jar

2. Run the .jar file with the same key that is specified in the VM arguments of the environment
-Djava.library.path=[ , LuaJava]

When the call method of class LuaState is accessed, 2 integer parameters are passed. This is the number of arguments and the number of return values. In the getRoomDescription () method you can see how to get the result from the Lua function (0 arguments, 1 return value), and in the runScriptFunction () method, how to pass java-objects to the script (1 argument, 0 return values). The LuaAdapter class is my class that has several public methods. These methods can be called from a script. Here is an example of a file with a script:

 function getRoomDescription() return "" end function processMessage(adapter) local message = adapter:getMessage() local user = adapter:getUser() local nick = user:getNick() adapter:sendMessageToRoomAndUser(user, nick..": "..message, "") end function userEnter(adapter) local user = adapter:getUser() local nick = user:getNick() adapter:sendMessageToRoomAndUser(user, "  "..nick, "   ") end function userLeft(adapter) local nick = adapter:getUser():getNick() adapter:sendMessageToRoom(" "..nick.."   ") end 


In this example, a conference room is scripted using Lua, where users can communicate. All public methods that are in the LuaAdapter class are successfully called and executed.

UPD: forgot one more thing. You can work with java-classes and objects from Lua not only as I described above (passing the object as a parameter to the Lua-function). In the manual LuaJava described several more ways. I worked with the first two, they worked without any problems, I did not try the others as superfluous. I will describe the first two ways:
1. Call the newInstance (className, ...) function of the LuaJava library. The first parameter of the function is the full name of the java-class, the rest are the parameters of the constructor. The result of calling this function will be a new java-object with which you can work.
Example:

 obj = luajava.newInstance("java.lang.Object") 


You can also call your classes by passing their names as parameters (for example, “myproject.mypackage.MyClass”). But here there are nuances that I have not met:


2. Call the bindClass (className) function of the LuaJava library. This function will return an object for which it will be possible to access the static fields and methods of the java-class whose name has been specified.
Example:

 sys = luajava.bindClass("java.lang.System") print ( sys:currentTimeMillis() ) 


You can also call your classes (with the same nuances).
I called these Lua-functions only from one project, and in functions I addressed classes only of this project, therefore I did not encounter any problems

UPD Found a nuance. The call method does not know how to catch errors that may be present in the script. If there is an error in the script, the whole program crashes. Iksepshenov this method does not throw. The only solution that nagulit - is to try to run a script call from another thread. But it is very ugly. But the LuaState class has a pcall method:

 public int pcall(int nArgs, int nResults, int errFunc) 


which in the case of a successful call will return the value 0, and a non-zero value as a result of an unsuccessful call. The first two arguments repeat the arguments of the call method, the last parameter apparently should point to the error handling function. In this case, the error description will be saved in the LuaState object with which the script function was called. Now the runScriptFunction method looks like this for me:

  public void runScriptFunction(String functionName, LuaAdapter adapter) { luaState.getGlobal(functionName); luaState.pushJavaObject(adapter); int res = luaState.pcall(1, 0, 0); if(res != 0) { Main.log(Level.SEVERE, "LuaScriptLoader call error: " + luaState.toString(-1)); } } } 

Source: https://habr.com/ru/post/126105/


All Articles