583 lines
21 KiB
C#
583 lines
21 KiB
C#
namespace NLua
|
|
{
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using NLua.Exceptions;
|
|
using Lua511;
|
|
|
|
/*
|
|
* Cached method
|
|
*/
|
|
struct MethodCache
|
|
{
|
|
private MethodBase _cachedMethod;
|
|
|
|
public MethodBase cachedMethod
|
|
{
|
|
get
|
|
{
|
|
return _cachedMethod;
|
|
}
|
|
set
|
|
{
|
|
_cachedMethod = value;
|
|
MethodInfo mi = value as MethodInfo;
|
|
if (mi != null)
|
|
{
|
|
IsReturnVoid = string.Compare(mi.ReturnType.Name, "System.Void", true) == 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 IReflect _TargetType;
|
|
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;
|
|
_TargetType = targetType;
|
|
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;
|
|
_TargetType = targetType;
|
|
|
|
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*/);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Convert C# exceptions into Lua errors
|
|
/// </summary>
|
|
/// <returns>num of things on stack</returns>
|
|
/// <param name="e">null for no pending exception</param>
|
|
int SetPendingException(Exception e)
|
|
{
|
|
return _Translator.interpreter.SetPendingException(e);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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];
|
|
|
|
int index = i + 1 + numStackToSkip;
|
|
|
|
Func<int, object> valueExtractor = (currentParam) => {
|
|
return type.extractValue (luaState, currentParam);
|
|
};
|
|
|
|
if (_LastCalledMethod.argTypes [i].isParamsArray) {
|
|
int count = index - _LastCalledMethod.argTypes.Length;
|
|
Array paramArray = _Translator.TableToArray (valueExtractor, type.paramsArrayType, index, count);
|
|
args [_LastCalledMethod.argTypes [i].index] = paramArray;
|
|
} else {
|
|
args [type.index] = valueExtractor (index);
|
|
}
|
|
|
|
if (_LastCalledMethod.args[_LastCalledMethod.argTypes[i].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, _LastCalledMethod.cachedMethod.Invoke(null, _LastCalledMethod.args));
|
|
}
|
|
else
|
|
{
|
|
if (_LastCalledMethod.cachedMethod.IsConstructor)
|
|
_Translator.push(luaState, ((ConstructorInfo)_LastCalledMethod.cachedMethod).Invoke(_LastCalledMethod.args));
|
|
else
|
|
_Translator.push(luaState, _LastCalledMethod.cachedMethod.Invoke(targetObject, _LastCalledMethod.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"
|
|
: ("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 = _Translator.matchParameters(luaState, methodToCall, ref _LastCalledMethod);
|
|
|
|
if (methodToCall.IsGenericMethodDefinition)
|
|
{
|
|
//need to make a concrete type of the generic method definition
|
|
List<Type> typeArgs = new List<Type>();
|
|
|
|
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, "invalid arguments to method call");
|
|
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
|
|
_Translator.push(luaState, _LastCalledMethod.cachedMethod.Invoke(targetObject, _LastCalledMethod.args));
|
|
}
|
|
}
|
|
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++;
|
|
//for(int i=0;i<lastCalledMethod.outList.Length;i++)
|
|
_Translator.push(luaState, _LastCalledMethod.args[_LastCalledMethod.outList[index]]);
|
|
}
|
|
|
|
//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;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// We keep track of what delegates we have auto attached to an event - to allow us to cleanly exit a LuaInterface session
|
|
/// </summary>
|
|
class EventHandlerContainer : IDisposable
|
|
{
|
|
Dictionary<Delegate, RegisterEventHandler> dict = new Dictionary<Delegate, RegisterEventHandler>();
|
|
|
|
public void Add(Delegate handler, RegisterEventHandler eventInfo)
|
|
{
|
|
dict.Add(handler, eventInfo);
|
|
}
|
|
|
|
public void Remove(Delegate handler)
|
|
{
|
|
bool found = dict.Remove(handler);
|
|
Debug.Assert(found);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove any still registered handlers
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
foreach (KeyValuePair<Delegate, RegisterEventHandler> 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;
|
|
}
|
|
}
|
|
}
|