Ok, now the other way around – and its getting more interesting:
We call a Lua program from Smalltalk and then we want to call a Smalltalk based function from that running Lua script. As you may know from other external libraries: a callback from an external code into Smalltalk is only allowed, if the callout from Smalltalk to the external code has been done in a synchronous way. Otherwise the system WILL crash !
And this example only works under Linux (Unix), simply because the Windows version of VASmalltalk only allows “stdcall” calling convention for EsEntryPoints. The Unix based versions only support “cdecl” calling conventions and this is the convention Lua expects. You will notice – if running these examples under Windows – that the system will crash. The Smalltalk function will be called in Windows (you can set a breakproint …), but when returning to Lua is simply crashes.
But now back to the example …
We define a function within Smalltalk as in the example before and leave out the Lua specific code. Just to give you an idea, what the Smalltalk method should return:
functionF: luaPointer | x y | ... some code missing ... ^(x * x * y sin) / (1-x)
and the Lua code looks like …
x = 4.0; y = 2.0; result = 0.0; result = smalltalkFunction(x, y);
The overall idea in this example is: use the defined method, make it fixed, get the EsEntryPoint for that method and somehow let Lua know about the EsEntryPoint address – and then let us see, how this all works together … :-).
As you might see: the (Smalltalk-)function (Lua is calling) takes two numerical parameters: “x” and “y”. We have to check the parameters within the called Smalltalk function. And here is the full source code of the Lua-oriented Smalltalk method:
functionF: pointerToLuaState | x y luaInterface n | "We create a Lua Interpreter with the status delivered with the parameter" luaInterface := MSKLuaLibrary newLua: pointerToLuaState. "number of arguments. Should be 2: x and y" n := luaInterface getTop. "We check the number of arguments and the types of the arguments" ^((n=2) and:[ (luaInterface isNumber:1) and:[ luaInterface isNumber: 2 ]]) ifTrue:[ "retrieve both arguments from stack" x := luaInterface toNumber: 1. y := luaInterface toNumber: 2. "push the result on top of the Lua stack" luaInterface pushNumber: ((x * x * y sin) / (1.0 - x)). "the return value of this method are the number of results on top of the Lua stack " 1 ] ifFalse:[ "we leave the error path out ..." ].
And here you see how Lua aware C functions have to be written. As all Lua functions you might return several results and these are pushed (by the function) on the Lua stack. The return value of the C function are the number of results values pushed on the Lua stack.
Ok, we have the Smalltalk function, but how do we make all this runnable. Well first we define an instance of EsEntryPoint:
createEntryPoint "^ Returns an unbind entry point for the receiver ...." ^EsEntryPoint receiver: self selector: #functionF: callingConvention: 'c' arrayBased: false parameterTypes: #(uint32) returnType: #int32
And now the Smalltalk-code to glue all stuff together:
"we create a new instance of the Lua Interpreter" luaInterpreter := MSKLuaLibrary newLua. "we tell this interpreter about our exported Smalltalk function and how it is named in the Lua environement" luaInterpreter registerEsEntryPoint: MSKLuaTestCase createEntryPoint as: 'smalltalkFunction'. "we execute the Lua code - see above" luaInterpreter doFile: './example03.lua'. "and query the result of global variable 'result', where we expect the result of the called Smalltalk function" result := luaInterpreter getGlobalNamed: 'result' ; toNumber: -1. Transcript cr ; show: 'Result :', result asString
and you get in the Smalltalk Transcript window:
There are some limitations in all this work:
-> callbacks from Lua to Smalltalk only work under Linux (Unix)
-> it only works with synchronous call outs from Smalltalk to Lua
But after all it may give you an idea how powerful this all can be …