tolua源码之小白剖析(一)
前言
用了一段时间tolua了,但对于tolua是怎么做到lua层和C#层进行交互的却知之甚少,所以趁自己的小游戏项目审核期间的这段剩余时间,对tolua的源码做一些研究,来帮助日后对于tolua项目的代码的有更好的整体把控整体把控。
tolua c
跟市面上大量的分析C#层Api不太一样,我打算先从C层的api入手,因为毕竟Lua虚拟机是在C层跑的,先分析好C层的代码,我们对于代码是怎么运行能有更深刻的底层了解。而分析代码的最好步骤,就是模仿着代码自己重新实现一遍流程。那我们就先开始实现一遍tolua c层的代码吧:
引入Lua源码
首先在Lua官网下载Lua源码:https://www.lua.org/ftp/
我这里下载的是Lua5.1版本的源码,下载完成后会得到一个这样的文件夹:

src文件夹目录中就是Lua源码。
然后我们新建VS的C++工程:

将lua-5.1引入到新建工程目录下:

右键项目->属性->C/C++->常规->附加包含目录中添加:.\lua-5.1\src

在右侧头文件(右键->添加->现有项)中引入src目录中所有的.h文件,在源文件(右键->添加->现有项)中引入src目录中的所有.c文件(注意:不用引入lua.c和luac.c,他们分别对应的是luac编译工具的启动函数和lua运行工具的启动函数。我们不需要):

右键项目 → 属性 → C/C++ → 预处理器 → 预处理器定义,添加:_CRT_SECURE_NO_WARNINGS,不然编译会报错

在源文件目录下新建一个tolua.c文件:

在tolua.c同级目录下新增文件HelloWorld.lua文件:

运行后,lua源码我们成功跑起来了(HelloWorld.lua里面就一句 print("Hello World") 不演示了):

参考资料:
编译tolua.dll
成功验证lua虚拟机运行成功后,我们就可以将lua源码编译成dll到Unity中运行。但是光光lua源码是不够格的,因为我们的目标是使得Unity端Api可以和lua进行交互,所以,我们这里可以首先下载一下tolua_runtime源码,下载完成后我们会得到这样一个文件夹:

将int64.c tolua.c uint64.c拉进源文件目录,将tolua.h拉进头文件目录,注意,拉进来后,可能会有几个报错,但都是小问题,各位自己想办法处理下吧,这里就不演示了。
之后右键项目添加下宏定义 LUA_BUILD_AS_DLL:

在将配置类型改成动态库(dll):

最后在luaconf.h文件中,按照我的注释,注释一下,并添加上#define LUA_API __declspec(dllexport),不然导出会报错

然后我们就可以生成dll文件了,编译成功后,可以在sln同级目录下找到对应的生成文件夹

在Unity端通过虚拟机的方式输出Hello tolua
新建Unity项目工程,将之前生成好的dll文件丢进来,并实现LuaState代码:
using System;
using System.Text;
using AOT;
using UnityEngine;
using UnityEngine.Windows;
namespace ToLua.Core
{
public class LuaState
{
protected IntPtr L;
private static LuaState mainState = null;
public LuaState()
{
if (mainState == null)
{
mainState = this;
}
L = LuaDLL.luaL_newstate();
LuaException.Init(L);
LuaDLL.tolua_openlibs(L);
LuaDLL.tolua_pushcfunction(L, Print);
LuaDLL.lua_setglobal(L, "print");
LuaSetTop(0);
}
public void DoString(string chunk, string chunkName)
{
byte[] buffer = Encoding.UTF8.GetBytes(chunk);
LuaLoadBuffer(buffer, chunkName);
}
protected void LuaLoadBuffer(byte[] buffer, string chunkName)
{
LuaDLL.tolua_pushtraceback(L);
int oldTop = LuaGetTop();
if (LuaLoadBuffer(buffer, buffer.Length, chunkName) == 0)
{
if (LuaPCall(0, LuaDLL.LUA_MULTRET, oldTop) == 0)
{
LuaSetTop(oldTop - 1);
return;
}
}
string err = LuaDLL.lua_tostring(L, -1);
LuaSetTop(oldTop - 1);
throw new LuaException(err, LuaException.GetLastError());
}
public int LuaLoadBuffer(byte[] buff, int size, string name)
{
return LuaDLL.luaL_loadbuffer(L, buff, size, name);
}
public int LuaPCall(int nArgs, int nResults, int errfunc)
{
return LuaDLL.lua_pcall(L, nArgs, nResults, errfunc);
}
public int LuaGetTop()
{
return LuaDLL.lua_gettop(L);
}
public void LuaSetTop(int newTop)
{
LuaDLL.lua_settop(L, newTop);
}
[MonoPInvokeCallback(typeof(LuaDLL.LuaCSFunction))]
static int Print(IntPtr L)
{
try
{
int n = LuaDLL.lua_gettop(L);
using (CString.Block())
{
CString sb = CString.Alloc(256);
#if UNITY_EDITOR
int line = LuaDLL.tolua_where(L, 1);
string filename = LuaDLL.lua_tostring(L, -1);
LuaDLL.lua_settop(L, n);
int offset = filename[0] == '@' ? 1 : 0;
if (!filename.Contains("."))
{
sb.Append('[').Append(filename, offset, filename.Length - offset).Append(".lua:").Append(line).Append("]:");
}
else
{
sb.Append('[').Append(filename, offset, filename.Length - offset).Append(':').Append(line).Append("]:");
}
#endif
for (int i = 1; i <= n; i++)
{
if (i > 1) sb.Append(" ");
if (LuaDLL.lua_isstring(L, i) == 1)
{
sb.Append(LuaDLL.lua_tostring(L, i));
}
else if (LuaDLL.lua_isnil(L, i))
{
sb.Append("nil");
}
else if (LuaDLL.lua_isboolean(L, i))
{
sb.Append(LuaDLL.lua_toboolean(L, i) ? "true" : "false");
}
else
{
IntPtr p = LuaDLL.lua_topointer(L, i);
if (p == IntPtr.Zero)
{
sb.Append("nil");
}
else
{
sb.Append(LuaDLL.luaL_typename(L, i)).Append(":0x").Append(p.ToString("X"));
}
}
}
Debug.Log(sb.ToString());
}
return 0;
}
catch (Exception e)
{
return LuaDLL.toluaL_exception(L, e);
}
}
}
}
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace ToLua.Core
{
public class LuaDLL
{
const string LUADLL = "tolua";
public static int LUA_MULTRET = -1;
public static string[] LuaTypeName = { "none", "nil", "boolean", "lightuserdata", "number", "string", "table", "function", "userdata", "thread" };
public class LuaIndexes
{
public static int LUA_REGISTRYINDEX = -10000;
public static int LUA_ENVIRONINDEX = -10001;
public static int LUA_GLOBALSINDEX = -10002;
}
public enum LuaTypes
{
LUA_TNONE = -1,
LUA_TNIL = 0,
LUA_TBOOLEAN = 1,
LUA_TLIGHTUSERDATA = 2,
LUA_TNUMBER = 3,
LUA_TSTRING = 4,
LUA_TTABLE = 5,
LUA_TFUNCTION = 6,
LUA_TUSERDATA = 7,
LUA_TTHREAD = 8,
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int LuaCSFunction(IntPtr luaState);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr luaL_newstate();
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void tolua_openlibs(IntPtr L);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_getfield(IntPtr L, int idx, string key);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr tolua_tolstring(IntPtr luaState, int index, out int strLen);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_pushcfunction(IntPtr L, IntPtr fn);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_objlen(IntPtr luaState, int stackPos);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void lua_rawgeti(IntPtr luaState, int idx, int n);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void lua_rawseti(IntPtr luaState, int tableIndex, int index); //[-1, +0, m]
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void lua_settop(IntPtr luaState, int top);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_loadbuffer(IntPtr luaState, byte[] buff, int size, string name);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_error(IntPtr L, string msg);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int tolua_setfield(IntPtr L, int idx, string key);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void tolua_pushtraceback(IntPtr L);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int lua_gettop(IntPtr luaState);
[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 tolua_where(IntPtr L, int level);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int lua_isstring(IntPtr luaState, int index);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern LuaTypes lua_type(IntPtr luaState, int index);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
public static extern int tolua_toboolean(IntPtr luaState, int index);
[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr lua_topointer(IntPtr L, int idx);
public static int tolua_pushcfunction(IntPtr luaState, LuaCSFunction func)
{
IntPtr fn = Marshal.GetFunctionPointerForDelegate(func);
tolua_pushcfunction(luaState, fn);
return 0;
}
public static void lua_getglobal(IntPtr luaState, string name)
{
lua_getfield(luaState, LuaIndexes.LUA_GLOBALSINDEX, name);
}
public static void lua_getfield(IntPtr L, int idx, string key)
{
if (tolua_getfield(L, idx, key) != 0)
{
string error = lua_tostring(L, -1);
throw new LuaException(error);
}
}
public static string lua_tostring(IntPtr luaState, int index)
{
int len = 0;
IntPtr str = tolua_tolstring(luaState, index, out len);
if (str != IntPtr.Zero)
{
return lua_ptrtostring(str, len);
}
return null;
}
public static string lua_ptrtostring(IntPtr str, int len)
{
string ss = Marshal.PtrToStringAnsi(str, len);
if (ss == null)
{
byte[] buffer = new byte[len];
Marshal.Copy(str, buffer, 0, len);
return Encoding.UTF8.GetString(buffer);
}
return ss;
}
public static int lua_objlen(IntPtr luaState, int idx)
{
return tolua_objlen(luaState, idx);
}
public static void lua_pop(IntPtr luaState, int amount)
{
lua_settop(luaState, -(amount) - 1);
}
public static int luaL_loadbuffer(IntPtr luaState, byte[] buff, int size, string name)
{
return tolua_loadbuffer(luaState, buff, size, name);
}
public static int toluaL_exception(IntPtr L, Exception e)
{
LuaException.luaStack = new LuaException(e.Message, e, 2);
return tolua_error(L, e.Message);
}
public static void lua_setglobal(IntPtr luaState, string name)
{
lua_setfield(luaState, LuaIndexes.LUA_GLOBALSINDEX, name);
}
public static void lua_setfield(IntPtr L, int idx, string key)
{
if (tolua_setfield(L, idx, key) != 0)
{
string error = LuaDLL.lua_tostring(L, -1);
throw new LuaException(error);
}
}
public static bool lua_isnil(IntPtr luaState, int n)
{
return (lua_type(luaState, n) == LuaTypes.LUA_TNIL);
}
public static bool lua_isboolean(IntPtr luaState, int n)
{
LuaTypes type = lua_type(luaState, n);
return type == LuaTypes.LUA_TBOOLEAN || type == LuaTypes.LUA_TNIL;
}
public static bool lua_toboolean(IntPtr luaState, int idx)
{
return tolua_toboolean(luaState, idx) == 1;
}
public static string luaL_typename(IntPtr luaState, int stackPos)
{
LuaTypes type = LuaDLL.lua_type(luaState, stackPos);
return lua_typename(luaState, type);
}
public static string lua_typename(IntPtr luaState, LuaTypes type)
{
int t = (int)type;
return LuaTypeName[t + 1];
}
}
}
using System;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace ToLua.Core
{
public class LuaException : Exception
{
public static Exception luaStack = null;
public static string projectFolder = null;
public static int InstantiateCount = 0;
public static int SendMsgCount = 0;
public static IntPtr L = IntPtr.Zero;
public override string StackTrace
{
get
{
return _stack;
}
}
protected string _stack = string.Empty;
public LuaException(string msg, Exception e = null, int skip = 1)
: base(msg)
{
if (e != null)
{
if (e is LuaException)
{
_stack = e.StackTrace;
}
else
{
StackTrace trace = new StackTrace(e, true);
StringBuilder sb = new StringBuilder();
ExtractFormattedStackTrace(trace, sb);
StackTrace self = new StackTrace(skip, true);
ExtractFormattedStackTrace(self, sb, trace);
_stack = sb.ToString();
}
}
else
{
StackTrace self = new StackTrace(skip, true);
StringBuilder sb = new StringBuilder();
ExtractFormattedStackTrace(self, sb);
_stack = sb.ToString();
}
}
public static Exception GetLastError()
{
Exception last = luaStack;
luaStack = null;
return last;
}
public static void ExtractFormattedStackTrace(StackTrace trace, StringBuilder sb, StackTrace skip = null)
{
int begin = 0;
if (skip != null && skip.FrameCount > 0)
{
MethodBase m0 = skip.GetFrame(skip.FrameCount - 1).GetMethod();
for (int i = 0; i < trace.FrameCount; i++)
{
StackFrame frame = trace.GetFrame(i);
MethodBase method = frame.GetMethod();
if (method == m0)
{
begin = i + 1;
break;
}
}
sb.Append("").Append("\r\n");
}
for (int i = begin; i < trace.FrameCount; i++)
{
StackFrame frame = trace.GetFrame(i);
MethodBase method = frame.GetMethod();
if (method == null || method.DeclaringType == null)
{
continue;
}
Type declaringType = method.DeclaringType;
string str = declaringType.Namespace;
if ( (InstantiateCount == 0 && declaringType == typeof(UnityEngine.Object) && method.Name == "Instantiate") //(method.Name == "Internal_CloneSingle"
|| (SendMsgCount == 0 && declaringType == typeof(GameObject) && method.Name == "SendMessage"))
{
break;
}
if ((str != null) && (str.Length != 0))
{
sb.Append(str);
sb.Append(".");
}
sb.Append(declaringType.Name);
sb.Append(":");
sb.Append(method.Name);
sb.Append("(");
int index = 0;
ParameterInfo[] parameters = method.GetParameters();
bool flag = true;
while (index < parameters.Length)
{
if (!flag)
{
sb.Append(", ");
}
else
{
flag = false;
}
sb.Append(parameters[index].ParameterType.Name);
index++;
}
sb.Append(")");
string fileName = frame.GetFileName();
if (fileName != null)
{
fileName = fileName.Replace('\\', '/');
sb.Append(" (at ");
if (fileName.StartsWith(projectFolder))
{
fileName = fileName.Substring(projectFolder.Length, fileName.Length - projectFolder.Length);
}
sb.Append(fileName);
sb.Append(":");
sb.Append(frame.GetFileLineNumber().ToString());
sb.Append(")");
}
if (i != trace.FrameCount - 1)
{
sb.Append("\n");
}
}
}
public static void Init(IntPtr L0)
{
L = L0;
Type type = typeof(StackTraceUtility);
FieldInfo field = type.GetField("projectFolder", BindingFlags.Static | BindingFlags.GetField | BindingFlags.NonPublic);
LuaException.projectFolder = (string)field.GetValue(null);
projectFolder = projectFolder.Replace('\\', '/');
#if DEVELOPER
Debugger.Log("projectFolder is {0}", projectFolder);
#endif
}
}
}
using System.Collections;
using System.Collections.Generic;
using ToLua.Core;
using UnityEngine;
public class LuaStartup : MonoBehaviour
{
void Start()
{
LuaState lua = new LuaState();
string hello =
@"
print('hello tolua#')
";
lua.DoString(hello, "LuaStartup.cs");
}
}
注意,我们还需要添加一个CString.dll文件,这个大家从tolua copy过来就好。然后将LuaStartup挂载好运行,成功输出:

至此,所有基本运行流程我们差不多是搞明白了,这篇文章到此结束,接下来会在其他文章继续深入分析tolua源码。

浙公网安备 33010602011771号