/* * This file is part of LuaInterface. * * Copyright (C) 2003-2005 Fabio Mascarenhas de Queiroz. * Copyright (C) 2012 Megax * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ using System; using System.IO; using System.Reflection; using System.Diagnostics; using System.Collections; using System.Collections.Generic; using LuaInterface.Exceptions; namespace LuaInterface { /* * Passes objects from the CLR to Lua and vice-versa * * Author: Fabio Mascarenhas * Version: 1.0 */ public class ObjectTranslator { internal CheckType typeChecker; // object # to object (FIXME - it should be possible to get object address as an object #) public readonly Dictionary objects = new Dictionary(); // object to object # public readonly Dictionary objectsBackMap = new Dictionary(); internal Lua interpreter; private MetaFunctions metaFunctions; private List assemblies; private KopiLua.Lua.lua_CFunction registerTableFunction,unregisterTableFunction,getMethodSigFunction, getConstructorSigFunction,importTypeFunction,loadAssemblyFunction; internal EventHandlerContainer pendingEvents = new EventHandlerContainer(); public ObjectTranslator(Lua interpreter,KopiLua.Lua.lua_State luaState) { this.interpreter=interpreter; typeChecker=new CheckType(this); metaFunctions=new MetaFunctions(this); assemblies=new List(); importTypeFunction=new KopiLua.Lua.lua_CFunction(this.importType); loadAssemblyFunction=new KopiLua.Lua.lua_CFunction(this.loadAssembly); registerTableFunction=new KopiLua.Lua.lua_CFunction(this.registerTable); unregisterTableFunction=new KopiLua.Lua.lua_CFunction(this.unregisterTable); getMethodSigFunction=new KopiLua.Lua.lua_CFunction(this.getMethodSignature); getConstructorSigFunction=new KopiLua.Lua.lua_CFunction(this.getConstructorSignature); createLuaObjectList(luaState); createIndexingMetaFunction(luaState); createBaseClassMetatable(luaState); createClassMetatable(luaState); createFunctionMetatable(luaState); setGlobalFunctions(luaState); } /* * Sets up the list of objects in the Lua side */ private void createLuaObjectList(KopiLua.Lua.lua_State luaState) { KopiLua.Lua.lua_pushstring(luaState,"luaNet_objects"); KopiLua.Lua.lua_newtable(luaState); KopiLua.Lua.lua_newtable(luaState); KopiLua.Lua.lua_pushstring(luaState,"__mode"); KopiLua.Lua.lua_pushstring(luaState,"v"); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_setmetatable(luaState,-2); KopiLua.Lua.lua_settable(luaState, (int) PseudoIndex.Registry); } /* * Registers the indexing function of CLR objects * passed to Lua */ private void createIndexingMetaFunction(KopiLua.Lua.lua_State luaState) { KopiLua.Lua.lua_pushstring(luaState,"luaNet_indexfunction"); LuaLib.luaL_dostring(luaState,MetaFunctions.luaIndexFunction); // steffenj: lua_dostring renamed to luaL_dostring //LuaLib.lua_pushstdcallcfunction(luaState,indexFunction); KopiLua.Lua.lua_rawset(luaState, (int) PseudoIndex.Registry); } /* * Creates the metatable for superclasses (the base * field of registered tables) */ private void createBaseClassMetatable(KopiLua.Lua.lua_State luaState) { KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_searchbase"); KopiLua.Lua.lua_pushstring(luaState,"__gc"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__tostring"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__index"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.baseIndexFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__newindex"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.newindexFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_settop(luaState,-2); } /* * Creates the metatable for type references */ private void createClassMetatable(KopiLua.Lua.lua_State luaState) { KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_class"); KopiLua.Lua.lua_pushstring(luaState,"__gc"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__tostring"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__index"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.classIndexFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__newindex"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.classNewindexFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__call"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.callConstructorFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_settop(luaState,-2); } /* * Registers the global functions used by LuaInterface */ private void setGlobalFunctions(KopiLua.Lua.lua_State luaState) { LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.indexFunction); KopiLua.Lua.lua_setglobal(luaState,"get_object_member"); LuaLib.lua_pushstdcallcfunction(luaState,importTypeFunction); KopiLua.Lua.lua_setglobal(luaState,"import_type"); LuaLib.lua_pushstdcallcfunction(luaState,loadAssemblyFunction); KopiLua.Lua.lua_setglobal(luaState,"load_assembly"); LuaLib.lua_pushstdcallcfunction(luaState,registerTableFunction); KopiLua.Lua.lua_setglobal(luaState,"make_object"); LuaLib.lua_pushstdcallcfunction(luaState,unregisterTableFunction); KopiLua.Lua.lua_setglobal(luaState,"free_object"); LuaLib.lua_pushstdcallcfunction(luaState,getMethodSigFunction); KopiLua.Lua.lua_setglobal(luaState,"get_method_bysig"); LuaLib.lua_pushstdcallcfunction(luaState,getConstructorSigFunction); KopiLua.Lua.lua_setglobal(luaState,"get_constructor_bysig"); } /* * Creates the metatable for delegates */ private void createFunctionMetatable(KopiLua.Lua.lua_State luaState) { KopiLua.Lua.luaL_newmetatable(luaState,"luaNet_function"); KopiLua.Lua.lua_pushstring(luaState,"__gc"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_pushstring(luaState,"__call"); LuaLib.lua_pushstdcallcfunction(luaState,metaFunctions.execDelegateFunction); KopiLua.Lua.lua_settable(luaState,-3); KopiLua.Lua.lua_settop(luaState,-2); } /* * Passes errors (argument e) to the Lua interpreter */ internal void throwError(KopiLua.Lua.lua_State luaState, object e) { // We use this to remove anything pushed by luaL_where int oldTop = KopiLua.Lua.lua_gettop(luaState); // Stack frame #1 is our C# wrapper, so not very interesting to the user // Stack frame #2 must be the lua code that called us, so that's what we want to use KopiLua.Lua.luaL_where(luaState, 1); object[] curlev = popValues(luaState, oldTop); // Determine the position in the script where the exception was triggered string errLocation = ""; if (curlev.Length > 0) errLocation = curlev[0].ToString(); string message = e as string; if (message != null) { // Wrap Lua error (just a string) and store the error location e = new LuaScriptException(message, errLocation); } else { Exception ex = e as Exception; if (ex != null) { // Wrap generic .NET exception as an InnerException and store the error location e = new LuaScriptException(ex, errLocation); } } push(luaState, e); KopiLua.Lua.lua_error(luaState); } /* * Implementation of load_assembly. Throws an error * if the assembly is not found. */ private int loadAssembly(KopiLua.Lua.lua_State luaState) { try { string assemblyName=KopiLua.Lua.lua_tostring(luaState,1).ToString(); Assembly assembly = null; try { assembly = Assembly.Load(assemblyName); } catch (BadImageFormatException) { // The assemblyName was invalid. It is most likely a path. } if (assembly == null) { assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName)); } if (assembly != null && !assemblies.Contains(assembly)) { assemblies.Add(assembly); } } catch(Exception e) { throwError(luaState,e); } return 0; } internal Type FindType(string className) { foreach(Assembly assembly in assemblies) { Type klass=assembly.GetType(className); if(klass!=null) { return klass; } } return null; } /* * Implementation of import_type. Returns nil if the * type is not found. */ private int importType(KopiLua.Lua.lua_State luaState) { string className=KopiLua.Lua.lua_tostring(luaState,1).ToString(); Type klass=FindType(className); if(klass!=null) pushType(luaState,klass); else KopiLua.Lua.lua_pushnil(luaState); return 1; } /* * Implementation of make_object. Registers a table (first * argument in the stack) as an object subclassing the * type passed as second argument in the stack. */ private int registerTable(KopiLua.Lua.lua_State luaState) { if(KopiLua.Lua.lua_type(luaState,1).ToLuaTypes()==LuaTypes.Table) { LuaTable luaTable=getTable(luaState,1); string superclassName = KopiLua.Lua.lua_tostring(luaState, 2).ToString(); if (superclassName != null) { Type klass = FindType(superclassName); if (klass != null) { // Creates and pushes the object in the stack, setting // it as the metatable of the first argument object obj = CodeGeneration.Instance.GetClassInstance(klass, luaTable); pushObject(luaState, obj, "luaNet_metatable"); KopiLua.Lua.lua_newtable(luaState); KopiLua.Lua.lua_pushstring(luaState, "__index"); KopiLua.Lua.lua_pushvalue(luaState, -3); KopiLua.Lua.lua_settable(luaState, -3); KopiLua.Lua.lua_pushstring(luaState, "__newindex"); KopiLua.Lua.lua_pushvalue(luaState, -3); KopiLua.Lua.lua_settable(luaState, -3); KopiLua.Lua.lua_setmetatable(luaState, 1); // Pushes the object again, this time as the base field // of the table and with the luaNet_searchbase metatable KopiLua.Lua.lua_pushstring(luaState, "base"); int index = addObject(obj); pushNewObject(luaState, obj, index, "luaNet_searchbase"); KopiLua.Lua.lua_rawset(luaState, 1); } else throwError(luaState, "register_table: can not find superclass '" + superclassName + "'"); } else throwError(luaState, "register_table: superclass name can not be null"); } else throwError(luaState,"register_table: first arg is not a table"); return 0; } /* * Implementation of free_object. Clears the metatable and the * base field, freeing the created object for garbage-collection */ private int unregisterTable(KopiLua.Lua.lua_State luaState) { try { if(KopiLua.Lua.lua_getmetatable(luaState,1)!=0) { KopiLua.Lua.lua_pushstring(luaState,"__index"); KopiLua.Lua.lua_gettable(luaState,-2); object obj=getRawNetObject(luaState,-1); if(obj==null) throwError(luaState,"unregister_table: arg is not valid table"); FieldInfo luaTableField=obj.GetType().GetField("__luaInterface_luaTable"); if(luaTableField==null) throwError(luaState,"unregister_table: arg is not valid table"); luaTableField.SetValue(obj,null); KopiLua.Lua.lua_pushnil(luaState); KopiLua.Lua.lua_setmetatable(luaState,1); KopiLua.Lua.lua_pushstring(luaState,"base"); KopiLua.Lua.lua_pushnil(luaState); KopiLua.Lua.lua_settable(luaState,1); } else throwError(luaState,"unregister_table: arg is not valid table"); } catch(Exception e) { throwError(luaState,e.Message); } return 0; } /* * Implementation of get_method_bysig. Returns nil * if no matching method is not found. */ private int getMethodSignature(KopiLua.Lua.lua_State luaState) { IReflect klass; object target; int udata=LuaLib.luanet_checkudata(luaState,1,"luaNet_class"); if(udata!=-1) { klass=(IReflect)objects[udata]; target=null; } else { target=getRawNetObject(luaState,1); if(target==null) { throwError(luaState,"get_method_bysig: first arg is not type or object reference"); KopiLua.Lua.lua_pushnil(luaState); return 1; } klass=target.GetType(); } string methodName=KopiLua.Lua.lua_tostring(luaState,2).ToString(); Type[] signature=new Type[KopiLua.Lua.lua_gettop(luaState)-2]; for(int i=0;i /// Given the Lua int ID for an object remove it from our maps /// /// internal void collectObject(int udata) { object o; bool found = objects.TryGetValue(udata, out o); // The other variant of collectObject might have gotten here first, in that case we will silently ignore the missing entry if (found) { // Debug.WriteLine("Removing " + o.ToString() + " @ " + udata); objects.Remove(udata); objectsBackMap.Remove(o); } } /// /// Given an object reference, remove it from our maps /// /// void collectObject(object o, int udata) { // Debug.WriteLine("Removing " + o.ToString() + " @ " + udata); objects.Remove(udata); objectsBackMap.Remove(o); } /// /// We want to ensure that objects always have a unique ID /// int nextObj = 0; int addObject(object obj) { // New object: inserts it in the list int index = nextObj++; // Debug.WriteLine("Adding " + obj.ToString() + " @ " + index); objects[index] = obj; objectsBackMap[obj] = index; return index; } /* * Gets an object from the Lua stack according to its Lua type. */ internal object getObject(KopiLua.Lua.lua_State luaState,int index) { LuaTypes type=KopiLua.Lua.lua_type(luaState,index).ToLuaTypes(); switch(type) { case LuaTypes.Number: { return KopiLua.Lua.lua_tonumber(luaState,index); } case LuaTypes.String: { return KopiLua.Lua.lua_tostring(luaState,index); } case LuaTypes.Boolean: { return KopiLua.Lua.lua_toboolean(luaState,index); } case LuaTypes.Table: { return getTable(luaState,index); } case LuaTypes.Function: { return getFunction(luaState,index); } case LuaTypes.UserData: { int udata=LuaLib.luanet_tonetobject(luaState,index); Console.WriteLine(udata); if(udata!=-1) return objects[udata]; else return getUserData(luaState,index); } default: return null; } } /* * Gets the table in the index positon of the Lua stack. */ internal LuaTable getTable(KopiLua.Lua.lua_State luaState,int index) { KopiLua.Lua.lua_pushvalue(luaState,index); return new LuaTable(LuaLib.lua_ref(luaState,1),interpreter); } /* * Gets the userdata in the index positon of the Lua stack. */ internal LuaUserData getUserData(KopiLua.Lua.lua_State luaState,int index) { KopiLua.Lua.lua_pushvalue(luaState,index); return new LuaUserData(LuaLib.lua_ref(luaState,1),interpreter); } /* * Gets the function in the index positon of the Lua stack. */ internal LuaFunction getFunction(KopiLua.Lua.lua_State luaState,int index) { KopiLua.Lua.lua_pushvalue(luaState,index); return new LuaFunction(LuaLib.lua_ref(luaState,1),interpreter); } /* * Gets the CLR object in the index positon of the Lua stack. Returns * delegates as Lua functions. */ internal object getNetObject(KopiLua.Lua.lua_State luaState,int index) { int idx=LuaLib.luanet_tonetobject(luaState,index); if(idx!=-1) return objects[idx]; else return null; } /* * Gets the CLR object in the index positon of the Lua stack. Returns * delegates as is. */ internal object getRawNetObject(KopiLua.Lua.lua_State luaState,int index) { int udata=LuaLib.luanet_rawnetobj(luaState,index); if(udata!=-1) { return objects[udata]; } return null; } /* * Pushes the entire array into the Lua stack and returns the number * of elements pushed. */ internal int returnValues(KopiLua.Lua.lua_State luaState, object[] returnValues) { if(KopiLua.Lua.lua_checkstack(luaState,returnValues.Length+5).ToBoolean()) { for(int i=0;i