diff --git a/AUTHORS b/AUTHORS index 960bb57077..19d7cd2132 100644 --- a/AUTHORS +++ b/AUTHORS @@ -69,6 +69,9 @@ Also thanks to: Using GeoLite data created by MaxMind and distributed under the CC BY-SA 3.0 license. +Using MonoBoxedLua created by Sparklin Labs +and distributed under the MIT license. + Finally, special thanks goes to the original teams at Westwood Studios and EA for creating the classic games which OpenRA aims to reimagine. diff --git a/LuaInterface/AssemblyInfo.cs b/LuaInterface/AssemblyInfo.cs new file mode 100644 index 0000000000..70db5f2c9b --- /dev/null +++ b/LuaInterface/AssemblyInfo.cs @@ -0,0 +1,56 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Security.Permissions; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("LuaInterface")] +[assembly: AssemblyDescription("Bridge between the Lua runtime and the CLR")] +[assembly: AssemblyCopyright("Copyright 2003-2008 Fabio Mascarenhas, Kevin Hester")] +[assembly: CLSCompliant(false)] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("2.0.4.*")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// + +// We call native DLLs (lua50) and we don't want to be fucked with or validated +// [assembly: SecurityPermission(RequestMinimum, UnmanagedCode = true)] diff --git a/LuaInterface/CheckType.cs b/LuaInterface/CheckType.cs new file mode 100644 index 0000000000..6fda337278 --- /dev/null +++ b/LuaInterface/CheckType.cs @@ -0,0 +1,314 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace LuaInterface +{ + /* + * Type checking and conversion functions. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class CheckType + { + private ObjectTranslator translator; + + ExtractValue extractNetObject; + Dictionary extractValues = new Dictionary(); + + public CheckType(ObjectTranslator translator) + { + this.translator = translator; + + extractValues.Add(typeof(object).TypeHandle.Value.ToInt64(), new ExtractValue(getAsObject)); + extractValues.Add(typeof(sbyte).TypeHandle.Value.ToInt64(), new ExtractValue(getAsSbyte)); + extractValues.Add(typeof(byte).TypeHandle.Value.ToInt64(), new ExtractValue(getAsByte)); + extractValues.Add(typeof(short).TypeHandle.Value.ToInt64(), new ExtractValue(getAsShort)); + extractValues.Add(typeof(ushort).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUshort)); + extractValues.Add(typeof(int).TypeHandle.Value.ToInt64(), new ExtractValue(getAsInt)); + extractValues.Add(typeof(uint).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUint)); + extractValues.Add(typeof(long).TypeHandle.Value.ToInt64(), new ExtractValue(getAsLong)); + extractValues.Add(typeof(ulong).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUlong)); + extractValues.Add(typeof(double).TypeHandle.Value.ToInt64(), new ExtractValue(getAsDouble)); + extractValues.Add(typeof(char).TypeHandle.Value.ToInt64(), new ExtractValue(getAsChar)); + extractValues.Add(typeof(float).TypeHandle.Value.ToInt64(), new ExtractValue(getAsFloat)); + extractValues.Add(typeof(decimal).TypeHandle.Value.ToInt64(), new ExtractValue(getAsDecimal)); + extractValues.Add(typeof(bool).TypeHandle.Value.ToInt64(), new ExtractValue(getAsBoolean)); + extractValues.Add(typeof(string).TypeHandle.Value.ToInt64(), new ExtractValue(getAsString)); + extractValues.Add(typeof(LuaFunction).TypeHandle.Value.ToInt64(), new ExtractValue(getAsFunction)); + extractValues.Add(typeof(LuaTable).TypeHandle.Value.ToInt64(), new ExtractValue(getAsTable)); + extractValues.Add(typeof(LuaUserData).TypeHandle.Value.ToInt64(), new ExtractValue(getAsUserdata)); + + extractNetObject = new ExtractValue(getAsNetObject); + } + + /* + * Checks if the value at Lua stack index stackPos matches paramType, + * returning a conversion function if it does and null otherwise. + */ + internal ExtractValue getExtractor(IReflect paramType) + { + return getExtractor(paramType.UnderlyingSystemType); + } + internal ExtractValue getExtractor(Type paramType) + { + if(paramType.IsByRef) paramType=paramType.GetElementType(); + + long runtimeHandleValue = paramType.TypeHandle.Value.ToInt64(); + + if(extractValues.ContainsKey(runtimeHandleValue)) + return extractValues[runtimeHandleValue]; + else + return extractNetObject; + } + + internal ExtractValue checkType(IntPtr luaState,int stackPos,Type paramType) + { + LuaTypes luatype = LuaDLL.lua_type(luaState, stackPos); + + if(paramType.IsByRef) paramType=paramType.GetElementType(); + + Type underlyingType = Nullable.GetUnderlyingType(paramType); + if (underlyingType != null) + { + paramType = underlyingType; // Silently convert nullable types to their non null requics + } + + long runtimeHandleValue = paramType.TypeHandle.Value.ToInt64(); + + if (paramType.Equals(typeof(object))) + return extractValues[runtimeHandleValue]; + + /* + //CP: Added support for generic parameters + if (paramType.IsGenericParameter) + { + if (luatype == LuaTypes.LUA_TBOOLEAN) + return extractValues[typeof(bool).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TSTRING) + return extractValues[typeof(string).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TTABLE) + return extractValues[typeof(LuaTable).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TUSERDATA) + return extractValues[typeof(object).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TFUNCTION) + return extractValues[typeof(LuaFunction).TypeHandle.Value.ToInt64()]; + else if (luatype == LuaTypes.LUA_TNUMBER) + return extractValues[typeof(double).TypeHandle.Value.ToInt64()]; + //else // suppress CS0642 + ;//an unsupported type was encountered + } + */ + + if (LuaDLL.lua_isnumber(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + + if (paramType == typeof(bool)) + { + if (LuaDLL.lua_isboolean(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + } + else if (paramType == typeof(string)) + { + if (LuaDLL.lua_isstring(luaState, stackPos)) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // kevinh - silently convert nil to a null string pointer + } + else if (paramType == typeof(LuaTable)) + { + if (luatype == LuaTypes.LUA_TTABLE) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // tkopal - silently convert nil to a null table + } + else if (paramType == typeof(LuaUserData)) + { + if (luatype == LuaTypes.LUA_TUSERDATA) + return extractValues[runtimeHandleValue]; + } + else if (paramType == typeof(LuaFunction)) + { + if (luatype == LuaTypes.LUA_TFUNCTION) + return extractValues[runtimeHandleValue]; + else if (luatype == LuaTypes.LUA_TNIL) + return extractNetObject; // elisee - silently convert nil to a null string pointer + } + else if (typeof(Delegate).IsAssignableFrom(paramType) && luatype == LuaTypes.LUA_TFUNCTION) + { + return new ExtractValue(new DelegateGenerator(translator, paramType).extractGenerated); + } + else if (paramType.IsInterface && luatype == LuaTypes.LUA_TTABLE) + { + return new ExtractValue(new ClassGenerator(translator, paramType).extractGenerated); + } + else if ((paramType.IsInterface || paramType.IsClass) && luatype == LuaTypes.LUA_TNIL) + { + // kevinh - allow nil to be silently converted to null - extractNetObject will return null when the item ain't found + return extractNetObject; + } + else if (LuaDLL.lua_type(luaState, stackPos) == LuaTypes.LUA_TTABLE) + { + if (LuaDLL.luaL_getmetafield(luaState, stackPos, "__index")) + { + object obj = translator.getNetObject(luaState, -1); + LuaDLL.lua_settop(luaState, -2); + if (obj != null && paramType.IsAssignableFrom(obj.GetType())) + return extractNetObject; + } + else + return null; + } + else + { + object obj = translator.getNetObject(luaState, stackPos); + if (obj != null && paramType.IsAssignableFrom(obj.GetType())) + return extractNetObject; + } + + return null; + } + + /* + * The following functions return the value in the Lua stack + * index stackPos as the desired type if it can, or null + * otherwise. + */ + private object getAsSbyte(IntPtr luaState,int stackPos) + { + sbyte retVal=(sbyte)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsByte(IntPtr luaState,int stackPos) + { + byte retVal=(byte)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsShort(IntPtr luaState,int stackPos) + { + short retVal=(short)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUshort(IntPtr luaState,int stackPos) + { + ushort retVal=(ushort)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsInt(IntPtr luaState,int stackPos) + { + int retVal=(int)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUint(IntPtr luaState,int stackPos) + { + uint retVal=(uint)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsLong(IntPtr luaState,int stackPos) + { + long retVal=(long)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsUlong(IntPtr luaState,int stackPos) + { + ulong retVal=(ulong)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsDouble(IntPtr luaState,int stackPos) + { + double retVal=LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsChar(IntPtr luaState,int stackPos) + { + char retVal=(char)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsFloat(IntPtr luaState,int stackPos) + { + float retVal=(float)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsDecimal(IntPtr luaState,int stackPos) + { + decimal retVal=(decimal)LuaDLL.lua_tonumber(luaState,stackPos); + if(retVal==0 && !LuaDLL.lua_isnumber(luaState,stackPos)) return null; + return retVal; + } + private object getAsBoolean(IntPtr luaState,int stackPos) + { + return LuaDLL.lua_toboolean(luaState,stackPos); + } + private object getAsString(IntPtr luaState,int stackPos) + { + string retVal=LuaDLL.lua_tostring(luaState,stackPos); + if(retVal=="" && !LuaDLL.lua_isstring(luaState,stackPos)) return null; + return retVal; + } + private object getAsTable(IntPtr luaState,int stackPos) + { + return translator.getTable(luaState,stackPos); + } + private object getAsFunction(IntPtr luaState,int stackPos) + { + return translator.getFunction(luaState,stackPos); + } + private object getAsUserdata(IntPtr luaState,int stackPos) + { + return translator.getUserData(luaState,stackPos); + } + public object getAsObject(IntPtr luaState,int stackPos) + { + if(LuaDLL.lua_type(luaState,stackPos)==LuaTypes.LUA_TTABLE) + { + if(LuaDLL.luaL_getmetafield(luaState,stackPos,"__index")) + { + if(LuaDLL.luaL_checkmetatable(luaState,-1)) + { + LuaDLL.lua_insert(luaState,stackPos); + LuaDLL.lua_remove(luaState,stackPos+1); + } + else + { + LuaDLL.lua_settop(luaState,-2); + } + } + } + object obj=translator.getObject(luaState,stackPos); + return obj; + } + public object getAsNetObject(IntPtr luaState,int stackPos) + { + object obj=translator.getNetObject(luaState,stackPos); + if(obj==null && LuaDLL.lua_type(luaState,stackPos)==LuaTypes.LUA_TTABLE) + { + if(LuaDLL.luaL_getmetafield(luaState,stackPos,"__index")) + { + if(LuaDLL.luaL_checkmetatable(luaState,-1)) + { + LuaDLL.lua_insert(luaState,stackPos); + LuaDLL.lua_remove(luaState,stackPos+1); + obj=translator.getNetObject(luaState,stackPos); + } + else + { + LuaDLL.lua_settop(luaState,-2); + } + } + } + return obj; + } + } +} diff --git a/LuaInterface/GenerateEventAssembly.cs b/LuaInterface/GenerateEventAssembly.cs new file mode 100644 index 0000000000..775b62a2cb --- /dev/null +++ b/LuaInterface/GenerateEventAssembly.cs @@ -0,0 +1,683 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Threading; + +namespace LuaInterface +{ + /* + * Structure to store a type and the return types of + * its methods (the type of the returned value and out/ref + * parameters). + */ + struct LuaClassType + { + public Type klass; + public Type[][] returnTypes; + } + + /* + * Common interface for types generated from tables. The method + * returns the table that overrides some or all of the type's methods. + */ + public interface ILuaGeneratedType + { + LuaTable __luaInterface_getLuaTable(); + } + + /* + * Class used for generating delegates that get a function from the Lua + * stack as a delegate of a specific type. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class DelegateGenerator + { + private ObjectTranslator translator; + private Type delegateType; + + public DelegateGenerator(ObjectTranslator translator,Type delegateType) + { + this.translator=translator; + this.delegateType=delegateType; + } + public object extractGenerated(IntPtr luaState,int stackPos) + { + return CodeGeneration.Instance.GetDelegate(delegateType,translator.getFunction(luaState,stackPos)); + } + } + + /* + * Class used for generating delegates that get a table from the Lua + * stack as a an object of a specific type. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class ClassGenerator + { + private ObjectTranslator translator; + private Type klass; + + public ClassGenerator(ObjectTranslator translator,Type klass) + { + this.translator=translator; + this.klass=klass; + } + public object extractGenerated(IntPtr luaState,int stackPos) + { + return CodeGeneration.Instance.GetClassInstance(klass,translator.getTable(luaState,stackPos)); + } + } + + /* + * Dynamically generates new types from existing types and + * Lua function and table values. Generated types are event handlers, + * delegates, interface implementations and subclasses. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class CodeGeneration + { + private Type eventHandlerParent=typeof(LuaEventHandler); + private Dictionary eventHandlerCollection=new Dictionary(); + + private Type delegateParent=typeof(LuaDelegate); + private Dictionary delegateCollection=new Dictionary(); + + private Type classHelper=typeof(LuaClassHelper); + private Dictionary classCollection=new Dictionary(); + + private AssemblyName assemblyName; + private AssemblyBuilder newAssembly; + private ModuleBuilder newModule; + private int luaClassNumber=1; + private static readonly CodeGeneration instance = new CodeGeneration(); + + static CodeGeneration() + { + } + + private CodeGeneration() + { + // Create an assembly name + assemblyName=new AssemblyName( ); + assemblyName.Name="LuaInterface_generatedcode"; + // Create a new assembly with one module. + newAssembly=Thread.GetDomain().DefineDynamicAssembly( + assemblyName, AssemblyBuilderAccess.Run); + newModule=newAssembly.DefineDynamicModule("LuaInterface_generatedcode"); + } + + /* + * Singleton instance of the class + */ + public static CodeGeneration Instance + { + get + { + return instance; + } + } + + /* + * Generates an event handler that calls a Lua function + */ + private Type GenerateEvent(Type eventHandlerType) + { + string typeName; + lock(this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + // Define a public class in the assembly, called typeName + TypeBuilder myType=newModule.DefineType(typeName,TypeAttributes.Public,eventHandlerParent); + + // Defines the handler method. Its signature is void(object,) + Type[] paramTypes = new Type[2]; + paramTypes[0]=typeof(object); + paramTypes[1]=eventHandlerType; + Type returnType=typeof(void); + MethodBuilder handleMethod=myType.DefineMethod("HandleEvent", + MethodAttributes.Public|MethodAttributes.HideBySig, + returnType,paramTypes); + + // Emits the IL for the method. It loads the arguments + // and calls the handleEvent method of the base class + ILGenerator generator=handleMethod.GetILGenerator( ); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + MethodInfo miGenericEventHandler; + miGenericEventHandler=eventHandlerParent.GetMethod("handleEvent"); + generator.Emit(OpCodes.Call,miGenericEventHandler); + // returns + generator.Emit(OpCodes.Ret); + + // creates the new type + return myType.CreateType(); + } + + /* + * Generates a type that can be used for instantiating a delegate + * of the provided type, given a Lua function. + */ + private Type GenerateDelegate(Type delegateType) + { + string typeName; + lock(this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + // Define a public class in the assembly, called typeName + TypeBuilder myType=newModule.DefineType(typeName,TypeAttributes.Public,delegateParent); + + // Defines the delegate method with the same signature as the + // Invoke method of delegateType + MethodInfo invokeMethod=delegateType.GetMethod("Invoke"); + ParameterInfo[] paramInfo=invokeMethod.GetParameters(); + Type[] paramTypes=new Type[paramInfo.Length]; + Type returnType=invokeMethod.ReturnType; + + // Counts out and ref params, for use later + int nOutParams=0; int nOutAndRefParams=0; + for(int i=0;i returnTypesList=new List(); + + // Counts out and ref parameters, for later use, + // and creates the list of return types + int nOutParams=0; int nOutAndRefParams=0; + Type returnType=method.ReturnType; + returnTypesList.Add(returnType); + for(int i=0;i returnTypes=new List(); + Type luaDelegateType; + if (delegateCollection.ContainsKey(delegateType)) + { + luaDelegateType=delegateCollection[delegateType]; + } + else + { + luaDelegateType=GenerateDelegate(delegateType); + delegateCollection[delegateType] = luaDelegateType; + } + MethodInfo methodInfo=delegateType.GetMethod("Invoke"); + returnTypes.Add(methodInfo.ReturnType); + foreach(ParameterInfo paramInfo in methodInfo.GetParameters()) + if(paramInfo.ParameterType.IsByRef) + returnTypes.Add(paramInfo.ParameterType); + LuaDelegate luaDelegate=(LuaDelegate)Activator.CreateInstance(luaDelegateType); + luaDelegate.function=luaFunc; + luaDelegate.returnTypes=returnTypes.ToArray(); + return Delegate.CreateDelegate(delegateType,luaDelegate,"CallFunction"); + } + + /* + * Gets an instance of an implementation of the klass interface or + * subclass of klass that delegates public virtual methods to the + * luaTable table. + * Caches the generated type. + */ + public object GetClassInstance(Type klass, LuaTable luaTable) + { + LuaClassType luaClassType; + if (classCollection.ContainsKey(klass)) + { + luaClassType=classCollection[klass]; + } + else + { + luaClassType=new LuaClassType(); + GenerateClass(klass,out luaClassType.klass,out luaClassType.returnTypes,luaTable); + classCollection[klass] = luaClassType; + } + return Activator.CreateInstance(luaClassType.klass,new object[] {luaTable,luaClassType.returnTypes}); + } + } +} diff --git a/LuaInterface/Lua.cs b/LuaInterface/Lua.cs new file mode 100644 index 0000000000..cc5ba42077 --- /dev/null +++ b/LuaInterface/Lua.cs @@ -0,0 +1,761 @@ + +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Reflection; + using System.Threading; + using OpenRA; + using System.Text; + + /* + * Main class of LuaInterface + * Object-oriented wrapper to Lua API + * + * Author: Fabio Mascarenhas + * Version: 1.0 + * + * // steffenj: important changes in Lua class: + * - removed all Open*Lib() functions + * - all libs automatically open in the Lua class constructor (just assign nil to unwanted libs) + * */ + public class Lua : IDisposable + { + /*readonly */ IntPtr luaState; + ObjectTranslator translator; + + LuaFunctionCallback panicCallback; + LuaCSFunction tracebackFunction; + // lockCallback, unlockCallback; used by debug code commented out for now + + public Lua() + { + luaState = LuaDLL.luaL_newstate(); // steffenj: Lua 5.1.1 API change (lua_open is gone) + + // Load libraries + LuaDLL.luaL_openlibs(luaState); + + // Add LuaInterface marker + LuaDLL.lua_pushstring(luaState, "LUAINTERFACE LOADED"); + LuaDLL.lua_pushboolean(luaState, true); + LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + + translator=new ObjectTranslator(this,luaState); + + tracebackFunction = new LuaCSFunction(traceback); + + // We need to keep this in a managed reference so the delegate doesn't get garbage collected + panicCallback = new LuaFunctionCallback(PanicCallback); + LuaDLL.lua_atpanic(luaState, panicCallback); + } + + private bool _StatePassed; + + /* + * CAUTION: LuaInterface.Lua instances can't share the same lua state! + */ + public Lua( Int64 luaState ) + { + // Check for existing LuaInterface marker + IntPtr lState = new IntPtr(luaState); + LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED"); + LuaDLL.lua_gettable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX); + + if(LuaDLL.lua_toboolean(lState,-1)) + { + LuaDLL.lua_settop(lState,-2); + throw new LuaException("There is already a LuaInterface.Lua instance associated with this Lua state"); + } + else + { + // Add LuaInterface marker + LuaDLL.lua_settop(lState,-2); + LuaDLL.lua_pushstring(lState, "LUAINTERFACE LOADED"); + LuaDLL.lua_pushboolean(lState, true); + LuaDLL.lua_settable(lState, (int)LuaIndexes.LUA_REGISTRYINDEX); + + this.luaState = lState; + LuaDLL.lua_pushvalue(lState, (int)LuaIndexes.LUA_GLOBALSINDEX); + + translator = new ObjectTranslator(this, this.luaState); + } + + _StatePassed = true; + } + + public void Close() + { + if (_StatePassed) + return; + + if (luaState != IntPtr.Zero) + LuaDLL.lua_close(luaState); + //luaState = IntPtr.Zero; <- suggested by Christopher Cebulski http://luaforge.net/forum/forum.php?thread_id=44593&forum_id=146 + } + + static int PanicCallback(IntPtr luaState) + { + // string desc = LuaDLL.lua_tostring(luaState, 1); + string reason = String.Format("unprotected error in call to Lua API ({0})", LuaDLL.lua_tostring(luaState, -1)); + + // lua_tostring(L, -1); + + throw new LuaException(reason); + } + + + + /// + /// Assuming we have a Lua error string sitting on the stack, throw a C# exception out to the user's app + /// + /// Thrown if the script caused an exception + void ThrowExceptionFromError(int oldTop) + { + object err = translator.getObject(luaState, -1); + LuaDLL.lua_settop(luaState, oldTop); + + // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved) + LuaScriptException luaEx = err as LuaScriptException; + if (luaEx != null) throw luaEx; + + // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it + if (err == null) err = "Unknown Lua Error"; + throw new LuaScriptException(err.ToString(), ""); + } + + + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + internal int SetPendingException(Exception e) + { + Exception caughtExcept = e; + + if (caughtExcept != null) + { + translator.throwError(luaState, caughtExcept); + LuaDLL.lua_pushnil(luaState); + + return 1; + } + else + return 0; + } + + private bool executing; + + /// + /// True while a script is being executed + /// + public bool IsExecuting { get { return executing; } } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(string chunk, string name) + { + int oldTop = LuaDLL.lua_gettop(luaState); + + executing = true; + + // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length + // Adapted for OpenRA + var chunkLength = Platform.CurrentPlatform != PlatformType.Windows + ? Encoding.UTF8.GetByteCount(chunk) + : chunk.Length; + + try + { + if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, name) != 0) + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + + LuaFunction result = translator.getFunction(luaState, -1); + translator.popValues(luaState, oldTop); + + return result; + } + + /// + /// + /// + /// + /// + public LuaFunction LoadFile(string fileName) + { + int oldTop = LuaDLL.lua_gettop(luaState); + if (LuaDLL.luaL_loadfile(luaState, fileName) != 0) + ThrowExceptionFromError(oldTop); + + LuaFunction result = translator.getFunction(luaState, -1); + translator.popValues(luaState, oldTop); + + return result; + } + + + /* + * Excutes a Lua chunk and returns all the chunk's return + * values in an array + */ + public object[] DoString(string chunk) + { + return DoString(chunk,"chunk"); + } + + /// + /// Executes a Lua chnk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk + /// + public object[] DoString(string chunk, string chunkName) + { + int oldTop = LuaDLL.lua_gettop(luaState); + executing = true; + + // Somehow, on OS X, we need to use the UTF-8 byte count rather than the string length + // Adapted for OpenRA + var chunkLength = Platform.CurrentPlatform != PlatformType.Windows + ? Encoding.UTF8.GetByteCount(chunk) + : chunk.Length; + + if (LuaDLL.luaL_loadbuffer(luaState, chunk, chunkLength, chunkName) == 0) + { + try + { + if (LuaDLL.lua_pcall(luaState, 0, -1, 0) == 0) + return translator.popValues(luaState, oldTop); + else + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + } + else + ThrowExceptionFromError(oldTop); + + return null; // Never reached - keeps compiler happy + } + + private int traceback(IntPtr luaState) + { + LuaDLL.lua_getglobal(luaState,"debug"); + LuaDLL.lua_getfield(luaState,-1,"traceback"); + LuaDLL.lua_pushvalue(luaState,1); + LuaDLL.lua_pushnumber(luaState,2); + LuaDLL.lua_call (luaState,2,1); + return 1; + } + + /* + * Excutes a Lua file and returns all the chunk's return + * values in an array + */ + public object[] DoFile(string fileName) + { + LuaDLL.lua_pushstdcallcfunction(luaState,tracebackFunction); + int oldTop=LuaDLL.lua_gettop(luaState); + if(LuaDLL.luaL_loadfile(luaState,fileName)==0) + { + executing = true; + try + { + if (LuaDLL.lua_pcall(luaState, 0, -1, -2) == 0) + return translator.popValues(luaState, oldTop); + else + ThrowExceptionFromError(oldTop); + } + finally { executing = false; } + } + else + ThrowExceptionFromError(oldTop); + + return null; // Never reached - keeps compiler happy + } + + + public void CollectGarbage() + { + LuaDLL.lua_gc( luaState, LuaGCOptions.LUA_GCCOLLECT, 0 ); + } + + /* + * Indexer for global variables from the LuaInterpreter + * Supports navigation of tables by using . operator + */ + public object this[string fullPath] + { + get + { + object returnValue=null; + int oldTop=LuaDLL.lua_gettop(luaState); + string[] path=fullPath.Split(new char[] { '.' }); + LuaDLL.lua_getglobal(luaState,path[0]); + returnValue=translator.getObject(luaState,-1); + if(path.Length>1) + { + string[] remainingPath=new string[path.Length-1]; + Array.Copy(path,1,remainingPath,0,path.Length-1); + returnValue=getObject(remainingPath); + } + LuaDLL.lua_settop(luaState,oldTop); + return returnValue; + } + set + { + int oldTop=LuaDLL.lua_gettop(luaState); + string[] path=fullPath.Split(new char[] { '.' }); + if(path.Length==1) + { + translator.push(luaState,value); + LuaDLL.lua_setglobal(luaState,fullPath); + } + else + { + LuaDLL.lua_getglobal(luaState,path[0]); + string[] remainingPath=new string[path.Length-1]; + Array.Copy(path,1,remainingPath,0,path.Length-1); + setObject(remainingPath,value); + } + LuaDLL.lua_settop(luaState,oldTop); + + // Globals auto-complete + if (value == null) + { + // Remove now obsolete entries + globals.Remove(fullPath); + } + else + { + // Add new entries + if (!globals.Contains(fullPath)) + registerGlobal(fullPath, value.GetType(), 0); + } + } + } + + #region Globals auto-complete + private readonly List globals = new List(); + private bool globalsSorted; + + /// + /// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance + /// + /// Members of globals are also listed. The formatting is optimized for text input auto-completion. + public IEnumerable Globals + { + get + { + // Only sort list when necessary + if (!globalsSorted) + { + globals.Sort(); + globalsSorted = true; + } + + return globals; + } + } + + /// + /// Adds an entry to (recursivley handles 2 levels of members) + /// + /// The index accessor path ot the entry + /// The type of the entry + /// How deep have we gone with recursion? + private void registerGlobal(string path, Type type, int recursionCounter) + { + // If the type is a global method, list it directly + if (type == typeof(LuaCSFunction)) + { + // Format for easy method invocation + globals.Add(path + "("); + } + // If the type is a class or an interface and recursion hasn't been running too long, list the members + else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < 2) + { + #region Methods + foreach (MethodInfo method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (method.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) && + // Exclude some generic .NET methods that wouldn't be very usefull in Lua + method.Name != "GetType" && method.Name != "GetHashCode" && method.Name != "Equals" && + method.Name != "ToString" && method.Name != "Clone" && method.Name != "Dispose" && + method.Name != "GetEnumerator" && method.Name != "CopyTo" && + !method.Name.StartsWith("get_", StringComparison.Ordinal) && + !method.Name.StartsWith("set_", StringComparison.Ordinal) && + !method.Name.StartsWith("add_", StringComparison.Ordinal) && + !method.Name.StartsWith("remove_", StringComparison.Ordinal)) + { + // Format for easy method invocation + string command = path + ":" + method.Name + "("; + if (method.GetParameters().Length == 0) command += ")"; + globals.Add(command); + } + } + #endregion + + #region Fields + foreach (FieldInfo field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (field.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0)) + { + // Go into recursion for members + registerGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1); + } + } + #endregion + + #region Properties + foreach (PropertyInfo property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (property.GetCustomAttributes(typeof(LuaHideAttribute), false).Length == 0) && + (property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Length == 0) + // Exclude some generic .NET properties that wouldn't be very usefull in Lua + && property.Name != "Item") + { + // Go into recursion for members + registerGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1); + } + } + #endregion + } + // Otherwise simply add the element to the list + else globals.Add(path); + + // List will need to be sorted on next access + globalsSorted = false; + } + #endregion + + /* + * Navigates a table in the top of the stack, returning + * the value of the specified field + */ + internal object getObject(string[] remainingPath) + { + object returnValue=null; + for(int i=0;i + /// Base class to provide consistent disposal flow across lua objects. Uses code provided by Yves Duhoux and suggestions by Hans Schmeidenbacher and Qingrui Li + /// + public abstract class LuaBase : IDisposable + { + private bool _Disposed; + protected int _Reference; + protected Lua _Interpreter; + + ~LuaBase() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public virtual void Dispose(bool disposeManagedResources) + { + if (!_Disposed) + { + if (disposeManagedResources) + { + if (_Reference != 0) + _Interpreter.dispose(_Reference); + } + _Interpreter = null; + _Disposed = true; + } + } + + public override bool Equals(object o) + { + if (o is LuaBase) + { + LuaBase l = (LuaBase)o; + return _Interpreter.compareRef(l._Reference, _Reference); + } + else return false; + } + + public override int GetHashCode() + { + return _Reference; + } + } +} diff --git a/LuaInterface/LuaDLL.cs b/LuaInterface/LuaDLL.cs new file mode 100644 index 0000000000..3156a67dc9 --- /dev/null +++ b/LuaInterface/LuaDLL.cs @@ -0,0 +1,456 @@ +namespace LuaInterface +{ + + using System; + using System.Runtime.InteropServices; + using System.Reflection; + using System.Collections; + using System.Text; + using System.Security; + + /* + * Lua types for the API, returned by lua_type function + */ + public enum LuaTypes + { + LUA_TNONE=-1, + LUA_TNIL=0, + LUA_TNUMBER=3, + LUA_TSTRING=4, + LUA_TBOOLEAN=1, + LUA_TTABLE=5, + LUA_TFUNCTION=6, + LUA_TUSERDATA=7, + LUA_TLIGHTUSERDATA=2 + } + + // steffenj: BEGIN lua garbage collector options + /* + * Lua Garbage Collector options (param "what") + */ + public enum LuaGCOptions + { + LUA_GCSTOP = 0, + LUA_GCRESTART = 1, + LUA_GCCOLLECT = 2, + LUA_GCCOUNT = 3, + LUA_GCCOUNTB = 4, + LUA_GCSTEP = 5, + LUA_GCSETPAUSE = 6, + LUA_GCSETSTEPMUL = 7, + } + /* + sealed class LuaGCOptions + { + public static int LUA_GCSTOP = 0; + public static int LUA_GCRESTART = 1; + public static int LUA_GCCOLLECT = 2; + public static int LUA_GCCOUNT = 3; + public static int LUA_GCCOUNTB = 4; + public static int LUA_GCSTEP = 5; + public static int LUA_GCSETPAUSE = 6; + public static int LUA_GCSETSTEPMUL = 7; + }; + */ + // steffenj: END lua garbage collector options + + /* + * Special stack indexes + */ + sealed class LuaIndexes + { + public static int LUA_REGISTRYINDEX=-10000; + public static int LUA_ENVIRONINDEX=-10001; // steffenj: added environindex + public static int LUA_GLOBALSINDEX=-10002; // steffenj: globalsindex previously was -10001 + } + + /* + * Structure used by the chunk reader + */ + [ StructLayout( LayoutKind.Sequential )] + public struct ReaderInfo + { + public String chunkData; + public bool finished; + } + + /* + * Delegate for functions passed to Lua as function pointers + */ + public delegate int LuaCSFunction(IntPtr luaState); + + /* + * Delegate for chunk readers used with lua_load + */ + public delegate string LuaChunkReader(IntPtr luaState,ref ReaderInfo data,ref uint size); + + + /// + /// Used to handle Lua panics + /// + /// + /// + public delegate int LuaFunctionCallback(IntPtr luaState); + + /* + * P/Invoke wrapper of the Lua API + * + * Author: Fabio Mascarenhas + * Version: 1.0 + * + * // steffenj: noteable changes in the LuaDLL API: + * - luaopen_* functions are gone + * (however Lua class constructor already calls luaL_openlibs now, so just remove these calls) + * - deprecated functions: lua_open, lua_strlen, lua_dostring + * (they still work but may be removed with next Lua version) + * + * list of functions of the Lua 5.1.1 C API that are not in LuaDLL + * i thought this may come in handy for the next Lua version upgrade and for anyone to see + * what the differences are in the APIs (C API vs LuaDLL API) + lua_concat (use System.String concatenation or similar) + lua_cpcall (no point in calling C functions) + lua_dump (would write to unmanaged memory via lua_Writer) + lua_getallocf (no C functions/pointers) + lua_isthread (no threads) + lua_newstate (use luaL_newstate) + lua_newthread (no threads) + lua_pushcclosure (no C functions/pointers) + lua_pushcfunction (no C functions/pointers) + lua_pushfstring (use lua_pushstring) + lua_pushthread (no threads) + lua_pushvfstring (use lua_pushstring) + lua_register (all libs already opened, use require in scripts for external libs) + lua_resume (no threads) + lua_setallocf (no C functions/pointers) + lua_status (no threads) + lua_tointeger (use System.Convert) + lua_tolstring (use lua_tostring) + lua_topointer (no C functions/pointers) + lua_tothread (no threads) + lua_xmove (no threads) + lua_yield (no threads) + + luaL_add* (use System.String concatenation or similar) + luaL_argcheck (function argument checking unnecessary) + luaL_argerror (function argument checking unnecessary) + luaL_buffinit (use System.String concatenation or similar) + luaL_checkany (function argument checking unnecessary) + luaL_checkint (function argument checking unnecessary) + luaL_checkinteger (function argument checking unnecessary) + luaL_checklong (function argument checking unnecessary) + luaL_checklstring (function argument checking unnecessary) + luaL_checknumber (function argument checking unnecessary) + luaL_checkoption (function argument checking unnecessary) + luaL_checkstring (function argument checking unnecessary) + luaL_checktype (function argument checking unnecessary) + luaL_prepbuffer (use System.String concatenation or similar) + luaL_pushresult (use System.String concatenation or similar) + luaL_register (all libs already opened, use require in scripts for external libs) + luaL_typerror (function argument checking unnecessary) + + (complete lua_Debug interface omitted) + lua_gethook*** + lua_getinfo + lua_getlocal + lua_getstack + lua_getupvalue + lua_sethook + lua_setlocal + lua_setupvalue + */ + public class LuaDLL + { + // for debugging + // const string BASEPATH = @"C:\development\software\dotnet\tools\PulseRecognizer\PulseRecognizer\bin\Debug\"; + // const string BASEPATH = @"C:\development\software\ThirdParty\lua\Built\"; + const string LUADLL = "lua51.dll"; + const string LUALIBDLL = LUADLL; + const string STUBDLL = LUADLL; + + // steffenj: BEGIN additional Lua API functions new in Lua 5.1 + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_gc(IntPtr luaState, LuaGCOptions what, int data); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern string lua_typename(IntPtr luaState, LuaTypes type); + public static string luaL_typename(IntPtr luaState, int stackPos) + { + return LuaDLL.lua_typename(luaState, LuaDLL.lua_type(luaState, stackPos)); + } + + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaL_error(IntPtr luaState, string message); + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern string luaL_gsub(IntPtr luaState, string str, string pattern, string replacement); + + // the functions below are still untested + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_getfenv(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_isfunction(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_islightuserdata(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_istable(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_isuserdata(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_lessthan(IntPtr luaState, int stackPos1, int stackPos2); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_rawequal(IntPtr luaState, int stackPos1, int stackPos2); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_setfenv(IntPtr luaState, int stackPos); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_setfield(IntPtr luaState, int stackPos, string name); + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int luaL_callmeta(IntPtr luaState, int stackPos, string name); + // steffenj: END additional Lua API functions new in Lua 5.1 + + // steffenj: BEGIN Lua 5.1.1 API change (lua_open replaced by luaL_newstate) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr luaL_newstate(); + /// DEPRECATED - use luaL_newstate() instead! + public static IntPtr lua_open() + { + return LuaDLL.luaL_newstate(); + } + // steffenj: END Lua 5.1.1 API change (lua_open replaced by luaL_newstate) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_close(IntPtr luaState); + // steffenj: BEGIN Lua 5.1.1 API change (new function luaL_openlibs) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaL_openlibs(IntPtr luaState); + /* + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void luaopen_base(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_io(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_table(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_string(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_math(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_debug(IntPtr luaState); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaopen_loadlib(IntPtr luaState); + */ + // steffenj: END Lua 5.1.1 API change (new function luaL_openlibs) + // steffenj: BEGIN Lua 5.1.1 API change (lua_strlen is now lua_objlen) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int lua_objlen(IntPtr luaState, int stackPos); + /// DEPRECATED - use lua_objlen(IntPtr luaState, int stackPos) instead! + public static int lua_strlen(IntPtr luaState, int stackPos) + { + return lua_objlen(luaState, stackPos); + } + // steffenj: END Lua 5.1.1 API change (lua_strlen is now lua_objlen) + // steffenj: BEGIN Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern int luaL_loadstring(IntPtr luaState, string chunk); + public static int luaL_dostring(IntPtr luaState, string chunk) + { + int result = LuaDLL.luaL_loadstring(luaState, chunk); + if (result != 0) + return result; + + return LuaDLL.lua_pcall(luaState, 0, -1, 0); + } + /// DEPRECATED - use luaL_dostring(IntPtr luaState, string chunk) instead! + public static int lua_dostring(IntPtr luaState, string chunk) + { + return LuaDLL.luaL_dostring(luaState, chunk); + } + // steffenj: END Lua 5.1.1 API change (lua_dostring is now a macro luaL_dostring) + // steffenj: BEGIN Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_createtable(IntPtr luaState, int narr, int nrec); + public static void lua_newtable(IntPtr luaState) + { + LuaDLL.lua_createtable(luaState, 0, 0); + } + // steffenj: END Lua 5.1.1 API change (lua_newtable is gone, lua_createtable is new) + // steffenj: BEGIN Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile macro) + //[DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static int luaL_dofile(IntPtr luaState, string fileName) + { + int result = LuaDLL.luaL_loadfile(luaState, fileName); + if (result != 0) + return result; + + return LuaDLL.lua_pcall(luaState, 0, -1, 0); + } + // steffenj: END Lua 5.1.1 API change (lua_dofile now in LuaLib as luaL_dofile) + public static void lua_getglobal(IntPtr luaState, string name) + { + LuaDLL.lua_pushstring(luaState,name); + LuaDLL.lua_gettable(luaState,LuaIndexes.LUA_GLOBALSINDEX); + } + public static void lua_setglobal(IntPtr luaState, string name) + { + LuaDLL.lua_pushstring(luaState,name); + LuaDLL.lua_insert(luaState,-2); + LuaDLL.lua_settable(luaState,LuaIndexes.LUA_GLOBALSINDEX); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_settop(IntPtr luaState, int newTop); + // steffenj: BEGIN added lua_pop "macro" + public static void lua_pop(IntPtr luaState, int amount) + { + LuaDLL.lua_settop(luaState, -(amount) - 1); + } + // steffenj: END added lua_pop "macro" + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_insert(IntPtr luaState, int newTop); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_remove(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_gettable(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawget(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_settable(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawset(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_setmetatable(IntPtr luaState, int objIndex); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_getmetatable(IntPtr luaState, int objIndex); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_equal(IntPtr luaState, int index1, int index2); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushvalue(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_replace(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_gettop(IntPtr luaState); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern LuaTypes lua_type(IntPtr luaState, int index); + public static bool lua_isnil(IntPtr luaState, int index) + { + return (LuaDLL.lua_type(luaState,index)==LuaTypes.LUA_TNIL); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_isnumber(IntPtr luaState, int index); + public static bool lua_isboolean(IntPtr luaState, int index) + { + return LuaDLL.lua_type(luaState,index)==LuaTypes.LUA_TBOOLEAN; + } + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_ref(IntPtr luaState, int registryIndex); + public static int lua_ref(IntPtr luaState, int lockRef) + { + if(lockRef!=0) + { + return LuaDLL.luaL_ref(luaState,LuaIndexes.LUA_REGISTRYINDEX); + } + else return 0; + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawgeti(IntPtr luaState, int tableIndex, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_rawseti(IntPtr luaState, int tableIndex, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_newuserdata(IntPtr luaState, int size); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_touserdata(IntPtr luaState, int index); + public static void lua_getref(IntPtr luaState, int reference) + { + LuaDLL.lua_rawgeti(luaState,LuaIndexes.LUA_REGISTRYINDEX,reference); + } + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaL_unref(IntPtr luaState, int registryIndex, int reference); + public static void lua_unref(IntPtr luaState, int reference) + { + LuaDLL.luaL_unref(luaState,LuaIndexes.LUA_REGISTRYINDEX,reference); + } + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_isstring(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_iscfunction(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushnil(IntPtr luaState); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushstdcallcfunction(IntPtr luaState, [MarshalAs(UnmanagedType.FunctionPtr)]LuaCSFunction function); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_call(IntPtr luaState, int nArgs, int nResults); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_pcall(IntPtr luaState, int nArgs, int nResults, int errfunc); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_rawcall(IntPtr luaState, int nArgs, int nResults); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr lua_tocfunction(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern double lua_tonumber(IntPtr luaState, int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool lua_toboolean(IntPtr luaState, int index); + + [DllImport(LUADLL,CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr lua_tolstring(IntPtr luaState, int index, out int strLen); + + public static string lua_tostring(IntPtr luaState, int index) + { + int strlen; + + IntPtr str = lua_tolstring(luaState, index, out strlen); + if (str != IntPtr.Zero) + return Marshal.PtrToStringAnsi(str, strlen); + else + return null; // treat lua nulls to as C# nulls + } + + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_atpanic(IntPtr luaState, LuaFunctionCallback panicf); + + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushnumber(IntPtr luaState, double number); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushboolean(IntPtr luaState, bool value); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushlstring(IntPtr luaState, string str, int size); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushstring(IntPtr luaState, string str); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_newmetatable(IntPtr luaState, string meta); + // steffenj: BEGIN Lua 5.1.1 API change (luaL_getmetatable is now a macro using lua_getfield) + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern void lua_getfield(IntPtr luaState, int stackPos, string meta); + public static void luaL_getmetatable(IntPtr luaState, string meta) + { + LuaDLL.lua_getfield(luaState, LuaIndexes.LUA_REGISTRYINDEX, meta); + } + // steffenj: END Lua 5.1.1 API change (luaL_getmetatable is now a macro using lua_getfield) + [DllImport(LUALIBDLL, CallingConvention = CallingConvention.Cdecl)] + public static extern IntPtr luaL_checkudata(IntPtr luaState, int stackPos, string meta); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool luaL_getmetafield(IntPtr luaState, int stackPos, string field); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_load(IntPtr luaState, LuaChunkReader chunkReader, ref ReaderInfo data, string chunkName); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_loadbuffer(IntPtr luaState, string buff, int size, string name); + [DllImport(LUALIBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luaL_loadfile(IntPtr luaState, string filename); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern bool luaL_checkmetatable(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_tonetobject(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_newudata(IntPtr luaState,int val); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_rawnetobj(IntPtr luaState,int obj); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int luanet_checkudata(IntPtr luaState,int obj,string meta); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_error(IntPtr luaState); + [DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)] + public static extern bool lua_checkstack(IntPtr luaState,int extra); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern int lua_next(IntPtr luaState,int index); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void lua_pushlightuserdata(IntPtr luaState, IntPtr udata); + [DllImport(STUBDLL,CallingConvention=CallingConvention.Cdecl)] + public static extern IntPtr luanet_gettag(); + [DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)] + public static extern void luaL_where (IntPtr luaState, int level); + } +} diff --git a/LuaInterface/LuaException.cs b/LuaInterface/LuaException.cs new file mode 100644 index 0000000000..1393e6a989 --- /dev/null +++ b/LuaInterface/LuaException.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.Serialization; + +namespace LuaInterface +{ + /// + /// Exceptions thrown by the Lua runtime + /// + [Serializable] + public class LuaException : Exception + { + public LuaException() + {} + + public LuaException(string message) : base(message) + {} + + public LuaException(string message, Exception innerException) : base(message, innerException) + {} + + protected LuaException(SerializationInfo info, StreamingContext context) : base(info, context) + {} + + public override string ToString() + { + return Message; + } + } +} diff --git a/LuaInterface/LuaFunction.cs b/LuaInterface/LuaFunction.cs new file mode 100644 index 0000000000..2ef89a1ab4 --- /dev/null +++ b/LuaInterface/LuaFunction.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LuaInterface +{ + public class LuaFunction : LuaBase + { + //private Lua interpreter; + internal LuaCSFunction function; + //internal int reference; + + public LuaFunction(int reference, Lua interpreter) + { + _Reference = reference; + this.function = null; + _Interpreter = interpreter; + } + + public LuaFunction(LuaCSFunction function, Lua interpreter) + { + _Reference = 0; + this.function = function; + _Interpreter = interpreter; + } + + //~LuaFunction() + //{ + // if (reference != 0) + // interpreter.dispose(reference); + //} + + //bool disposed = false; + //~LuaFunction() + //{ + // Dispose(false); + //} + + //public void Dispose() + //{ + // Dispose(true); + // GC.SuppressFinalize(this); + //} + + //public virtual void Dispose(bool disposeManagedResources) + //{ + // if (!this.disposed) + // { + // if (disposeManagedResources) + // { + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + // } + + // disposed = true; + // } + //} + + + /* + * Calls the function casting return values to the types + * in returnTypes + */ + internal object[] call(object[] args, Type[] returnTypes) + { + return _Interpreter.callFunction(this, args, returnTypes); + } + /* + * Calls the function and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + return _Interpreter.callFunction(this, args); + } + /* + * Pushes the function into the Lua stack + */ + internal void push(IntPtr luaState) + { + if (_Reference != 0) + LuaDLL.lua_getref(luaState, _Reference); + else + _Interpreter.pushCSFunction(function); + } + public override string ToString() + { + return "function"; + } + public override bool Equals(object o) + { + if (o is LuaFunction) + { + LuaFunction l = (LuaFunction)o; + if (this._Reference != 0 && l._Reference != 0) + return _Interpreter.compareRef(l._Reference, this._Reference); + else + return this.function == l.function; + } + else return false; + } + + public override int GetHashCode() + { + if (_Reference != 0) + // elisee: Used to return _Reference + // which doesn't make sense as you can have different refs + // to the same function + return 0; + else + return function.GetHashCode(); + } + } + +} diff --git a/LuaInterface/LuaGlobalAttribute.cs b/LuaInterface/LuaGlobalAttribute.cs new file mode 100644 index 0000000000..f444f10ec1 --- /dev/null +++ b/LuaInterface/LuaGlobalAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace LuaInterface +{ + /// + /// Marks a method for global usage in Lua scripts + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LuaGlobalAttribute : Attribute + { + /// + /// An alternative name to use for calling the function in Lua - leave empty for CLR name + /// + public string Name { get; set; } + + /// + /// A description of the function + /// + public string Description { get; set; } + } +} diff --git a/LuaInterface/LuaHideAttribute.cs b/LuaInterface/LuaHideAttribute.cs new file mode 100644 index 0000000000..854f5de78b --- /dev/null +++ b/LuaInterface/LuaHideAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace LuaInterface +{ + /// + /// Marks a method, field or property to be hidden from Lua auto-completion + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class LuaHideAttribute : Attribute + {} +} diff --git a/LuaInterface/LuaInterface.csproj b/LuaInterface/LuaInterface.csproj new file mode 100644 index 0000000000..9098fd212a --- /dev/null +++ b/LuaInterface/LuaInterface.csproj @@ -0,0 +1,47 @@ + + + + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1} + v3.5 + Client + + + ..\ + + + Library + + + + + + + + + + + + + + + + + + + + + + + + + + + + {bdaeab25-991e-46a7-af1e-4f0e03358daa} + OpenRA.FileFormats + + + + + + \ No newline at end of file diff --git a/LuaInterface/LuaRegistrationHelper.cs b/LuaInterface/LuaRegistrationHelper.cs new file mode 100644 index 0000000000..9e3fc0fa5a --- /dev/null +++ b/LuaInterface/LuaRegistrationHelper.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +namespace LuaInterface +{ + public static class LuaRegistrationHelper + { + #region Tagged instance methods + /// + /// Registers all public instance methods in an object tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The object to get the methods from + public static void TaggedInstanceMethods(Lua lua, object o) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + if (o == null) throw new ArgumentNullException("o"); + #endregion + + foreach (MethodInfo method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, o, method); // CLR name + else + lua.RegisterFunction(attribute.Name, o, method); // Custom name + } + } + } + #endregion + + #region Tagged static methods + /// + /// Registers all public static methods in a class tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The class type to get the methods from + public static void TaggedStaticMethods(Lua lua, Type type) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + if (type == null) throw new ArgumentNullException("type"); + if (!type.IsClass) throw new ArgumentException("The type must be a class!", "type"); + #endregion + + foreach (MethodInfo method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, null, method); // CLR name + else + lua.RegisterFunction(attribute.Name, null, method); // Custom name + } + } + } + #endregion + + #region Enumeration + /// + /// Registers an enumeration's values for usage as a Lua variable table + /// + /// The enum type to register + /// The Lua VM to add the enum to + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type parameter is used to select an enum type")] + public static void Enumeration(Lua lua) + { + #region Sanity checks + if (lua == null) throw new ArgumentNullException("lua"); + #endregion + + Type type = typeof(T); + if (!type.IsEnum) throw new ArgumentException("The type must be an enumeration!"); + + string[] names = Enum.GetNames(type); + T[] values = (T[])Enum.GetValues(type); + + lua.NewTable(type.Name); + for (int i = 0; i < names.Length; i++) + { + string path = type.Name + "." + names[i]; + lua[path] = values[i]; + } + } + #endregion + } +} diff --git a/LuaInterface/LuaScriptException.cs b/LuaInterface/LuaScriptException.cs new file mode 100644 index 0000000000..6b45a0ca7e --- /dev/null +++ b/LuaInterface/LuaScriptException.cs @@ -0,0 +1,50 @@ +using System; + +namespace LuaInterface +{ + /// + /// Exceptions thrown by the Lua runtime because of errors in the script + /// + public class LuaScriptException : LuaException + { + /// + /// Returns true if the exception has occured as the result of a .NET exception in user code + /// + public bool IsNetException { get; private set; } + + private readonly string source; + + /// + /// The position in the script where the exception was triggered. + /// + public override string Source { get { return source; } } + + /// + /// Creates a new Lua-only exception. + /// + /// The message that describes the error. + /// The position in the script where the exception was triggered. + public LuaScriptException(string message, string source) : base(message) + { + this.source = source; + } + + /// + /// Creates a new .NET wrapping exception. + /// + /// The .NET exception triggered by user-code. + /// The position in the script where the exception was triggered. + public LuaScriptException(Exception innerException, string source) + : base(innerException.Message, innerException) + { + this.source = source; + this.IsNetException = true; + } + + public override string ToString() + { + // Prepend the error source + return GetType().FullName + ": " + source + Message; + } + } +} diff --git a/LuaInterface/LuaTable.cs b/LuaInterface/LuaTable.cs new file mode 100644 index 0000000000..d9fb67c47d --- /dev/null +++ b/LuaInterface/LuaTable.cs @@ -0,0 +1,145 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Collections; + +namespace LuaInterface +{ + /* + * Wrapper class for Lua tables + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaTable : LuaBase + { + public bool IsOrphaned; + + //internal int _Reference; + //private Lua _Interpreter; + public LuaTable(int reference, Lua interpreter) + { + _Reference = reference; + _Interpreter = interpreter; + } + + //bool disposed = false; + //~LuaTable() + //{ + // Dispose(false); + //} + + //public void Dispose() + //{ + // Dispose(true); + // GC.SuppressFinalize(this); + //} + + //public virtual void Dispose(bool disposeManagedResources) + //{ + // if (!this.disposed) + // { + // if (disposeManagedResources) + // { + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + // } + + // disposed = true; + // } + //} + //~LuaTable() + //{ + // _Interpreter.dispose(_Reference); + //} + /* + * Indexer for string fields of the table + */ + public object this[string field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Indexer for numeric fields of the table + */ + public object this[object field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + + + public System.Collections.IDictionaryEnumerator GetEnumerator() + { + return _Interpreter.GetTableDict(this).GetEnumerator(); + } + + public ICollection Keys + { + get { return _Interpreter.GetTableDict(this).Keys; } + } + + public ICollection Values + { + get { return _Interpreter.GetTableDict(this).Values; } + } + + /* + * Gets an string fields of a table ignoring its metatable, + * if it exists + */ + internal object rawget(string field) + { + return _Interpreter.rawGetObject(_Reference, field); + } + + internal object rawgetFunction(string field) + { + object obj = _Interpreter.rawGetObject(_Reference, field); + + if (obj is LuaCSFunction) + return new LuaFunction((LuaCSFunction)obj, _Interpreter); + else + return obj; + } + + /* + * Pushes this table into the Lua stack + */ + internal void push(IntPtr luaState) + { + LuaDLL.lua_getref(luaState, _Reference); + } + public override string ToString() + { + return "table"; + } + + //public override bool Equals(object o) + //{ + // if (o is LuaTable) + // { + // LuaTable l = (LuaTable)o; + // return _Interpreter.compareRef(l._Reference, _Reference); + // } + // else return false; + //} + //public override int GetHashCode() + //{ + // return _Reference; + //} + } +} diff --git a/LuaInterface/LuaUserData.cs b/LuaInterface/LuaUserData.cs new file mode 100644 index 0000000000..17b3e75505 --- /dev/null +++ b/LuaInterface/LuaUserData.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LuaInterface +{ + public class LuaUserData : LuaBase + { + //internal int _Reference; + //private Lua _Interpreter; + public LuaUserData(int reference, Lua interpreter) + { + _Reference = reference; + _Interpreter = interpreter; + } + //~LuaUserData() + //{ + // if (_Reference != 0) + // _Interpreter.dispose(_Reference); + //} + /* + * Indexer for string fields of the userdata + */ + public object this[string field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Indexer for numeric fields of the userdata + */ + public object this[object field] + { + get + { + return _Interpreter.getObject(_Reference, field); + } + set + { + _Interpreter.setObject(_Reference, field, value); + } + } + /* + * Calls the userdata and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + return _Interpreter.callFunction(this, args); + } + /* + * Pushes the userdata into the Lua stack + */ + internal void push(IntPtr luaState) + { + LuaDLL.lua_getref(luaState, _Reference); + } + public override string ToString() + { + return "userdata"; + } + //public override bool Equals(object o) + //{ + // if (o is LuaUserData) + // { + // LuaUserData l = (LuaUserData)o; + // return _Interpreter.compareRef(l._Reference, _Reference); + // } + // else return false; + //} + //public override int GetHashCode() + //{ + // return _Reference; + //} + } +} diff --git a/LuaInterface/Metatables.cs b/LuaInterface/Metatables.cs new file mode 100644 index 0000000000..630ea6791e --- /dev/null +++ b/LuaInterface/Metatables.cs @@ -0,0 +1,935 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Diagnostics; + using System.Collections.Generic; + using System.Runtime.InteropServices; + + /* + * Functions used in the metatables of userdata representing + * CLR objects + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class MetaFunctions + { + /* + * __index metafunction for CLR objects. Implemented in Lua. + */ + internal static string luaIndexFunction = + @" + local function index(obj,name) + local meta=getmetatable(obj) + local cached=meta.cache[name] + if cached then + return cached + else + local value,isFunc = get_object_member(obj,name) + if value==nil and type(isFunc)=='string' then error(isFunc,2) end + if isFunc then + meta.cache[name]=value + end + return value + end + end + return index"; + + private ObjectTranslator translator; + private Hashtable memberCache = new Hashtable(); + internal LuaCSFunction gcFunction, indexFunction, newindexFunction, + baseIndexFunction, classIndexFunction, classNewindexFunction, + execDelegateFunction, callConstructorFunction, toStringFunction; + + public MetaFunctions(ObjectTranslator translator) + { + this.translator = translator; + gcFunction = new LuaCSFunction(this.collectObject); + toStringFunction = new LuaCSFunction(this.toString); + indexFunction = new LuaCSFunction(this.getMethod); + newindexFunction = new LuaCSFunction(this.setFieldOrProperty); + baseIndexFunction = new LuaCSFunction(this.getBaseMethod); + callConstructorFunction = new LuaCSFunction(this.callConstructor); + classIndexFunction = new LuaCSFunction(this.getClassMethod); + classNewindexFunction = new LuaCSFunction(this.setClassFieldOrProperty); + execDelegateFunction = new LuaCSFunction(this.runFunctionDelegate); + } + + /* + * __call metafunction of CLR delegates, retrieves and calls the delegate. + */ + private int runFunctionDelegate(IntPtr luaState) + { + LuaCSFunction func = (LuaCSFunction)translator.getRawNetObject(luaState, 1); + LuaDLL.lua_remove(luaState, 1); + return func(luaState); + } + /* + * __gc metafunction of CLR objects. + */ + private int collectObject(IntPtr luaState) + { + int udata = LuaDLL.luanet_rawnetobj(luaState, 1); + if (udata != -1) + { + translator.collectObject(udata); + } + else + { + // Debug.WriteLine("not found: " + udata); + } + return 0; + } + /* + * __tostring metafunction of CLR objects. + */ + private int toString(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj != null) + { + translator.push(luaState, obj.ToString() + ": " + obj.GetHashCode()); + } + else LuaDLL.lua_pushnil(luaState); + return 1; + } + + + /// + /// Debug tool to dump the lua stack + /// + /// FIXME, move somewhere else + public static void dumpStack(ObjectTranslator translator, IntPtr luaState) + { + int depth = LuaDLL.lua_gettop(luaState); + + Debug.WriteLine("lua stack depth: " + depth); + for (int i = 1; i <= depth; i++) + { + LuaTypes type = LuaDLL.lua_type(luaState, i); + // we dump stacks when deep in calls, calling typename while the stack is in flux can fail sometimes, so manually check for key types + string typestr = (type == LuaTypes.LUA_TTABLE) ? "table" : LuaDLL.lua_typename(luaState, type); + + string strrep = LuaDLL.lua_tostring(luaState, i); + if (type == LuaTypes.LUA_TUSERDATA) + { + object obj = translator.getRawNetObject(luaState, i); + strrep = obj.ToString(); + } + + Debug.Print("{0}: ({1}) {2}", i, typestr, strrep); + } + } + + /* + * Called by the __index metafunction of CLR objects in case the + * method is not cached or it is a field/property/event. + * Receives the object and the member name as arguments and returns + * either the value of the member or a delegate to call it. + * If the member does not exist returns nil. + */ + private int getMethod(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null) + { + translator.throwError(luaState, "trying to index an invalid object reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + object index = translator.getObject(luaState, 2); + + string methodName = index as string; // will be null if not a string arg + Type objType = obj.GetType(); + + // Handle the most common case, looking up the method by name. + + // CP: This will fail when using indexers and attempting to get a value with the same name as a property of the object, + // ie: xmlelement['item'] <- item is a property of xmlelement + try + { + if (methodName != null && isMemberPresent(objType, methodName)) + return getMember(luaState, objType, obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + } + catch { } + bool failed = true; + + // Try to access by array if the type is right and index is an int (lua numbers always come across as double) + if (objType.IsArray && index is double) + { + int intIndex = (int)((double)index); + Array aa = obj as Array; + if (intIndex >= aa.Length) { + return translator.pushError(luaState,"array index out of bounds: "+intIndex + " " + aa.Length); + } + object val = aa.GetValue(intIndex); + translator.push (luaState,val); + failed = false; + } + else + { + // Try to use get_Item to index into this .net object + //MethodInfo getter = objType.GetMethod("get_Item"); + // issue here is that there may be multiple indexers.. + MethodInfo[] methods = objType.GetMethods(); + + foreach (MethodInfo mInfo in methods) + { + if (mInfo.Name == "get_Item") + { + //check if the signature matches the input + if (mInfo.GetParameters().Length == 1) + { + MethodInfo getter = mInfo; + ParameterInfo[] actualParms = (getter != null) ? getter.GetParameters() : null; + if (actualParms == null || actualParms.Length != 1) + { + return translator.pushError(luaState, "method not found (or no indexer): " + index); + } + else + { + // Get the index in a form acceptable to the getter + index = translator.getAsType(luaState, 2, actualParms[0].ParameterType); + // Just call the indexer - if out of bounds an exception will happen + try + { + object result = getter.Invoke(obj, new object[]{index}); + translator.push(luaState, result); + failed = false; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + return translator.pushError(luaState, "key '" + index + "' not found "); + else + return translator.pushError(luaState, "exception indexing '" + index + "' " + e.Message); + + + } + } + } + } + } + + + } + if (failed) { + return translator.pushError(luaState,"cannot find " + index); + } + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + + + /* + * __index metafunction of base classes (the base field of Lua tables). + * Adds a prefix to the method name to call the base version of the method. + */ + private int getBaseMethod(IntPtr luaState) + { + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null) + { + translator.throwError(luaState, "trying to index an invalid object reference"); + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + string methodName = LuaDLL.lua_tostring(luaState, 2); + if (methodName == null) + { + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + getMember(luaState, obj.GetType(), obj, "__luaInterface_base_" + methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + LuaDLL.lua_settop(luaState, -2); + if (LuaDLL.lua_type(luaState, -1) == LuaTypes.LUA_TNIL) + { + LuaDLL.lua_settop(luaState, -2); + return getMember(luaState, obj.GetType(), obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase); + } + LuaDLL.lua_pushboolean(luaState, false); + return 2; + } + + + /// + /// Does this method exist as either an instance or static? + /// + /// + /// + /// + bool isMemberPresent(IReflect objType, string methodName) + { + object cachedMember = checkMemberCache(memberCache, objType, methodName); + + if (cachedMember != null) + return true; + + //CP: Removed NonPublic binding search + MemberInfo[] members = objType.GetMember(methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase/* | BindingFlags.NonPublic*/); + return (members.Length > 0); + } + + /* + * Pushes the value of a member or a delegate to call it, depending on the type of + * the member. Works with static or instance members. + * Uses reflection to find members, and stores the reflected MemberInfo object in + * a cache (indexed by the type of the object and the name of the member). + */ + private int getMember(IntPtr luaState, IReflect objType, object obj, string methodName, BindingFlags bindingType) + { + bool implicitStatic = false; + MemberInfo member = null; + object cachedMember = checkMemberCache(memberCache, objType, methodName); + //object cachedMember=null; + if (cachedMember is LuaCSFunction) + { + translator.pushFunction(luaState, (LuaCSFunction)cachedMember); + translator.push(luaState, true); + return 2; + } + else if (cachedMember != null) + { + member = (MemberInfo)cachedMember; + } + else + { + //CP: Removed NonPublic binding search + MemberInfo[] members = objType.GetMember(methodName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + if (members.Length > 0) + member = members[0]; + else + { + // If we can't find any suitable instance members, try to find them as statics - but we only want to allow implicit static + // lookups for fields/properties/events -kevinh + //CP: Removed NonPublic binding search and made case insensitive + members = objType.GetMember(methodName, bindingType | BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + + if (members.Length > 0) + { + member = members[0]; + implicitStatic = true; + } + } + } + if (member != null) + { + if (member.MemberType == MemberTypes.Field) + { + FieldInfo field = (FieldInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + try + { + translator.push(luaState, field.GetValue(obj)); + } + catch + { + LuaDLL.lua_pushnil(luaState); + } + } + else if (member.MemberType == MemberTypes.Property) + { + PropertyInfo property = (PropertyInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + try + { + object val = property.GetValue(obj, null); + + translator.push(luaState, val); + } + catch (ArgumentException) + { + // If we can't find the getter in our class, recurse up to the base class and see + // if they can help. + + if (objType is Type && !(((Type)objType) == typeof(object))) + return getMember(luaState, ((Type)objType).BaseType, obj, methodName, bindingType); + else + LuaDLL.lua_pushnil(luaState); + } + catch (TargetInvocationException e) // Convert this exception into a Lua error + { + ThrowError(luaState, e); + LuaDLL.lua_pushnil(luaState); + } + } + else if (member.MemberType == MemberTypes.Event) + { + EventInfo eventInfo = (EventInfo)member; + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + translator.push(luaState, new RegisterEventHandler(translator.pendingEvents, obj, eventInfo)); + } + else if (!implicitStatic) + { + if (member.MemberType == MemberTypes.NestedType) + { + // kevinh - added support for finding nested types + + // cache us + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member); + + // Find the name of our class + string name = member.Name; + Type dectype = member.DeclaringType; + + // Build a new long name and try to find the type by name + string longname = dectype.FullName + "+" + name; + Type nestedType = translator.FindType(longname); + + translator.pushType(luaState, nestedType); + } + else + { + // Member type must be 'method' + LuaCSFunction wrapper = new LuaCSFunction((new LuaMethodWrapper(translator, objType, methodName, bindingType)).call); + + if (cachedMember == null) setMemberCache(memberCache, objType, methodName, wrapper); + translator.pushFunction(luaState, wrapper); + translator.push(luaState, true); + return 2; + } + } + else + { + // If we reach this point we found a static method, but can't use it in this context because the user passed in an instance + translator.throwError(luaState, "can't pass instance to static method " + methodName); + + LuaDLL.lua_pushnil(luaState); + } + } + else + { + // kevinh - we want to throw an exception because meerly returning 'nil' in this case + // is not sufficient. valid data members may return nil and therefore there must be some + // way to know the member just doesn't exist. + + translator.throwError(luaState, "unknown member name " + methodName); + + LuaDLL.lua_pushnil(luaState); + } + + // push false because we are NOT returning a function (see luaIndexFunction) + translator.push(luaState, false); + return 2; + } + /* + * Checks if a MemberInfo object is cached, returning it or null. + */ + private object checkMemberCache(Hashtable memberCache, IReflect objType, string memberName) + { + Hashtable members = (Hashtable)memberCache[objType]; + if (members != null) + return members[memberName]; + else + return null; + } + /* + * Stores a MemberInfo object in the member cache. + */ + private void setMemberCache(Hashtable memberCache, IReflect objType, string memberName, object member) + { + Hashtable members = (Hashtable)memberCache[objType]; + if (members == null) + { + members = new Hashtable(); + memberCache[objType] = members; + } + members[memberName] = member; + } + /* + * __newindex metafunction of CLR objects. Receives the object, + * the member name and the value to be stored as arguments. Throws + * and error if the assignment is invalid. + */ + private int setFieldOrProperty(IntPtr luaState) + { + object target = translator.getRawNetObject(luaState, 1); + if (target == null) + { + translator.throwError(luaState, "trying to index and invalid object reference"); + return 0; + } + Type type = target.GetType(); + + // First try to look up the parameter as a property name + string detailMessage; + bool didMember = trySetMember(luaState, type, target, BindingFlags.Instance | BindingFlags.IgnoreCase, out detailMessage); + + if (didMember) + return 0; // Must have found the property name + + // We didn't find a property name, now see if we can use a [] style this accessor to set array contents + try + { + if (type.IsArray && LuaDLL.lua_isnumber(luaState, 2)) + { + int index = (int)LuaDLL.lua_tonumber(luaState, 2); + + Array arr = (Array)target; + object val = translator.getAsType(luaState, 3, arr.GetType().GetElementType()); + arr.SetValue(val, index); + } + else + { + // Try to see if we have a this[] accessor + MethodInfo setter = type.GetMethod("set_Item"); + if (setter != null) + { + ParameterInfo[] args = setter.GetParameters(); + Type valueType = args[1].ParameterType; + + // The new val ue the user specified + object val = translator.getAsType(luaState, 3, valueType); + + Type indexType = args[0].ParameterType; + object index = translator.getAsType(luaState, 2, indexType); + + object[] methodArgs = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + methodArgs[0] = index; + methodArgs[1] = val; + + setter.Invoke(target, methodArgs); + } + else + { + translator.throwError(luaState, detailMessage); // Pass the original message from trySetMember because it is probably best + } + } + } + catch (SEHException) + { + // If we are seeing a C++ exception - this must actually be for Lua's private use. Let it handle it + throw; + } + catch (Exception e) + { + ThrowError(luaState, e); + } + return 0; + } + + /// + /// Tries to set a named property or field + /// + /// + /// + /// + /// + /// false if unable to find the named member, true for success + private bool trySetMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType, out string detailMessage) + { + detailMessage = null; // No error yet + + // If not already a string just return - we don't want to call tostring - which has the side effect of + // changing the lua typecode to string + // Note: We don't use isstring because the standard lua C isstring considers either strings or numbers to + // be true for isstring. + if (LuaDLL.lua_type(luaState, 2) != LuaTypes.LUA_TSTRING) + { + detailMessage = "property names must be strings"; + return false; + } + + // We only look up property names by string + string fieldName = LuaDLL.lua_tostring(luaState, 2); + if (fieldName == null || fieldName.Length < 1 || !(char.IsLetter(fieldName[0]) || fieldName[0] == '_')) + { + detailMessage = "invalid property name"; + return false; + } + + // Find our member via reflection or the cache + MemberInfo member = (MemberInfo)checkMemberCache(memberCache, targetType, fieldName); + if (member == null) + { + //CP: Removed NonPublic binding search and made case insensitive + MemberInfo[] members = targetType.GetMember(fieldName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/); + if (members.Length > 0) + { + member = members[0]; + setMemberCache(memberCache, targetType, fieldName, member); + } + else + { + detailMessage = "field or property '" + fieldName + "' does not exist"; + return false; + } + } + + if (member.MemberType == MemberTypes.Field) + { + FieldInfo field = (FieldInfo)member; + object val = translator.getAsType(luaState, 3, field.FieldType); + try + { + field.SetValue(target, val); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + // We did a call + return true; + } + else if (member.MemberType == MemberTypes.Property) + { + PropertyInfo property = (PropertyInfo)member; + object val = translator.getAsType(luaState, 3, property.PropertyType); + try + { + property.SetValue(target, val, null); + } + catch (Exception e) + { + ThrowError(luaState, e); + } + // We did a call + return true; + } + + detailMessage = "'" + fieldName + "' is not a .net field or property"; + return false; + } + + + /* + * Writes to fields or properties, either static or instance. Throws an error + * if the operation is invalid. + */ + private int setMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType) + { + string detail; + bool success = trySetMember(luaState, targetType, target, bindingType, out detail); + + if (!success) + translator.throwError(luaState, detail); + + return 0; + } + + /// + /// Convert a C# exception into a Lua error + /// + /// + /// We try to look into the exception to give the most meaningful description + void ThrowError(IntPtr luaState, Exception e) + { + // If we got inside a reflection show what really happened + TargetInvocationException te = e as TargetInvocationException; + + if (te != null) + e = te.InnerException; + + translator.throwError(luaState, e); + } + + /* + * __index metafunction of type references, works on static members. + */ + private int getClassMethod(IntPtr luaState) + { + IReflect klass; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to index an invalid type reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + else klass = (IReflect)obj; + if (LuaDLL.lua_isnumber(luaState, 2)) + { + int size = (int)LuaDLL.lua_tonumber(luaState, 2); + translator.push(luaState, Array.CreateInstance(klass.UnderlyingSystemType, size)); + return 1; + } + else + { + string methodName = LuaDLL.lua_tostring(luaState, 2); + if (methodName == null) + { + LuaDLL.lua_pushnil(luaState); + return 1; + } //CP: Ignore case + else return getMember(luaState, klass, null, methodName, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase); + } + } + /* + * __newindex function of type references, works on static members. + */ + private int setClassFieldOrProperty(IntPtr luaState) + { + IReflect target; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to index an invalid type reference"); + return 0; + } + else target = (IReflect)obj; + return setMember(luaState, target, null, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase); + } + /* + * __call metafunction of type references. Searches for and calls + * a constructor for the type. Returns nil if the constructor is not + * found or if the arguments are invalid. Throws an error if the constructor + * generates an exception. + */ + private int callConstructor(IntPtr luaState) + { + MethodCache validConstructor = new MethodCache(); + IReflect klass; + object obj = translator.getRawNetObject(luaState, 1); + if (obj == null || !(obj is IReflect)) + { + translator.throwError(luaState, "trying to call constructor on an invalid type reference"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + else klass = (IReflect)obj; + LuaDLL.lua_remove(luaState, 1); + ConstructorInfo[] constructors = klass.UnderlyingSystemType.GetConstructors(); + foreach (ConstructorInfo constructor in constructors) + { + bool isConstructor = matchParameters(luaState, constructor, ref validConstructor); + if (isConstructor) + { + try + { + translator.push(luaState, constructor.Invoke(validConstructor.args)); + } + catch (TargetInvocationException e) + { + ThrowError(luaState, e); + LuaDLL.lua_pushnil(luaState); + } + catch + { + LuaDLL.lua_pushnil(luaState); + } + return 1; + } + } + + string constructorName = (constructors.Length == 0) ? "unknown" : constructors[0].Name; + + translator.throwError(luaState, String.Format("{0} does not contain constructor({1}) argument match", + klass.UnderlyingSystemType, + constructorName)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + private static bool IsInteger(double x) { + return Math.Ceiling(x) == x; + } + + + internal Array TableToArray(object luaParamValue, Type paramArrayType) { + Array paramArray; + + if (luaParamValue is LuaTable) { + LuaTable table = (LuaTable)luaParamValue; + IDictionaryEnumerator tableEnumerator = table.GetEnumerator(); + tableEnumerator.Reset(); + paramArray = Array.CreateInstance(paramArrayType, table.Values.Count); + + int paramArrayIndex = 0; + + while(tableEnumerator.MoveNext()) { + object o = tableEnumerator.Value; + if (paramArrayType == typeof(object)) { + if (o != null && o.GetType() == typeof(double) && IsInteger((double)o)) + o = Convert.ToInt32((double)o); + } + paramArray.SetValue(Convert.ChangeType(o, paramArrayType), paramArrayIndex); + paramArrayIndex++; + } + } else { + paramArray = Array.CreateInstance(paramArrayType, 1); + paramArray.SetValue(luaParamValue, 0); + } + + return paramArray; + + } + + /* + * Matches a method against its arguments in the Lua stack. Returns + * if the match was succesful. It it was also returns the information + * necessary to invoke the method. + */ + internal bool matchParameters(IntPtr luaState, MethodBase method, ref MethodCache methodCache) + { + ExtractValue extractValue; + bool isMethod = true; + ParameterInfo[] paramInfo = method.GetParameters(); + int currentLuaParam = 1; + int nLuaParams = LuaDLL.lua_gettop(luaState); + ArrayList paramList = new ArrayList(); + List outList = new List(); + List argTypes = new List(); + foreach (ParameterInfo currentNetParam in paramInfo) + { + if (!currentNetParam.IsIn && currentNetParam.IsOut) // Skips out params + { + outList.Add(paramList.Add(null)); + } + else if (currentLuaParam > nLuaParams) // Adds optional parameters + { + if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + } + else + { + isMethod = false; + break; + } + } + else if (_IsTypeCorrect(luaState, currentLuaParam, currentNetParam, out extractValue)) // Type checking + { + int index = paramList.Add(extractValue(luaState, currentLuaParam)); + + MethodArgs methodArg = new MethodArgs(); + methodArg.index = index; + methodArg.extractValue = extractValue; + argTypes.Add(methodArg); + + if (currentNetParam.ParameterType.IsByRef) + outList.Add(index); + currentLuaParam++; + } // Type does not match, ignore if the parameter is optional + else if (_IsParamsArray(luaState, currentLuaParam, currentNetParam, out extractValue)) + { + object luaParamValue = extractValue(luaState, currentLuaParam); + Type paramArrayType = currentNetParam.ParameterType.GetElementType(); + + Array paramArray = TableToArray(luaParamValue, paramArrayType); + int index = paramList.Add(paramArray); + + MethodArgs methodArg = new MethodArgs(); + methodArg.index = index; + methodArg.extractValue = extractValue; + methodArg.isParamsArray = true; + methodArg.paramsArrayType = paramArrayType; + argTypes.Add(methodArg); + + currentLuaParam++; + } + else if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + } + else // No match + { + isMethod = false; + break; + } + } + if (currentLuaParam != nLuaParams + 1) // Number of parameters does not match + isMethod = false; + if (isMethod) + { + methodCache.args = paramList.ToArray(); + methodCache.cachedMethod = method; + methodCache.outList = outList.ToArray(); + methodCache.argTypes = argTypes.ToArray(); + } + return isMethod; + } + + /// + /// CP: Fix for operator overloading failure + /// Returns true if the type is set and assigns the extract value + /// + /// + /// + /// + /// + /// + private bool _IsTypeCorrect(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + try + { + return (extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, currentNetParam.ParameterType)) != null; + } + catch + { + extractValue = null; + Debug.WriteLine("Type wasn't correct"); + return false; + } + } + + private bool _IsParamsArray(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = null; + + if (currentNetParam.GetCustomAttributes(typeof(ParamArrayAttribute), false).Length > 0) + { + LuaTypes luaType; + + try + { + luaType = LuaDLL.lua_type(luaState, currentLuaParam); + } + catch (Exception ex) + { + Debug.WriteLine("Could not retrieve lua type while attempting to determine params Array Status."+ ex.ToString()); + Debug.WriteLine(ex.Message); + extractValue = null; + return false; + } + + if (luaType == LuaTypes.LUA_TTABLE) + { + try + { + extractValue = translator.typeChecker.getExtractor(typeof(LuaTable)); + } + catch (Exception ex) + { + Debug.WriteLine("An error occurred during an attempt to retrieve a LuaTable extractor while checking for params array status." + ex.ToString()); + } + + if (extractValue != null) + { + return true; + } + } + else + { + Type paramElementType = currentNetParam.ParameterType.GetElementType(); + + try + { + extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, paramElementType); + } + catch (Exception ex) + { + Debug.WriteLine(string.Format("An error occurred during an attempt to retrieve an extractor ({0}) while checking for params array status:{1}", paramElementType.FullName,ex.ToString())); + } + + if (extractValue != null) + { + return true; + } + } + } + + Debug.WriteLine("Type wasn't Params object."); + + return false; + } + } +} diff --git a/LuaInterface/MethodWrapper.cs b/LuaInterface/MethodWrapper.cs new file mode 100644 index 0000000000..f572420729 --- /dev/null +++ b/LuaInterface/MethodWrapper.cs @@ -0,0 +1,595 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Collections.Generic; + using System.Diagnostics; + + /* + * Cached method + */ + struct MethodCache + { + private MethodBase _cachedMethod; + + public MethodBase cachedMethod + { + get + { + return _cachedMethod; + } + set + { + _cachedMethod = value; + MethodInfo mi = value as MethodInfo; + if (mi != null) + { + //SJD this is guaranteed to be correct irrespective of actual name used for type.. + IsReturnVoid = mi.ReturnType == typeof(void); + } + } + } + + public bool IsReturnVoid; + + // List or arguments + public object[] args; + // Positions of out parameters + public int[] outList; + // Types of parameters + public MethodArgs[] argTypes; + } + + /* + * Parameter information + */ + struct MethodArgs + { + // Position of parameter + public int index; + // Type-conversion function + public ExtractValue extractValue; + + public bool isParamsArray; + + public Type paramsArrayType; + } + + /* + * Argument extraction with type-conversion function + */ + delegate object ExtractValue(IntPtr luaState, int stackPos); + + /* + * Wrapper class for methods/constructors accessed from Lua. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class LuaMethodWrapper + { + private ObjectTranslator _Translator; + private MethodBase _Method; + private MethodCache _LastCalledMethod = new MethodCache(); + private string _MethodName; + private MemberInfo[] _Members; + private ExtractValue _ExtractTarget; + private object _Target; + private BindingFlags _BindingType; + + /* + * Constructs the wrapper for a known MethodBase instance + */ + public LuaMethodWrapper(ObjectTranslator translator, object target, IReflect targetType, MethodBase method) + { + _Translator = translator; + _Target = target; + if (targetType != null) + _ExtractTarget = translator.typeChecker.getExtractor(targetType); + _Method = method; + _MethodName = method.Name; + + if (method.IsStatic) + { _BindingType = BindingFlags.Static; } + else + { _BindingType = BindingFlags.Instance; } + } + /* + * Constructs the wrapper for a known method name + */ + public LuaMethodWrapper(ObjectTranslator translator, IReflect targetType, string methodName, BindingFlags bindingType) + { + _Translator = translator; + _MethodName = methodName; + + if (targetType != null) + _ExtractTarget = translator.typeChecker.getExtractor(targetType); + + _BindingType = bindingType; + + //CP: Removed NonPublic binding search and added IgnoreCase + _Members = targetType.UnderlyingSystemType.GetMember(methodName, MemberTypes.Method, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*|BindingFlags.NonPublic*/); + } + + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + int SetPendingException(Exception e) + { + return _Translator.interpreter.SetPendingException(e); + } + + private static bool IsInteger(double x) { + return Math.Ceiling(x) == x; + } + + + /* + * Calls the method. Receives the arguments from the Lua stack + * and returns values in it. + */ + public int call(IntPtr luaState) + { + MethodBase methodToCall = _Method; + object targetObject = _Target; + bool failedCall = true; + int nReturnValues = 0; + + if (!LuaDLL.lua_checkstack(luaState, 5)) + throw new LuaException("Lua stack overflow"); + + bool isStatic = (_BindingType & BindingFlags.Static) == BindingFlags.Static; + + SetPendingException(null); + + if (methodToCall == null) // Method from name + { + if (isStatic) + targetObject = null; + else + targetObject = _ExtractTarget(luaState, 1); + + //LuaDLL.lua_remove(luaState,1); // Pops the receiver + if (_LastCalledMethod.cachedMethod != null) // Cached? + { + int numStackToSkip = isStatic ? 0 : 1; // If this is an instance invoe we will have an extra arg on the stack for the targetObject + int numArgsPassed = LuaDLL.lua_gettop(luaState) - numStackToSkip; + MethodBase method = _LastCalledMethod.cachedMethod; + + if (numArgsPassed == _LastCalledMethod.argTypes.Length) // No. of args match? + { + if (!LuaDLL.lua_checkstack(luaState, _LastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + object[] args = _LastCalledMethod.args; + + try + { + for (int i = 0; i < _LastCalledMethod.argTypes.Length; i++) + { + MethodArgs type = _LastCalledMethod.argTypes[i]; + object luaParamValue = type.extractValue(luaState, i + 1 + numStackToSkip); + if (_LastCalledMethod.argTypes[i].isParamsArray) + { + args[type.index] = _Translator.tableToArray(luaParamValue,type.paramsArrayType); + } + else + { + args[type.index] = luaParamValue; + } + + if (args[type.index] == null && + !LuaDLL.lua_isnil(luaState, i + 1 + numStackToSkip)) + { + throw new LuaException("argument number " + (i + 1) + " is invalid"); + } + } + if ((_BindingType & BindingFlags.Static) == BindingFlags.Static) + { + _Translator.push(luaState, method.Invoke(null, args)); + } + else + { + if (_LastCalledMethod.cachedMethod.IsConstructor) + _Translator.push(luaState, ((ConstructorInfo)method).Invoke(args)); + else + _Translator.push(luaState, method.Invoke(targetObject,args)); + } + failedCall = false; + } + catch (TargetInvocationException e) + { + // Failure of method invocation + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + if (_Members.Length == 1) // Is the method overloaded? + // No, throw error + return SetPendingException(e); + } + } + } + + // Cache miss + if (failedCall) + { + // System.Diagnostics.Debug.WriteLine("cache miss on " + methodName); + + // If we are running an instance variable, we can now pop the targetObject from the stack + if (!isStatic) + { + if (targetObject == null) + { + _Translator.throwError(luaState, String.Format("instance method '{0}' requires a non null target object", _MethodName)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + + LuaDLL.lua_remove(luaState, 1); // Pops the receiver + } + + bool hasMatch = false; + string candidateName = null; + + foreach (MemberInfo member in _Members) + { + candidateName = member.ReflectedType.Name + "." + member.Name; + + MethodBase m = (MethodInfo)member; + + bool isMethod = _Translator.matchParameters(luaState, m, ref _LastCalledMethod); + if (isMethod) + { + hasMatch = true; + break; + } + } + if (!hasMatch) + { + string msg = (candidateName == null) + ? "invalid arguments to method call: " + _MethodName + : ("invalid arguments to method: " + candidateName); + + _Translator.throwError(luaState, msg); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + } + else // Method from MethodBase instance + { + if (methodToCall.ContainsGenericParameters) + { + // bool isMethod = //* not used + _Translator.matchParameters(luaState, methodToCall, ref _LastCalledMethod); + + if (methodToCall.IsGenericMethodDefinition) + { + //need to make a concrete type of the generic method definition + List typeArgs = new List(); + + foreach (object arg in _LastCalledMethod.args) + typeArgs.Add(arg.GetType()); + + MethodInfo concreteMethod = (methodToCall as MethodInfo).MakeGenericMethod(typeArgs.ToArray()); + + _Translator.push(luaState, concreteMethod.Invoke(targetObject, _LastCalledMethod.args)); + failedCall = false; + } + else if (methodToCall.ContainsGenericParameters) + { + _Translator.throwError(luaState, "unable to invoke method on generic class as the current method is an open generic method"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + else + { + if (!methodToCall.IsStatic && !methodToCall.IsConstructor && targetObject == null) + { + targetObject = _ExtractTarget(luaState, 1); + LuaDLL.lua_remove(luaState, 1); // Pops the receiver + } + + if (!_Translator.matchParameters(luaState, methodToCall, ref _LastCalledMethod)) + { + _Translator.throwError(luaState, string.Format("invalid arguments to method call {0} of type {1}", methodToCall, methodToCall.ReflectedType)); + LuaDLL.lua_pushnil(luaState); + return 1; + } + } + } + + if (failedCall) + { + if (!LuaDLL.lua_checkstack(luaState, _LastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + try + { + if (isStatic) + { + _Translator.push(luaState, _LastCalledMethod.cachedMethod.Invoke(null, _LastCalledMethod.args)); + } + else + { + if (_LastCalledMethod.cachedMethod.IsConstructor) + _Translator.push(luaState, ((ConstructorInfo)_LastCalledMethod.cachedMethod).Invoke(_LastCalledMethod.args)); + else + { + object returnValue = _LastCalledMethod.cachedMethod.Invoke( targetObject, _LastCalledMethod.args ); + _Translator.push(luaState, returnValue ); + + LuaTable returnValueLuaBase = returnValue as LuaTable; + if( returnValueLuaBase != null && returnValueLuaBase.IsOrphaned ) + { + returnValueLuaBase.Dispose(); + } + } + } + } + catch (TargetInvocationException e) + { + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + return SetPendingException(e); + } + } + + // Pushes out and ref return values + for (int index = 0; index < _LastCalledMethod.outList.Length; index++) + { + nReturnValues++; + + object outArg = _LastCalledMethod.args[_LastCalledMethod.outList[index]]; + + _Translator.push(luaState, outArg ); + + LuaTable outArgLuaBase = outArg as LuaTable; + if( outArgLuaBase != null && outArgLuaBase.IsOrphaned ) + { + outArgLuaBase.Dispose(); + } + } + + //by isSingle 2010-09-10 11:26:31 + //Desc: + // if not return void,we need add 1, + // or we will lost the function's return value + // when call dotnet function like "int foo(arg1,out arg2,out arg3)" in lua code + if (!_LastCalledMethod.IsReturnVoid && nReturnValues > 0) + { + nReturnValues++; + } + + return nReturnValues < 1 ? 1 : nReturnValues; + } + } + + + + + /// + /// We keep track of what delegates we have auto attached to an event - to allow us to cleanly exit a LuaInterface session + /// + class EventHandlerContainer : IDisposable + { + Dictionary dict = new Dictionary(); + + public void Add(Delegate handler, RegisterEventHandler eventInfo) + { + dict.Add(handler, eventInfo); + } + + public void Remove(Delegate handler) + { + bool found = dict.Remove(handler); + Debug.Assert(found); + } + + /// + /// Remove any still registered handlers + /// + public void Dispose() + { + foreach (KeyValuePair pair in dict) + { + pair.Value.RemovePending(pair.Key); + } + + dict.Clear(); + } + } + + + /* + * Wrapper class for events that does registration/deregistration + * of event handlers. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + class RegisterEventHandler + { + object target; + EventInfo eventInfo; + EventHandlerContainer pendingEvents; + + public RegisterEventHandler(EventHandlerContainer pendingEvents, object target, EventInfo eventInfo) + { + this.target = target; + this.eventInfo = eventInfo; + this.pendingEvents = pendingEvents; + } + + + /* + * Adds a new event handler + */ + public Delegate Add(LuaFunction function) + { + //CP: Fix by Ben Bryant for event handling with one parameter + //link: http://luaforge.net/forum/message.php?msg_id=9266 + Delegate handlerDelegate = CodeGeneration.Instance.GetDelegate(eventInfo.EventHandlerType, function); + eventInfo.AddEventHandler(target, handlerDelegate); + pendingEvents.Add(handlerDelegate, this); + + return handlerDelegate; + + + //MethodInfo mi = eventInfo.EventHandlerType.GetMethod("Invoke"); + //ParameterInfo[] pi = mi.GetParameters(); + //LuaEventHandler handler=CodeGeneration.Instance.GetEvent(pi[1].ParameterType,function); + + //Delegate handlerDelegate=Delegate.CreateDelegate(eventInfo.EventHandlerType,handler,"HandleEvent"); + //eventInfo.AddEventHandler(target,handlerDelegate); + //pendingEvents.Add(handlerDelegate, this); + + //return handlerDelegate; + } + + /* + * Removes an existing event handler + */ + public void Remove(Delegate handlerDelegate) + { + RemovePending(handlerDelegate); + pendingEvents.Remove(handlerDelegate); + } + + /* + * Removes an existing event handler (without updating the pending handlers list) + */ + internal void RemovePending(Delegate handlerDelegate) + { + eventInfo.RemoveEventHandler(target, handlerDelegate); + } + } + + /* + * Base wrapper class for Lua function event handlers. + * Subclasses that do actual event handling are created + * at runtime. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaEventHandler + { + public LuaFunction handler = null; + + // CP: Fix provided by Ben Bryant for delegates with one param + // link: http://luaforge.net/forum/message.php?msg_id=9318 + public void handleEvent(object[] args) + { + handler.Call(args); + } + //public void handleEvent(object sender,object data) + //{ + // handler.call(new object[] { sender,data },new Type[0]); + //} + } + + /* + * Wrapper class for Lua functions as delegates + * Subclasses with correct signatures are created + * at runtime. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaDelegate + { + public Type[] returnTypes; + public LuaFunction function; + public LuaDelegate() + { + function = null; + returnTypes = null; + } + public object callFunction(object[] args, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.call(inArgs, returnTypes); + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + // Sets the value of out and ref parameters (from + // the values returned by the Lua function). + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + return returnValue; + } + } + + /* + * Static helper methods for Lua tables acting as CLR objects. + * + * Author: Fabio Mascarenhas + * Version: 1.0 + */ + public class LuaClassHelper + { + /* + * Gets the function called name from the provided table, + * returning null if it does not exist + */ + public static LuaFunction getTableFunction(LuaTable luaTable, string name) + { + object funcObj = luaTable.rawget(name); + if (funcObj is LuaFunction) + return (LuaFunction)funcObj; + else + return null; + } + /* + * Calls the provided function with the provided parameters + */ + public static object callFunction(LuaFunction function, object[] args, Type[] returnTypes, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.call(inArgs, returnTypes); + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + return returnValue; + } + } +} diff --git a/LuaInterface/ObjectTranslator.cs b/LuaInterface/ObjectTranslator.cs new file mode 100644 index 0000000000..a28c73e236 --- /dev/null +++ b/LuaInterface/ObjectTranslator.cs @@ -0,0 +1,870 @@ +namespace LuaInterface +{ + using System; + using System.IO; + using System.Collections; + using System.Reflection; + using System.Collections.Generic; + using System.Diagnostics; + + /* + * 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 LuaCSFunction getMethodSigFunction, getConstructorSigFunction, ctypeFunction, enumFromIntFunction; + + internal EventHandlerContainer pendingEvents = new EventHandlerContainer(); + + public ObjectTranslator(Lua interpreter,IntPtr luaState) + { + this.interpreter = interpreter; + typeChecker = new CheckType(this); + metaFunctions = new MetaFunctions(this); + assemblies = new List(); + + getMethodSigFunction= getMethodSignature; + getConstructorSigFunction= getConstructorSignature; + + ctypeFunction = ctype; + enumFromIntFunction = enumFromInt; + + 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(IntPtr luaState) + { + LuaDLL.lua_pushstring(luaState,"luaNet_objects"); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_pushstring(luaState,"__mode"); + LuaDLL.lua_pushstring(luaState,"v"); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_setmetatable(luaState,-2); + LuaDLL.lua_settable(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + } + /* + * Registers the indexing function of CLR objects + * passed to Lua + */ + private void createIndexingMetaFunction(IntPtr luaState) + { + LuaDLL.lua_pushstring(luaState,"luaNet_indexfunction"); + LuaDLL.luaL_dostring(luaState,MetaFunctions.luaIndexFunction); // steffenj: lua_dostring renamed to luaL_dostring + //LuaDLL.lua_pushstdcallcfunction(luaState,indexFunction); + LuaDLL.lua_rawset(luaState, (int) LuaIndexes.LUA_REGISTRYINDEX); + } + /* + * Creates the metatable for superclasses (the base + * field of registered tables) + */ + private void createBaseClassMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_searchbase"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__tostring"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.baseIndexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__newindex"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.newindexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Creates the metatable for type references + */ + private void createClassMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_class"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__tostring"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.toStringFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.classIndexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__newindex"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.classNewindexFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__call"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.callConstructorFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Registers the global functions used by LuaInterface + */ + private void setGlobalFunctions(IntPtr luaState) + { + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.indexFunction); + LuaDLL.lua_setglobal(luaState,"get_object_member"); + /*LuaDLL.lua_pushstdcallcfunction(luaState,importTypeFunction); + LuaDLL.lua_setglobal(luaState,"import_type"); + LuaDLL.lua_pushstdcallcfunction(luaState,loadAssemblyFunction); + LuaDLL.lua_setglobal(luaState,"load_assembly"); + LuaDLL.lua_pushstdcallcfunction(luaState,registerTableFunction); + LuaDLL.lua_setglobal(luaState,"make_object"); + LuaDLL.lua_pushstdcallcfunction(luaState,unregisterTableFunction); + LuaDLL.lua_setglobal(luaState,"free_object");*/ + LuaDLL.lua_pushstdcallcfunction(luaState,getMethodSigFunction); + LuaDLL.lua_setglobal(luaState,"get_method_bysig"); + LuaDLL.lua_pushstdcallcfunction(luaState,getConstructorSigFunction); + LuaDLL.lua_setglobal(luaState,"get_constructor_bysig"); + LuaDLL.lua_pushstdcallcfunction(luaState,ctypeFunction); + LuaDLL.lua_setglobal(luaState,"ctype"); + LuaDLL.lua_pushstdcallcfunction(luaState,enumFromIntFunction); + LuaDLL.lua_setglobal(luaState,"enum"); + + } + + /* + * Creates the metatable for delegates + */ + private void createFunctionMetatable(IntPtr luaState) + { + LuaDLL.luaL_newmetatable(luaState,"luaNet_function"); + LuaDLL.lua_pushstring(luaState,"__gc"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.gcFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_pushstring(luaState,"__call"); + LuaDLL.lua_pushstdcallcfunction(luaState,metaFunctions.execDelegateFunction); + LuaDLL.lua_settable(luaState,-3); + LuaDLL.lua_settop(luaState,-2); + } + /* + * Passes errors (argument e) to the Lua interpreter + */ + internal void throwError(IntPtr luaState, object e) + { + // We use this to remove anything pushed by luaL_where + int oldTop = LuaDLL.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 + LuaDLL.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); + LuaDLL.lua_error(luaState); + } + /* + * Implementation of load_assembly. Throws an error + * if the assembly is not found. + */ + private int loadAssembly(IntPtr luaState) + { + try + { + string assemblyName = LuaDLL.lua_tostring(luaState,1); + + Assembly 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(IntPtr luaState) + { + string className=LuaDLL.lua_tostring(luaState,1); + Type klass=FindType(className); + if(klass!=null) + pushType(luaState,klass); + else + LuaDLL.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(IntPtr luaState) + { + if(LuaDLL.lua_type(luaState,1)==LuaTypes.LUA_TTABLE) + { + LuaTable luaTable=getTable(luaState,1); + string superclassName = LuaDLL.lua_tostring(luaState, 2); + 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"); + LuaDLL.lua_newtable(luaState); + LuaDLL.lua_pushstring(luaState, "__index"); + LuaDLL.lua_pushvalue(luaState, -3); + LuaDLL.lua_settable(luaState, -3); + LuaDLL.lua_pushstring(luaState, "__newindex"); + LuaDLL.lua_pushvalue(luaState, -3); + LuaDLL.lua_settable(luaState, -3); + LuaDLL.lua_setmetatable(luaState, 1); + // Pushes the object again, this time as the base field + // of the table and with the luaNet_searchbase metatable + LuaDLL.lua_pushstring(luaState, "base"); + int index = addObject(obj); + pushNewObject(luaState, obj, index, "luaNet_searchbase"); + LuaDLL.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(IntPtr luaState) + { + try + { + if(LuaDLL.lua_getmetatable(luaState,1)!=0) + { + LuaDLL.lua_pushstring(luaState,"__index"); + LuaDLL.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); + LuaDLL.lua_pushnil(luaState); + LuaDLL.lua_setmetatable(luaState,1); + LuaDLL.lua_pushstring(luaState,"base"); + LuaDLL.lua_pushnil(luaState); + LuaDLL.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(IntPtr luaState) + { + IReflect klass; object target; + int udata=LuaDLL.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"); + LuaDLL.lua_pushnil(luaState); + return 1; + } + klass=target.GetType(); + } + string methodName=LuaDLL.lua_tostring(luaState,2); + Type[] signature=new Type[LuaDLL.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(IntPtr luaState,int index) + { + LuaTypes type=LuaDLL.lua_type(luaState,index); + switch(type) + { + case LuaTypes.LUA_TNUMBER: + { + return LuaDLL.lua_tonumber(luaState,index); + } + case LuaTypes.LUA_TSTRING: + { + return LuaDLL.lua_tostring(luaState,index); + } + case LuaTypes.LUA_TBOOLEAN: + { + return LuaDLL.lua_toboolean(luaState,index); + } + case LuaTypes.LUA_TTABLE: + { + return getTable(luaState,index); + } + case LuaTypes.LUA_TFUNCTION: + { + return getFunction(luaState,index); + } + case LuaTypes.LUA_TUSERDATA: + { + int udata=LuaDLL.luanet_tonetobject(luaState,index); + 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(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaTable(LuaDLL.lua_ref(luaState,1),interpreter); + } + /* + * Gets the userdata in the index positon of the Lua stack. + */ + internal LuaUserData getUserData(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaUserData(LuaDLL.lua_ref(luaState,1),interpreter); + } + /* + * Gets the function in the index positon of the Lua stack. + */ + internal LuaFunction getFunction(IntPtr luaState,int index) + { + LuaDLL.lua_pushvalue(luaState,index); + return new LuaFunction(LuaDLL.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(IntPtr luaState,int index) + { + int idx=LuaDLL.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(IntPtr luaState,int index) + { + int udata=LuaDLL.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(IntPtr luaState, object[] returnValues) + { + if(LuaDLL.lua_checkstack(luaState,returnValues.Length+5)) + { + for(int i=0;i + /// Summary description for ProxyType. + /// + public class ProxyType : IReflect + { + + Type proxy; + + public ProxyType(Type proxy) + { + this.proxy = proxy; + } + + /// + /// Provide human readable short hand for this proxy object + /// + /// + public override string ToString() + { + return "ProxyType(" + UnderlyingSystemType + ")"; + } + + + public Type UnderlyingSystemType + { + get + { + return proxy; + } + } + + public FieldInfo GetField(string name, BindingFlags bindingAttr) + { + return proxy.GetField(name, bindingAttr); + } + + public FieldInfo[] GetFields(BindingFlags bindingAttr) + { + return proxy.GetFields(bindingAttr); + } + + public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) + { + return proxy.GetMember(name, bindingAttr); + } + + public MemberInfo[] GetMembers(BindingFlags bindingAttr) + { + return proxy.GetMembers(bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr) + { + return proxy.GetMethod(name, bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers) + { + return proxy.GetMethod(name, bindingAttr, binder, types, modifiers); + } + + public MethodInfo[] GetMethods(BindingFlags bindingAttr) + { + return proxy.GetMethods(bindingAttr); + } + + public PropertyInfo GetProperty(string name, BindingFlags bindingAttr) + { + return proxy.GetProperty(name, bindingAttr); + } + + public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers) + { + return proxy.GetProperty(name, bindingAttr, binder, returnType, types, modifiers); + } + + public PropertyInfo[] GetProperties(BindingFlags bindingAttr) + { + return proxy.GetProperties(bindingAttr); + } + + public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters) + { + return proxy.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters); + } + + } +} diff --git a/OpenRA.sln b/OpenRA.sln index 2e9cbc466c..b343c4d9ec 100644 --- a/OpenRA.sln +++ b/OpenRA.sln @@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Irc", "OpenRA.Irc\Op EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenRA.Renderer.Sdl2", "OpenRA.Renderer.Sdl2\OpenRA.Renderer.Sdl2.csproj", "{33D03738-C154-4028-8EA8-63A3C488A651}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LuaInterface", "LuaInterface\LuaInterface.csproj", "{E915A0A4-2641-4F7E-8A88-8F123FA88BF1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -184,6 +186,14 @@ Global {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Any CPU.Build.0 = Release|Any CPU {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {85B48234-8B31-4BE6-AF9C-665CC6866841}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Any CPU.Build.0 = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E915A0A4-2641-4F7E-8A88-8F123FA88BF1}.Release|Mixed Platforms.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution StartupItem = OpenRA.Game\OpenRA.Game.csproj