Metatables.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955
  1. namespace LuaInterface
  2. {
  3. using System;
  4. using System.IO;
  5. using System.Collections;
  6. using System.Reflection;
  7. using System.Diagnostics;
  8. using System.Collections.Generic;
  9. using System.Runtime.InteropServices;
  10. /*
  11. * Functions used in the metatables of userdata representing
  12. * CLR objects
  13. *
  14. * Author: Fabio Mascarenhas
  15. * Version: 1.0
  16. */
  17. public class MetaFunctions
  18. {
  19. /*
  20. * __index metafunction for CLR objects. Implemented in Lua.
  21. */
  22. internal static string luaIndexFunction =
  23. @"
  24. local function index(obj,name)
  25. local meta=getmetatable(obj)
  26. local cached=meta.cache[name]
  27. if cached then
  28. return cached
  29. else
  30. local value,isFunc = get_object_member(obj,name)
  31. if value==nil and type(isFunc)=='string' then error(isFunc,2) end
  32. if isFunc then
  33. meta.cache[name]=value
  34. end
  35. return value
  36. end
  37. end
  38. return index";
  39. private ObjectTranslator translator;
  40. private Hashtable memberCache = new Hashtable();
  41. internal LuaCSFunction gcFunction, indexFunction, newindexFunction,
  42. baseIndexFunction, classIndexFunction, classNewindexFunction,
  43. execDelegateFunction, callConstructorFunction, toStringFunction;
  44. public MetaFunctions(ObjectTranslator translator)
  45. {
  46. this.translator = translator;
  47. gcFunction = new LuaCSFunction(collectObject);
  48. toStringFunction = new LuaCSFunction(toString);
  49. indexFunction = new LuaCSFunction(getMethod);
  50. newindexFunction = new LuaCSFunction(setFieldOrProperty);
  51. baseIndexFunction = new LuaCSFunction(getBaseMethod);
  52. callConstructorFunction = new LuaCSFunction(callConstructor);
  53. classIndexFunction = new LuaCSFunction(getClassMethod);
  54. classNewindexFunction = new LuaCSFunction(setClassFieldOrProperty);
  55. execDelegateFunction = new LuaCSFunction(runFunctionDelegate);
  56. }
  57. /*
  58. * __call metafunction of CLR delegates, retrieves and calls the delegate.
  59. */
  60. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  61. public static int runFunctionDelegate(IntPtr luaState)
  62. {
  63. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  64. LuaCSFunction func = (LuaCSFunction)translator.getRawNetObject(luaState, 1);
  65. LuaDLL.lua_remove(luaState, 1);
  66. return func(luaState);
  67. }
  68. /*
  69. * __gc metafunction of CLR objects.
  70. */
  71. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  72. public static int collectObject(IntPtr luaState)
  73. {
  74. int udata = LuaDLL.luanet_rawnetobj(luaState, 1);
  75. if (udata != -1)
  76. {
  77. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  78. translator.collectObject(udata);
  79. }
  80. else
  81. {
  82. // Debug.WriteLine("not found: " + udata);
  83. }
  84. return 0;
  85. }
  86. /*
  87. * __tostring metafunction of CLR objects.
  88. */
  89. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  90. public static int toString(IntPtr luaState)
  91. {
  92. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  93. object obj = translator.getRawNetObject(luaState, 1);
  94. if (obj != null)
  95. {
  96. translator.push(luaState, obj.ToString() + ": " + obj.GetHashCode());
  97. }
  98. else LuaDLL.lua_pushnil(luaState);
  99. return 1;
  100. }
  101. /// <summary>
  102. /// Debug tool to dump the lua stack
  103. /// </summary>
  104. /// FIXME, move somewhere else
  105. public static void dumpStack(ObjectTranslator translator, IntPtr luaState)
  106. {
  107. int depth = LuaDLL.lua_gettop(luaState);
  108. Debug.WriteLine("lua stack depth: " + depth);
  109. for (int i = 1; i <= depth; i++)
  110. {
  111. LuaTypes type = LuaDLL.lua_type(luaState, i);
  112. // we dump stacks when deep in calls, calling typename while the stack is in flux can fail sometimes, so manually check for key types
  113. string typestr = (type == LuaTypes.LUA_TTABLE) ? "table" : LuaDLL.lua_typename(luaState, type);
  114. string strrep = LuaDLL.lua_tostring(luaState, i);
  115. if (type == LuaTypes.LUA_TUSERDATA)
  116. {
  117. object obj = translator.getRawNetObject(luaState, i);
  118. strrep = obj.ToString();
  119. }
  120. Debug.WriteLine(String.Format("{0}: ({1}) {2}", i, typestr, strrep));
  121. }
  122. }
  123. /*
  124. * Called by the __index metafunction of CLR objects in case the
  125. * method is not cached or it is a field/property/event.
  126. * Receives the object and the member name as arguments and returns
  127. * either the value of the member or a delegate to call it.
  128. * If the member does not exist returns nil.
  129. */
  130. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  131. public static int getMethod(IntPtr luaState)
  132. {
  133. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  134. object obj = translator.getRawNetObject(luaState, 1);
  135. if (obj == null)
  136. {
  137. translator.throwError(luaState, "trying to index an invalid object reference");
  138. LuaDLL.lua_pushnil(luaState);
  139. return 1;
  140. }
  141. object index = translator.getObject(luaState, 2);
  142. //Type indexType = index.GetType(); //* not used
  143. string methodName = index as string; // will be null if not a string arg
  144. Type objType = obj.GetType();
  145. // Handle the most common case, looking up the method by name.
  146. // CP: This will fail when using indexers and attempting to get a value with the same name as a property of the object,
  147. // ie: xmlelement['item'] <- item is a property of xmlelement
  148. try
  149. {
  150. if (methodName != null && translator.metaFunctions.isMemberPresent(objType, methodName))
  151. return translator.metaFunctions.getMember(luaState, objType, obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase);
  152. }
  153. catch { }
  154. bool failed = true;
  155. // Try to access by array if the type is right and index is an int (lua numbers always come across as double)
  156. if (objType.IsArray && index is double)
  157. {
  158. int intIndex = (int)((double)index);
  159. Array aa = obj as Array;
  160. if (intIndex >= aa.Length) {
  161. return translator.pushError(luaState,"array index out of bounds: "+intIndex + " " + aa.Length);
  162. }
  163. object val = aa.GetValue(intIndex);
  164. translator.push (luaState,val);
  165. failed = false;
  166. }
  167. else
  168. {
  169. // Try to use get_Item to index into this .net object
  170. //MethodInfo getter = objType.GetMethod("get_Item");
  171. // issue here is that there may be multiple indexers..
  172. MethodInfo[] methods = objType.GetMethods();
  173. foreach (MethodInfo mInfo in methods)
  174. {
  175. if (mInfo.Name == "get_Item")
  176. {
  177. //check if the signature matches the input
  178. if (mInfo.GetParameters().Length == 1)
  179. {
  180. MethodInfo getter = mInfo;
  181. ParameterInfo[] actualParms = (getter != null) ? getter.GetParameters() : null;
  182. if (actualParms == null || actualParms.Length != 1)
  183. {
  184. return translator.pushError(luaState, "method not found (or no indexer): " + index);
  185. }
  186. else
  187. {
  188. // Get the index in a form acceptable to the getter
  189. index = translator.getAsType(luaState, 2, actualParms[0].ParameterType);
  190. // Just call the indexer - if out of bounds an exception will happen
  191. try
  192. {
  193. object result = getter.Invoke(obj, new object[]{index});
  194. translator.push(luaState, result);
  195. failed = false;
  196. }
  197. catch (TargetInvocationException e)
  198. {
  199. // Provide a more readable description for the common case of key not found
  200. if (e.InnerException is KeyNotFoundException)
  201. return translator.pushError(luaState, "key '" + index + "' not found ");
  202. else
  203. return translator.pushError(luaState, "exception indexing '" + index + "' " + e.Message);
  204. }
  205. }
  206. }
  207. }
  208. }
  209. }
  210. if (failed) {
  211. return translator.pushError(luaState,"cannot find " + index);
  212. }
  213. LuaDLL.lua_pushboolean(luaState, false);
  214. return 2;
  215. }
  216. /*
  217. * __index metafunction of base classes (the base field of Lua tables).
  218. * Adds a prefix to the method name to call the base version of the method.
  219. */
  220. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  221. public static int getBaseMethod(IntPtr luaState)
  222. {
  223. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  224. object obj = translator.getRawNetObject(luaState, 1);
  225. if (obj == null)
  226. {
  227. translator.throwError(luaState, "trying to index an invalid object reference");
  228. LuaDLL.lua_pushnil(luaState);
  229. LuaDLL.lua_pushboolean(luaState, false);
  230. return 2;
  231. }
  232. string methodName = LuaDLL.lua_tostring(luaState, 2);
  233. if (methodName == null)
  234. {
  235. LuaDLL.lua_pushnil(luaState);
  236. LuaDLL.lua_pushboolean(luaState, false);
  237. return 2;
  238. }
  239. translator.metaFunctions.getMember(luaState, obj.GetType(), obj, "__luaInterface_base_" + methodName, BindingFlags.Instance | BindingFlags.IgnoreCase);
  240. LuaDLL.lua_settop(luaState, -2);
  241. if (LuaDLL.lua_type(luaState, -1) == LuaTypes.LUA_TNIL)
  242. {
  243. LuaDLL.lua_settop(luaState, -2);
  244. return translator.metaFunctions.getMember(luaState, obj.GetType(), obj, methodName, BindingFlags.Instance | BindingFlags.IgnoreCase);
  245. }
  246. LuaDLL.lua_pushboolean(luaState, false);
  247. return 2;
  248. }
  249. /// <summary>
  250. /// Does this method exist as either an instance or static?
  251. /// </summary>
  252. /// <param name="objType"></param>
  253. /// <param name="methodName"></param>
  254. /// <returns></returns>
  255. bool isMemberPresent(IReflect objType, string methodName)
  256. {
  257. object cachedMember = checkMemberCache(memberCache, objType, methodName);
  258. if (cachedMember != null)
  259. return true;
  260. //CP: Removed NonPublic binding search
  261. MemberInfo[] members = objType.GetMember(methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase/* | BindingFlags.NonPublic*/);
  262. return (members.Length > 0);
  263. }
  264. /*
  265. * Pushes the value of a member or a delegate to call it, depending on the type of
  266. * the member. Works with static or instance members.
  267. * Uses reflection to find members, and stores the reflected MemberInfo object in
  268. * a cache (indexed by the type of the object and the name of the member).
  269. */
  270. private int getMember(IntPtr luaState, IReflect objType, object obj, string methodName, BindingFlags bindingType)
  271. {
  272. bool implicitStatic = false;
  273. MemberInfo member = null;
  274. object cachedMember = checkMemberCache(memberCache, objType, methodName);
  275. //object cachedMember=null;
  276. if (cachedMember is LuaCSFunction)
  277. {
  278. translator.pushFunction(luaState, (LuaCSFunction)cachedMember);
  279. translator.push(luaState, true);
  280. return 2;
  281. }
  282. else if (cachedMember != null)
  283. {
  284. member = (MemberInfo)cachedMember;
  285. }
  286. else
  287. {
  288. //CP: Removed NonPublic binding search
  289. MemberInfo[] members = objType.GetMember(methodName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/);
  290. if (members.Length > 0)
  291. member = members[0];
  292. else
  293. {
  294. // If we can't find any suitable instance members, try to find them as statics - but we only want to allow implicit static
  295. // lookups for fields/properties/events -kevinh
  296. //CP: Removed NonPublic binding search and made case insensitive
  297. members = objType.GetMember(methodName, bindingType | BindingFlags.Static | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/);
  298. if (members.Length > 0)
  299. {
  300. member = members[0];
  301. implicitStatic = true;
  302. }
  303. }
  304. }
  305. if (member != null)
  306. {
  307. if (member.MemberType == MemberTypes.Field)
  308. {
  309. FieldInfo field = (FieldInfo)member;
  310. if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member);
  311. try
  312. {
  313. translator.push(luaState, field.GetValue(obj));
  314. }
  315. catch
  316. {
  317. LuaDLL.lua_pushnil(luaState);
  318. }
  319. }
  320. else if (member.MemberType == MemberTypes.Property)
  321. {
  322. PropertyInfo property = (PropertyInfo)member;
  323. if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member);
  324. try
  325. {
  326. object val = property.GetValue(obj, null);
  327. translator.push(luaState, val);
  328. }
  329. catch (ArgumentException)
  330. {
  331. // If we can't find the getter in our class, recurse up to the base class and see
  332. // if they can help.
  333. if (objType is Type && !(((Type)objType) == typeof(object)))
  334. return getMember(luaState, ((Type)objType).BaseType, obj, methodName, bindingType);
  335. else
  336. LuaDLL.lua_pushnil(luaState);
  337. }
  338. catch (TargetInvocationException e) // Convert this exception into a Lua error
  339. {
  340. ThrowError(luaState, e);
  341. LuaDLL.lua_pushnil(luaState);
  342. }
  343. }
  344. else if (member.MemberType == MemberTypes.Event)
  345. {
  346. EventInfo eventInfo = (EventInfo)member;
  347. if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member);
  348. translator.push(luaState, new RegisterEventHandler(translator.pendingEvents, obj, eventInfo));
  349. }
  350. else if (!implicitStatic)
  351. {
  352. if (member.MemberType == MemberTypes.NestedType)
  353. {
  354. // kevinh - added support for finding nested types
  355. // cache us
  356. if (cachedMember == null) setMemberCache(memberCache, objType, methodName, member);
  357. // Find the name of our class
  358. string name = member.Name;
  359. Type dectype = member.DeclaringType;
  360. // Build a new long name and try to find the type by name
  361. string longname = dectype.FullName + "+" + name;
  362. Type nestedType = translator.FindType(longname);
  363. translator.pushType(luaState, nestedType);
  364. }
  365. else
  366. {
  367. // Member type must be 'method'
  368. LuaCSFunction wrapper = new LuaCSFunction((new LuaMethodWrapper(translator, objType, methodName, bindingType)).call);
  369. if (cachedMember == null) setMemberCache(memberCache, objType, methodName, wrapper);
  370. translator.pushFunction(luaState, wrapper);
  371. translator.push(luaState, true);
  372. return 2;
  373. }
  374. }
  375. else
  376. {
  377. // 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
  378. translator.throwError(luaState, "can't pass instance to static method " + methodName);
  379. LuaDLL.lua_pushnil(luaState);
  380. }
  381. }
  382. else
  383. {
  384. // kevinh - we want to throw an exception because meerly returning 'nil' in this case
  385. // is not sufficient. valid data members may return nil and therefore there must be some
  386. // way to know the member just doesn't exist.
  387. translator.throwError(luaState, "unknown member name " + methodName);
  388. LuaDLL.lua_pushnil(luaState);
  389. }
  390. // push false because we are NOT returning a function (see luaIndexFunction)
  391. translator.push(luaState, false);
  392. return 2;
  393. }
  394. /*
  395. * Checks if a MemberInfo object is cached, returning it or null.
  396. */
  397. private object checkMemberCache(Hashtable memberCache, IReflect objType, string memberName)
  398. {
  399. Hashtable members = (Hashtable)memberCache[objType];
  400. if (members != null)
  401. return members[memberName];
  402. else
  403. return null;
  404. }
  405. /*
  406. * Stores a MemberInfo object in the member cache.
  407. */
  408. private void setMemberCache(Hashtable memberCache, IReflect objType, string memberName, object member)
  409. {
  410. Hashtable members = (Hashtable)memberCache[objType];
  411. if (members == null)
  412. {
  413. members = new Hashtable();
  414. memberCache[objType] = members;
  415. }
  416. members[memberName] = member;
  417. }
  418. /*
  419. * __newindex metafunction of CLR objects. Receives the object,
  420. * the member name and the value to be stored as arguments. Throws
  421. * and error if the assignment is invalid.
  422. */
  423. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  424. public static int setFieldOrProperty(IntPtr luaState)
  425. {
  426. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  427. object target = translator.getRawNetObject(luaState, 1);
  428. if (target == null)
  429. {
  430. translator.throwError(luaState, "trying to index and invalid object reference");
  431. return 0;
  432. }
  433. Type type = target.GetType();
  434. // First try to look up the parameter as a property name
  435. string detailMessage;
  436. bool didMember = translator.metaFunctions.trySetMember(luaState, type, target, BindingFlags.Instance | BindingFlags.IgnoreCase, out detailMessage);
  437. if (didMember)
  438. return 0; // Must have found the property name
  439. // We didn't find a property name, now see if we can use a [] style this accessor to set array contents
  440. try
  441. {
  442. if (type.IsArray && LuaDLL.lua_isnumber(luaState, 2))
  443. {
  444. int index = (int)LuaDLL.lua_tonumber(luaState, 2);
  445. Array arr = (Array)target;
  446. object val = translator.getAsType(luaState, 3, arr.GetType().GetElementType());
  447. arr.SetValue(val, index);
  448. }
  449. else
  450. {
  451. // Try to see if we have a this[] accessor
  452. MethodInfo setter = type.GetMethod("set_Item");
  453. if (setter != null)
  454. {
  455. ParameterInfo[] args = setter.GetParameters();
  456. Type valueType = args[1].ParameterType;
  457. // The new val ue the user specified
  458. object val = translator.getAsType(luaState, 3, valueType);
  459. Type indexType = args[0].ParameterType;
  460. object index = translator.getAsType(luaState, 2, indexType);
  461. object[] methodArgs = new object[2];
  462. // Just call the indexer - if out of bounds an exception will happen
  463. methodArgs[0] = index;
  464. methodArgs[1] = val;
  465. setter.Invoke(target, methodArgs);
  466. }
  467. else
  468. {
  469. translator.throwError(luaState, detailMessage); // Pass the original message from trySetMember because it is probably best
  470. }
  471. }
  472. }
  473. catch (SEHException)
  474. {
  475. // If we are seeing a C++ exception - this must actually be for Lua's private use. Let it handle it
  476. throw;
  477. }
  478. catch (Exception e)
  479. {
  480. translator.metaFunctions.ThrowError(luaState, e);
  481. }
  482. return 0;
  483. }
  484. /// <summary>
  485. /// Tries to set a named property or field
  486. /// </summary>
  487. /// <param name="luaState"></param>
  488. /// <param name="targetType"></param>
  489. /// <param name="target"></param>
  490. /// <param name="bindingType"></param>
  491. /// <returns>false if unable to find the named member, true for success</returns>
  492. private bool trySetMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType, out string detailMessage)
  493. {
  494. detailMessage = null; // No error yet
  495. // If not already a string just return - we don't want to call tostring - which has the side effect of
  496. // changing the lua typecode to string
  497. // Note: We don't use isstring because the standard lua C isstring considers either strings or numbers to
  498. // be true for isstring.
  499. if (LuaDLL.lua_type(luaState, 2) != LuaTypes.LUA_TSTRING)
  500. {
  501. detailMessage = "property names must be strings";
  502. return false;
  503. }
  504. // We only look up property names by string
  505. string fieldName = LuaDLL.lua_tostring(luaState, 2);
  506. if (fieldName == null || fieldName.Length < 1 || !(char.IsLetter(fieldName[0]) || fieldName[0] == '_'))
  507. {
  508. detailMessage = "invalid property name";
  509. return false;
  510. }
  511. // Find our member via reflection or the cache
  512. MemberInfo member = (MemberInfo)checkMemberCache(memberCache, targetType, fieldName);
  513. if (member == null)
  514. {
  515. //CP: Removed NonPublic binding search and made case insensitive
  516. MemberInfo[] members = targetType.GetMember(fieldName, bindingType | BindingFlags.Public | BindingFlags.IgnoreCase/*| BindingFlags.NonPublic*/);
  517. if (members.Length > 0)
  518. {
  519. member = members[0];
  520. setMemberCache(memberCache, targetType, fieldName, member);
  521. }
  522. else
  523. {
  524. detailMessage = "field or property '" + fieldName + "' does not exist";
  525. return false;
  526. }
  527. }
  528. if (member.MemberType == MemberTypes.Field)
  529. {
  530. FieldInfo field = (FieldInfo)member;
  531. object val = translator.getAsType(luaState, 3, field.FieldType);
  532. try
  533. {
  534. field.SetValue(target, val);
  535. }
  536. catch (Exception e)
  537. {
  538. ThrowError(luaState, e);
  539. }
  540. // We did a call
  541. return true;
  542. }
  543. else if (member.MemberType == MemberTypes.Property)
  544. {
  545. PropertyInfo property = (PropertyInfo)member;
  546. object val = translator.getAsType(luaState, 3, property.PropertyType);
  547. try
  548. {
  549. property.SetValue(target, val, null);
  550. }
  551. catch (Exception e)
  552. {
  553. ThrowError(luaState, e);
  554. }
  555. // We did a call
  556. return true;
  557. }
  558. detailMessage = "'" + fieldName + "' is not a .net field or property";
  559. return false;
  560. }
  561. /*
  562. * Writes to fields or properties, either static or instance. Throws an error
  563. * if the operation is invalid.
  564. */
  565. private int setMember(IntPtr luaState, IReflect targetType, object target, BindingFlags bindingType)
  566. {
  567. string detail;
  568. bool success = trySetMember(luaState, targetType, target, bindingType, out detail);
  569. if (!success)
  570. translator.throwError(luaState, detail);
  571. return 0;
  572. }
  573. /// <summary>
  574. /// Convert a C# exception into a Lua error
  575. /// </summary>
  576. /// <param name="e"></param>
  577. /// We try to look into the exception to give the most meaningful description
  578. void ThrowError(IntPtr luaState, Exception e)
  579. {
  580. // If we got inside a reflection show what really happened
  581. TargetInvocationException te = e as TargetInvocationException;
  582. if (te != null)
  583. e = te.InnerException;
  584. translator.throwError(luaState, e);
  585. }
  586. /*
  587. * __index metafunction of type references, works on static members.
  588. */
  589. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  590. public static int getClassMethod(IntPtr luaState)
  591. {
  592. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  593. IReflect klass;
  594. object obj = translator.getRawNetObject(luaState, 1);
  595. if (obj == null || !(obj is IReflect))
  596. {
  597. translator.throwError(luaState, "trying to index an invalid type reference");
  598. LuaDLL.lua_pushnil(luaState);
  599. return 1;
  600. }
  601. else klass = (IReflect)obj;
  602. if (LuaDLL.lua_isnumber(luaState, 2))
  603. {
  604. int size = (int)LuaDLL.lua_tonumber(luaState, 2);
  605. translator.push(luaState, Array.CreateInstance(klass.UnderlyingSystemType, size));
  606. return 1;
  607. }
  608. else
  609. {
  610. string methodName = LuaDLL.lua_tostring(luaState, 2);
  611. if (methodName == null)
  612. {
  613. LuaDLL.lua_pushnil(luaState);
  614. return 1;
  615. } //CP: Ignore case
  616. else return translator.metaFunctions.getMember(luaState, klass, null, methodName, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase);
  617. }
  618. }
  619. /*
  620. * __newindex function of type references, works on static members.
  621. */
  622. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  623. public static int setClassFieldOrProperty(IntPtr luaState)
  624. {
  625. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  626. IReflect target;
  627. object obj = translator.getRawNetObject(luaState, 1);
  628. if (obj == null || !(obj is IReflect))
  629. {
  630. translator.throwError(luaState, "trying to index an invalid type reference");
  631. return 0;
  632. }
  633. else target = (IReflect)obj;
  634. return translator.metaFunctions.setMember(luaState, target, null, BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.IgnoreCase);
  635. }
  636. /*
  637. * __call metafunction of type references. Searches for and calls
  638. * a constructor for the type. Returns nil if the constructor is not
  639. * found or if the arguments are invalid. Throws an error if the constructor
  640. * generates an exception.
  641. */
  642. [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  643. public static int callConstructor(IntPtr luaState)
  644. {
  645. ObjectTranslator translator = ObjectTranslator.FromState(luaState);
  646. MethodCache validConstructor = new MethodCache();
  647. IReflect klass;
  648. object obj = translator.getRawNetObject(luaState, 1);
  649. if (obj == null || !(obj is IReflect))
  650. {
  651. translator.throwError(luaState, "trying to call constructor on an invalid type reference");
  652. LuaDLL.lua_pushnil(luaState);
  653. return 1;
  654. }
  655. else klass = (IReflect)obj;
  656. LuaDLL.lua_remove(luaState, 1);
  657. ConstructorInfo[] constructors = klass.UnderlyingSystemType.GetConstructors();
  658. foreach (ConstructorInfo constructor in constructors)
  659. {
  660. bool isConstructor = translator.metaFunctions.matchParameters(luaState, constructor, ref validConstructor);
  661. if (isConstructor)
  662. {
  663. try
  664. {
  665. translator.push(luaState, constructor.Invoke(validConstructor.args));
  666. }
  667. catch (TargetInvocationException e)
  668. {
  669. translator.metaFunctions.ThrowError(luaState, e);
  670. LuaDLL.lua_pushnil(luaState);
  671. }
  672. catch
  673. {
  674. LuaDLL.lua_pushnil(luaState);
  675. }
  676. return 1;
  677. }
  678. }
  679. string constructorName = (constructors.Length == 0) ? "unknown" : constructors[0].Name;
  680. translator.throwError(luaState, String.Format("{0} does not contain constructor({1}) argument match",
  681. klass.UnderlyingSystemType,
  682. constructorName));
  683. LuaDLL.lua_pushnil(luaState);
  684. return 1;
  685. }
  686. private static bool IsInteger(double x) {
  687. return Math.Ceiling(x) == x;
  688. }
  689. internal Array TableToArray(object luaParamValue, Type paramArrayType) {
  690. Array paramArray;
  691. if (luaParamValue is LuaTable) {
  692. LuaTable table = (LuaTable)luaParamValue;
  693. IDictionaryEnumerator tableEnumerator = table.GetEnumerator();
  694. tableEnumerator.Reset();
  695. paramArray = Array.CreateInstance(paramArrayType, table.Values.Count);
  696. int paramArrayIndex = 0;
  697. while(tableEnumerator.MoveNext()) {
  698. object o = tableEnumerator.Value;
  699. if (paramArrayType == typeof(object)) {
  700. if (o != null && o.GetType() == typeof(double) && IsInteger((double)o))
  701. o = Convert.ToInt32((double)o);
  702. }
  703. paramArray.SetValue(Convert.ChangeType(o, paramArrayType), paramArrayIndex);
  704. paramArrayIndex++;
  705. }
  706. } else {
  707. paramArray = Array.CreateInstance(paramArrayType, 1);
  708. paramArray.SetValue(luaParamValue, 0);
  709. }
  710. return paramArray;
  711. }
  712. /*
  713. * Matches a method against its arguments in the Lua stack. Returns
  714. * if the match was succesful. It it was also returns the information
  715. * necessary to invoke the method.
  716. */
  717. internal bool matchParameters(IntPtr luaState, MethodBase method, ref MethodCache methodCache)
  718. {
  719. ExtractValue extractValue;
  720. bool isMethod = true;
  721. ParameterInfo[] paramInfo = method.GetParameters();
  722. int currentLuaParam = 1;
  723. int nLuaParams = LuaDLL.lua_gettop(luaState);
  724. ArrayList paramList = new ArrayList();
  725. List<int> outList = new List<int>();
  726. List<MethodArgs> argTypes = new List<MethodArgs>();
  727. foreach (ParameterInfo currentNetParam in paramInfo)
  728. {
  729. if (!currentNetParam.IsIn && currentNetParam.IsOut) // Skips out params
  730. {
  731. outList.Add(paramList.Add(null));
  732. }
  733. else if (currentLuaParam > nLuaParams) // Adds optional parameters
  734. {
  735. if (currentNetParam.IsOptional)
  736. {
  737. paramList.Add(currentNetParam.DefaultValue);
  738. }
  739. else
  740. {
  741. isMethod = false;
  742. break;
  743. }
  744. }
  745. else if (_IsTypeCorrect(luaState, currentLuaParam, currentNetParam, out extractValue)) // Type checking
  746. {
  747. int index = paramList.Add(extractValue(luaState, currentLuaParam));
  748. MethodArgs methodArg = new MethodArgs();
  749. methodArg.index = index;
  750. methodArg.extractValue = extractValue;
  751. argTypes.Add(methodArg);
  752. if (currentNetParam.ParameterType.IsByRef)
  753. outList.Add(index);
  754. currentLuaParam++;
  755. } // Type does not match, ignore if the parameter is optional
  756. else if (_IsParamsArray(luaState, currentLuaParam, currentNetParam, out extractValue))
  757. {
  758. object luaParamValue = extractValue(luaState, currentLuaParam);
  759. Type paramArrayType = currentNetParam.ParameterType.GetElementType();
  760. Array paramArray = TableToArray(luaParamValue, paramArrayType);
  761. int index = paramList.Add(paramArray);
  762. MethodArgs methodArg = new MethodArgs();
  763. methodArg.index = index;
  764. methodArg.extractValue = extractValue;
  765. methodArg.isParamsArray = true;
  766. methodArg.paramsArrayType = paramArrayType;
  767. argTypes.Add(methodArg);
  768. currentLuaParam++;
  769. }
  770. else if (currentNetParam.IsOptional)
  771. {
  772. paramList.Add(currentNetParam.DefaultValue);
  773. }
  774. else // No match
  775. {
  776. isMethod = false;
  777. break;
  778. }
  779. }
  780. if (currentLuaParam != nLuaParams + 1) // Number of parameters does not match
  781. isMethod = false;
  782. if (isMethod)
  783. {
  784. methodCache.args = paramList.ToArray();
  785. methodCache.cachedMethod = method;
  786. methodCache.outList = outList.ToArray();
  787. methodCache.argTypes = argTypes.ToArray();
  788. }
  789. return isMethod;
  790. }
  791. /// <summary>
  792. /// CP: Fix for operator overloading failure
  793. /// Returns true if the type is set and assigns the extract value
  794. /// </summary>
  795. /// <param name="luaState"></param>
  796. /// <param name="currentLuaParam"></param>
  797. /// <param name="currentNetParam"></param>
  798. /// <param name="extractValue"></param>
  799. /// <returns></returns>
  800. private bool _IsTypeCorrect(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue)
  801. {
  802. try
  803. {
  804. return (extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, currentNetParam.ParameterType)) != null;
  805. }
  806. catch
  807. {
  808. extractValue = null;
  809. Debug.WriteLine("Type wasn't correct");
  810. return false;
  811. }
  812. }
  813. private bool _IsParamsArray(IntPtr luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue)
  814. {
  815. extractValue = null;
  816. if (currentNetParam.GetCustomAttributes(typeof(ParamArrayAttribute), false).Length > 0)
  817. {
  818. LuaTypes luaType;
  819. try
  820. {
  821. luaType = LuaDLL.lua_type(luaState, currentLuaParam);
  822. }
  823. catch (Exception ex)
  824. {
  825. Debug.WriteLine("Could not retrieve lua type while attempting to determine params Array Status."+ ex.ToString());
  826. Debug.WriteLine(ex.Message);
  827. extractValue = null;
  828. return false;
  829. }
  830. if (luaType == LuaTypes.LUA_TTABLE)
  831. {
  832. try
  833. {
  834. extractValue = translator.typeChecker.getExtractor(typeof(LuaTable));
  835. }
  836. catch (Exception ex)
  837. {
  838. Debug.WriteLine("An error occurred during an attempt to retrieve a LuaTable extractor while checking for params array status." + ex.ToString());
  839. }
  840. if (extractValue != null)
  841. {
  842. return true;
  843. }
  844. }
  845. else
  846. {
  847. Type paramElementType = currentNetParam.ParameterType.GetElementType();
  848. try
  849. {
  850. extractValue = translator.typeChecker.checkType(luaState, currentLuaParam, paramElementType);
  851. }
  852. catch (Exception ex)
  853. {
  854. 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()));
  855. }
  856. if (extractValue != null)
  857. {
  858. return true;
  859. }
  860. }
  861. }
  862. Debug.WriteLine("Type wasn't Params object.");
  863. return false;
  864. }
  865. }
  866. }