356 lines
12 KiB
C#
356 lines
12 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
using NLua.Exceptions;
|
|
using NLua.Extensions;
|
|
|
|
using LuaState = KeraLua.Lua;
|
|
using LuaNativeFunction = KeraLua.LuaFunction;
|
|
|
|
namespace NLua.Method
|
|
{
|
|
/*
|
|
* Argument extraction with type-conversion function
|
|
*/
|
|
delegate object ExtractValue(LuaState luaState, int stackPos);
|
|
|
|
/*
|
|
* Wrapper class for methods/constructors accessed from Lua.
|
|
*
|
|
*/
|
|
class LuaMethodWrapper
|
|
{
|
|
internal LuaNativeFunction InvokeFunction;
|
|
|
|
readonly ObjectTranslator _translator;
|
|
readonly MethodBase _method;
|
|
|
|
readonly ExtractValue _extractTarget;
|
|
readonly object _target;
|
|
readonly bool _isStatic;
|
|
|
|
readonly string _methodName;
|
|
readonly MethodInfo[] _members;
|
|
|
|
private MethodCache _lastCalledMethod;
|
|
|
|
|
|
/*
|
|
* Constructs the wrapper for a known MethodBase instance
|
|
*/
|
|
public LuaMethodWrapper(ObjectTranslator translator, object target, ProxyType targetType, MethodBase method)
|
|
{
|
|
InvokeFunction = Call;
|
|
_translator = translator;
|
|
_target = target;
|
|
_extractTarget = translator.typeChecker.GetExtractor(targetType);
|
|
_lastCalledMethod = new MethodCache();
|
|
|
|
_method = method;
|
|
_methodName = method.Name;
|
|
_isStatic = method.IsStatic;
|
|
|
|
}
|
|
|
|
/*
|
|
* Constructs the wrapper for a known method name
|
|
*/
|
|
public LuaMethodWrapper(ObjectTranslator translator, ProxyType targetType, string methodName, BindingFlags bindingType)
|
|
{
|
|
InvokeFunction = Call;
|
|
|
|
_translator = translator;
|
|
_methodName = methodName;
|
|
_extractTarget = translator.typeChecker.GetExtractor(targetType);
|
|
_lastCalledMethod = new MethodCache();
|
|
|
|
_isStatic = (bindingType & BindingFlags.Static) == BindingFlags.Static;
|
|
MethodInfo [] methods = GetMethodsRecursively(targetType.UnderlyingSystemType,
|
|
methodName,
|
|
bindingType | BindingFlags.Public);
|
|
_members = ReorderMethods(methods);
|
|
}
|
|
|
|
private static MethodInfo[] ReorderMethods(MethodInfo[] m)
|
|
{
|
|
int len = m.Length;
|
|
|
|
if (len < 2)
|
|
return m;
|
|
|
|
return m.
|
|
GroupBy(c => c.GetParameters().Length).
|
|
SelectMany(g => g.OrderByDescending(ci => ci.ToString())).
|
|
ToArray();
|
|
}
|
|
|
|
MethodInfo[] GetMethodsRecursively(Type type, string methodName, BindingFlags bindingType)
|
|
{
|
|
if (type == typeof(object))
|
|
return type.GetMethods(methodName, bindingType);
|
|
|
|
var methods = type.GetMethods(methodName, bindingType);
|
|
var baseMethods = GetMethodsRecursively(type.BaseType, methodName, bindingType);
|
|
|
|
return methods.Concat(baseMethods).ToArray();
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
|
|
void FillMethodArguments(LuaState luaState, int numStackToSkip)
|
|
{
|
|
object[] args = _lastCalledMethod.args;
|
|
|
|
|
|
for (int i = 0; i < _lastCalledMethod.argTypes.Length; i++)
|
|
{
|
|
MethodArgs type = _lastCalledMethod.argTypes[i];
|
|
|
|
int index = i + 1 + numStackToSkip;
|
|
|
|
|
|
if (_lastCalledMethod.argTypes[i].IsParamsArray)
|
|
{
|
|
int count = _lastCalledMethod.argTypes.Length - i;
|
|
Array paramArray = _translator.TableToArray(luaState, type.ExtractValue, type.ParameterType, index, count);
|
|
args[_lastCalledMethod.argTypes[i].Index] = paramArray;
|
|
}
|
|
else
|
|
{
|
|
args[type.Index] = type.ExtractValue(luaState, index);
|
|
}
|
|
|
|
if (_lastCalledMethod.args[_lastCalledMethod.argTypes[i].Index] == null &&
|
|
!luaState.IsNil(i + 1 + numStackToSkip))
|
|
throw new LuaException(string.Format("Argument number {0} is invalid", (i + 1)));
|
|
}
|
|
}
|
|
|
|
int PushReturnValue(LuaState luaState)
|
|
{
|
|
int nReturnValues = 0;
|
|
// Pushes out and ref return values
|
|
for (int index = 0; index < _lastCalledMethod.outList.Length; index++)
|
|
{
|
|
nReturnValues++;
|
|
_translator.Push(luaState, _lastCalledMethod.args[_lastCalledMethod.outList[index]]);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
int CallInvoke(LuaState luaState, MethodBase method, object targetObject)
|
|
{
|
|
if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6))
|
|
throw new LuaException("Lua stack overflow");
|
|
|
|
try
|
|
{
|
|
object result;
|
|
|
|
if (method.IsConstructor)
|
|
result = ((ConstructorInfo)method).Invoke(_lastCalledMethod.args);
|
|
else
|
|
result = method.Invoke(targetObject, _lastCalledMethod.args);
|
|
|
|
_translator.Push(luaState, result);
|
|
}
|
|
catch (TargetInvocationException e)
|
|
{
|
|
// Failure of method invocation
|
|
if (_translator.interpreter.UseTraceback)
|
|
e.GetBaseException().Data["Traceback"] = _translator.interpreter.GetDebugTraceback();
|
|
return SetPendingException(e.GetBaseException());
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
return SetPendingException(e);
|
|
}
|
|
|
|
return PushReturnValue(luaState);
|
|
}
|
|
|
|
bool IsMethodCached(LuaState luaState, int numArgsPassed, int skipParams)
|
|
{
|
|
if (_lastCalledMethod.cachedMethod == null)
|
|
return false;
|
|
|
|
if (numArgsPassed != _lastCalledMethod.argTypes.Length)
|
|
return false;
|
|
|
|
// If there is no method overloads, is ok to use the cached method
|
|
if (_members.Length == 1)
|
|
return true;
|
|
|
|
return _translator.MatchParameters(luaState, _lastCalledMethod.cachedMethod, _lastCalledMethod, skipParams);
|
|
}
|
|
|
|
int CallMethodFromName(LuaState luaState)
|
|
{
|
|
object targetObject = null;
|
|
|
|
if (!_isStatic)
|
|
targetObject = _extractTarget(luaState, 1);
|
|
|
|
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 = luaState.GetTop() - numStackToSkip;
|
|
|
|
// Cached?
|
|
if (IsMethodCached(luaState, numArgsPassed, numStackToSkip))
|
|
{
|
|
MethodBase method = _lastCalledMethod.cachedMethod;
|
|
|
|
if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6))
|
|
throw new LuaException("Lua stack overflow");
|
|
|
|
FillMethodArguments(luaState, numStackToSkip);
|
|
|
|
return CallInvoke(luaState, method, targetObject);
|
|
}
|
|
|
|
// 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));
|
|
return 1;
|
|
}
|
|
|
|
luaState.Remove(1); // Pops the receiver
|
|
}
|
|
|
|
bool hasMatch = false;
|
|
string candidateName = null;
|
|
|
|
foreach (var member in _members)
|
|
{
|
|
if (member.ReflectedType == null)
|
|
continue;
|
|
|
|
candidateName = member.ReflectedType.Name + "." + member.Name;
|
|
bool isMethod = _translator.MatchParameters(luaState, member, _lastCalledMethod, 0);
|
|
|
|
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);
|
|
return 1;
|
|
}
|
|
|
|
if (_lastCalledMethod.cachedMethod.ContainsGenericParameters)
|
|
return CallInvokeOnGenericMethod(luaState, (MethodInfo)_lastCalledMethod.cachedMethod, targetObject);
|
|
|
|
return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject);
|
|
}
|
|
|
|
int CallInvokeOnGenericMethod(LuaState luaState, MethodInfo methodToCall, object targetObject)
|
|
{
|
|
//need to make a concrete type of the generic method definition
|
|
var typeArgs = new List<Type>();
|
|
|
|
ParameterInfo [] parameters = methodToCall.GetParameters();
|
|
|
|
for (int i = 0; i < parameters.Length; i++)
|
|
{
|
|
ParameterInfo parameter = parameters[i];
|
|
|
|
if (!parameter.ParameterType.IsGenericParameter)
|
|
continue;
|
|
|
|
typeArgs.Add(_lastCalledMethod.args[i].GetType());
|
|
}
|
|
|
|
MethodInfo concreteMethod = methodToCall.MakeGenericMethod(typeArgs.ToArray());
|
|
|
|
_translator.Push(luaState, concreteMethod.Invoke(targetObject, _lastCalledMethod.args));
|
|
|
|
return PushReturnValue(luaState);
|
|
}
|
|
|
|
/*
|
|
* Calls the method. Receives the arguments from the Lua stack
|
|
* and returns values in it.
|
|
*/
|
|
int Call(IntPtr state)
|
|
{
|
|
var luaState = LuaState.FromIntPtr(state);
|
|
|
|
MethodBase methodToCall = _method;
|
|
object targetObject = _target;
|
|
|
|
if (!luaState.CheckStack(5))
|
|
throw new LuaException("Lua stack overflow");
|
|
|
|
SetPendingException(null);
|
|
|
|
// Method from name
|
|
if (methodToCall == null)
|
|
return CallMethodFromName(luaState);
|
|
|
|
// Method from MethodBase instance
|
|
if (!methodToCall.ContainsGenericParameters)
|
|
{
|
|
if (!methodToCall.IsStatic && !methodToCall.IsConstructor && targetObject == null)
|
|
{
|
|
targetObject = _extractTarget(luaState, 1);
|
|
luaState.Remove(1); // Pops the receiver
|
|
}
|
|
|
|
if (!_translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0))
|
|
{
|
|
_translator.ThrowError(luaState, "Invalid arguments to method call");
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!methodToCall.IsGenericMethodDefinition)
|
|
{
|
|
_translator.ThrowError(luaState,
|
|
"Unable to invoke method on generic class as the current method is an open generic method");
|
|
return 1;
|
|
}
|
|
|
|
_translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0);
|
|
|
|
return CallInvokeOnGenericMethod(luaState, (MethodInfo) methodToCall, targetObject);
|
|
}
|
|
|
|
if (_isStatic)
|
|
targetObject = null;
|
|
|
|
return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject);
|
|
}
|
|
}
|
|
}
|