ToLua框架下C#与Lua代码的互调操作

Lua是目前国内使用最多的热更语言,基于Lua的热更框架也非常多,最近学习了一下ToLua的热更框架,主要使用的问题在于C#和Lua之间的互调,因此做一下学习记录以备后查。

所谓“互调”,当然要包括两个方面,一是通过C#调用Lua代码,二是通过Lua代码调用C#脚本,第二点还包括注册在C#脚本里的Unity物体。

1. ToLua的简单实现原理

ToLua框架主要是通过静态绑定来实现C#与Lua之间的交互的,基本原理是通过建立一个Lua虚拟机来映射C#脚本,然后再通过这个虚拟机来运行Lua脚本,Lua脚本在运行时可以通过虚拟机反过来调用C#脚本里注册过的物体,这种方式的优势在于比起使用反射的uLua来说效率更高。

ToLua框架下可以将实现分成三大部分:普通的Unity+C#部分、ToLua虚拟机部分和Lua脚本部分,结构见下图:

ToLua结构

目前国内需要热更的手游一般都将主要的逻辑框架和组件功能用C#实现,而具体功能和调用放在Lua中,因为C#是不能被打包进AssetBundle中的,所以无法通过AssetBundle对代码进行改动,但是Lua是即时编译型语言,并且可以被打包进入AssetBundle中,在需要修改简单功能时,将Lua代码通过AssetBundle进行更新即可。

2. ToLua的下载的安装

首先是下载地址:

ToLua

这是作者的github地址,进入以后点击下载Zip,完成后解压到自己需要的目录,再用Unity打开即可。

点击下载zip即可

第一次打开工程时会提示是否需要自动生成注册文件,新手可以选择直接生成,若选择了取消,也可以在编辑器菜单中手动注册。——这是一个非常重要的操作,后文也会提到。

下面开始关于使用的正文。

3. ToLua的基本使用

前面有提到过ToLua的基本实现方式,这里可以再细化一点:创建虚拟机——绑定数据——调用Lua代码,这套步骤在框架自带的Example里也非常清晰。

首先脱离Example实现一下这三个步骤。

ToLua虚拟机的创建非常简单,只需要new一个LuaState即可,我们建立一个C#脚本作为入口,引用LuaInterface命名空间,输入以下代码,将文件挂载到场景中的一个空物体上即可。

using LuaInterface;
using UnityEngine;

public class LuaScene : MonoBehaviour
{
 string luaString = @"
  print('这是一个使用DoString的Lua程序')
     ";
 string luaFile = "LuaStudy";
 LuaState state; 

 void Start()
 {
 state = new LuaState();//建立Lua虚拟机
 state.Start();//启动虚拟机

 //使用string调用Lua
 state.DoString(luaString);

 //使用文件调用Lua
 //手动添加一个lua文件搜索地址
 string sceneFile = Application.dataPath + "/LuaStudy";
 state.AddSearchPath(sceneFile);
 state.DoFile(luaFile);
 state.Require(luaFile);

 state.Dispose();//使用完毕回收虚拟机
 Debug.LogFormat("当前虚拟机状态:{0}", null == state);//验证虚拟机状态
 }
}

这里使用的Lua脚本也非常简单

print('这是一个使用DoFile的Lua程序')

Lua挂载

ToLua直接调用Lua代码的方式有两种,一种是DoString,另一种是DoFile;此外还有一个Require方法,这个方法和前两个方法不同的是,ToLua会将调用的Lua文件载入Lua栈中,而前两者只是运行一次,运行之后保存在缓存中,虽然也可以后续调用,但是会。

在上述代码中要注意,使用DoFile和Require方法时,要手动给目标文件添加一个文件搜索位置。

运行结果如下:

Lua运行结果

最后,使用完毕记得清理虚拟机,我使用null==state来进行判断,最后输出“true”,说明调用LuaState.Dispose()后,虚拟机已经被清理。

4. C#中调用Lua变量/函数

我们上面实现了C#调用Lua文件和string,其实对于ToLua而且,直接调用string和文件并没有本质区别,最后都会转换成byte[]进行载入。

接下来实现一下ToLua调用指定Lua变量和函数,这里通过文件导入Lua代码。

首先是我们的Lua代码,这一段Lua代码一共有一个普通变量、一个带有函数的表,一个无参函数,一个有参函数,功能非常简单,并且在这一段代码中没有调用。

num = 0
mytable={1,2,3,4}
mytable.tableFunc=function()
 print('调用TableFunc');
end

function Count()
 num=num+1
 print('计数器+1,当前计数器为'..num)
 return num;
end

function InputValue( param)
 print('[lua中调用:]InputValue方法传入参数:'..tostring( param))
end

然后是C#代码,还是一样的套路,先创建虚拟机,读入Lua文件。下面依次说明普通变量、无参函数、有参函数和table的调用。

注意:如果带有local标识,那么C#中无法直接获取

普通变量

普通变量的调用非常简单,在载入文件后,通过LuaState[string]的形式就可以直接获取到,也可以通过这个表达式来直接赋值。

无参函数

函数的调用有两种方式,一是先缓存为LuaFunction类型后调用,二是直接能过Call方法调用。

有参函数

有参函数和无参函数调用的区别在于参数的传入,在ToLua中重载了非常多的传参函数,与无参函数的调用方法相同,有参函数也有两种调用方式,这里具体说明一下传入参数的不同方式。

传入参和调用分离。

这种方式一般需要先将函数缓存为LuaFunction,然后使用BeginPcall方法标记函数,再使用Push或者PushArgs方法将参数传入函数,最后调用PCall,还可以调用EndPcall标记结束。

 //对方法传入参数
 LuaFunction valueFunc = state.GetFunction("InputValue");
 valueFunc.BeginPCall();
 valueFunc.Push("--push方法从C#中传入参数--");
 valueFunc.PCall();

调用时直接传入参数。

这是最符合一般操作逻辑的方式,但是查看实现代码会发现,事实上只是LuaFunction中封装的一套实现,其本质和上一种是一样的。

4.table

table是lua中的一个百宝箱,一切东西都可以往里装,table里可以有普通的变量,还可以有table,也可以有方法。

在ToLua里对table的数据结构进行了解析,实现了非常多的方法,这里完全可以将table看一个LuaState来进行操作,两者没有什么区别。

以下是完整的C#代码,运行结果后附。

using LuaInterface;
using UnityEngine;
public class LuaAccess : MonoBehaviour
{
 string luaFile = "LuaAccess";
 LuaState state;
 void Start()
 {
 state = new LuaState();
 state.Start();

 //使用文件调用Lua
 //手动添加一个lua文件搜索地址
 string sceneFile = Application.dataPath + "/LuaStudy";
 state.AddSearchPath(sceneFile);

 state.Require(luaFile);//载入文件

 //获取Lua变量
 Debug.Log("获取文件中变量:" + state["num"]);
 state["num"] = 10;
 Debug.Log("设置文件中变量为:" + state["num"]);

 //调用Lua方法
 LuaFunction luaFunc = state.GetFunction("Count");
 luaFunc.Call();
 Debug.Log("C#调用LuaFunc,函数返回值:" + state["num"]);

 Debug.Log("C#直接调用Count方法。");
 state.Call("Count", false); 

 //对方法传入参数
 LuaFunction valueFunc = state.GetFunction("InputValue");
 valueFunc.BeginPCall();
 valueFunc.Push("--push方法从C#中传入参数--");
 valueFunc.PCall();
 valueFunc.EndPCall();

 valueFunc.Call("--直接Call方法从C#传入参数--"); 

 //获取LuaTable
 LuaTable table = state.GetTable("mytable");
 table.Call("tableFunc");
 LuaFunction tableFunc = table.GetLuaFunction("tableFunc");
 Debug.Log("C#调用table中的func");
 tableFunc.Call();

 Debug.Log("获取table中的num值:"+table["num"]);

 //通过下标直接获取
 for (int i = 0; i < table.Length; i++)
 {
  Debug.Log("获取table的值:" + table[i]);
 } 

 //转换成LuaDictTable
 LuaDictTable dicTable = table.ToDictTable();
 foreach (var item in dicTable)
 {
  Debug.LogFormat("遍历table:{0}--{1}", item.Key, item.Value);
 } 

 state.Dispose();
 }
}

Lua访问变量

5. Lua中调用C#方法/变量

之前在 @罗夏L的文章里看过一篇他关于lua调用C#的笔记,但总觉得少了点什么,所以在我自己记笔记的时候特别注意了一下具体的实现。

在@罗夏L的文章中,将一个C#对象作为参数传入列表中,然后直接在Lua代码里运行对应的方法名,其中少了几个关键的步骤,如果只是进行了这几步,是实现不了在Lua里引用的。

首先还是从实现原理说起,在文章开始的第一节我提过ToLua的基本实现思路,并且将这套系统分成了三部分,在这三部分之中,ToLua作为一个桥梁实现了沟通Lua脚本和C#的功能,我们知道Lua的实质是通过字节码对C进行了一套封装,具有即时编译的特点,从C#或者其他语言中来调用Lua都不算太困难,只需要提前约定特定方法名然后载入脚本即可,但C#是需要提前编译的,怎么通过一段解释器来调用C#中的对象就是主要的难点了,ToLua实现的就是这两方面的功能。

从这方面来分析,我觉得大多数人会想到的最直接的实现思路大概都是通过反射来实现,uLua也是通过反射来实现的,但是反射的效率非常低,虽然确实可以实现,但问题还是非常明显。

ToLua是通过方法名绑定的方式来实现这个映射的,首先构造一个Lua虚拟机,在虚拟机启动后对所需的方法进行绑定,在虚拟机运行时可以在Lua中调用特定方法,虚拟机变相地实现了一个解释器的功能,在Lua调用特定方法和对象时,虚拟机会在已绑定的方法中找到对应的C#方法和对象进行操作,并且ToLua已经自动实现了一些绑定的方法 。

基本原理大概了解以后,我们就可以来看看它的具体实现了。

第一步还是建立虚拟机并且启动,为了实现Lua对C#的调用,首先我们要调用一下绑定方法,于是我们的代码变成了下面这样。可以看到,这里和之前的唯一区别是增加了LuaBinder.Bind(state)方法,这一个方法内部其实是对许多定义好的方法的绑定,也就是上面说的绑定方法。

using LuaInterface;
using UnityEngine;

public class CSharpAccess : MonoBehaviour
{
 private string luaFile = "LuaCall";
 LuaState state;

 void Start()
 {
 state = new LuaState();
 state.Start();

 string sceneFile = Application.dataPath + "/LuaStudy";
 state.AddSearchPath(sceneFile);

 // 注册方法调用
 LuaBinder.Bind(state);

 state.Require(luaFile);//载入文件
 }
}

然后我们加入一个变量和一个方法,我们要实现的是完成在Lua中对这个方法和变量的调用。

 public string AccessVar = "++这是初始值++";
 public void PrintArg(string arg)
 {
 Debug.Log("C#输出变量值:" + arg);
 }

在有了目标方法之后,我们要将这个变量和方法绑定进入虚拟机中。

查看LuaState的实现代码,可以发现绑定主要有RegFunction、RegVar和RegConstant三个方法,分别用于绑定函数/委托、变量和常量。在这里ToLua是通过一个委托来实现方法的映射,这个委托需要传入一个luaState变量,类型是IntPtr,这个变量的实质是一个句柄,在实际操作中,会将虚拟机作为变量传入。

 public delegate int LuaCSFunction(IntPtr luaState);
 public void RegFunction(string name, LuaCSFunction func);
 public void RegVar(string name, LuaCSFunction get, LuaCSFunction set);
 public void RegConstant(string name, double d);
 public void RegConstant(string name, bool flag);

总结一下几个方法的特点:

这几个方法都需要传入一个string name,这个name就是之后在Lua中调用的变量或方法名。

RegConstant方法比较简单,传入一个name再传入一个常量即可;

RegFunction和RegVar都是通过LuaCSFunction类型的委托实现;

RegFunction需要一个LuaCSFunction委托,这个委托需要对原方法重新进行一次实现;

RegVar除了name之外,还需要两个LuaCSFunction委托,可以理解为一个变量的get/set方法,如果只有get或set,另一个留null即可。

接下来我们对AccessVar和PrintArg方法进行一下LuaCSFunction形式的实现。

private int PrintCall(System.IntPtr L)
 {
 try
 {
  ToLua.CheckArgsCount(L, 2); //对参数进行校验
  CSharpAccess obj = (CSharpAccess)ToLua.CheckObject(L, 1, typeof(CSharpAccess));//获取目标对象并转换格式
  string arg0 = ToLua.CheckString(L, 2);//获取特定值
  obj.PrintArg(arg0);//调用对象方法
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e);
 }
 }

 private int GetAccesVar(System.IntPtr L)
 {
 object o = null;

 try
 {
  o = ToLua.ToObject(L, 1); //获得变量实例
  CSharpAccess obj = (CSharpAccess)o; //转换目标格式
  string ret = obj.AccessVar; //获取目标值
  ToLua.Push(L, ret);//将目标对象传入虚拟机
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e, o, "attempt to index AccessVar on a nil value");
 }
 }

 private int SetAccesVar(System.IntPtr L)
 {
 object o = null;

 try
 {
  o = ToLua.ToObject(L, 1);//获得变量实例
  CSharpAccess obj = (CSharpAccess)o;//转换目标格式
  obj.AccessVar = ToLua.ToString(L, 2);//将要修改的值进行设定,注意这里如果是值类型可能会出现拆装箱
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e, o, "attempt to index AccessVar on a nil value");
 }
 }

可以看到这三个方法的格式都是一致的,通用的步骤如下:

使用ToLua中的方法对L句柄进行校验,出现异常则抛出,本例中使用ToLua.CheckArgsCount方法;

获得目标类的实例,并转换格式,具体转换方法较多,可以根据需要在ToLua类中选择,本例中使用了ToLua.CheckObject和ToLua.ToObject等方法;

调用对应方法,不同的方法调用略有区别。

值得注意的是,在ToLua的ToObjectQuat、ToObjectVec2等获取值类型的方法中,会出现拆装箱的情况。

下一步将几个方法注册进lua虚拟机。

注意这里有两对方法,分别是BeginModule\EndModule和BeginClass\EndClass,BeginModule\EndModule用于绑定命名空间,可以逐层嵌套;而BeginClass\EndClass用于开启具体的类型空间,具体的方法和变量绑定必须在这成对的方法之中,否则会导致ToLua崩溃(百试百灵,别问我怎么知道的)。

 private void Bind(LuaState L)
 {
 L.BeginModule(null);

 L.BeginClass(typeof(CSharpAccess), typeof(UnityEngine.MonoBehaviour));

 state.RegFunction("Debug", PrintCall);
 state.RegVar("AccessVar", GetAccesVar, SetAccesVar);

 L.EndClass();

 L.EndModule();
 }

最后是我们的Lua代码,非常简单,注意Debug和AccessVar调用的区别。

print('--进入Lua调用--')
local go = UnityEngine.GameObject.Find("LuaScene")
local access=go:GetComponent("CSharpAccess")
access:Debug("Lua调用C#方法")
access.AccessVar="--这是修改值--"
print('--Lua调用结束--')

完整C#代码

using LuaInterface;
using UnityEngine;

public class CSharpAccess : MonoBehaviour
{
 private string luaFile = "LuaCall";
 LuaState state;

 void Start()
 {
 state = new LuaState();
 state.Start(); 

 string sceneFile = Application.dataPath + "/LuaStudy";
 state.AddSearchPath(sceneFile);

 // 注册方法调用
 LuaBinder.Bind(state);
 Bind(state); 

 Debug.Log("AccessVar初始值:" + AccessVar);
 state.Require(luaFile);//载入文件
 Debug.Log("C#查看:" + AccessVar);

 state.Dispose();
 }

 private void Bind(LuaState L)
 {
 L.BeginModule(null);
 L.BeginClass(typeof(CSharpAccess), typeof(UnityEngine.MonoBehaviour));
 state.RegFunction("Debug", PrintCall);
 state.RegVar("AccessVar", GetAccesVar, SetAccesVar);
 L.EndClass();
 L.EndModule();
 }

 private int PrintCall(System.IntPtr L)
 {
 try
 {
  ToLua.CheckArgsCount(L, 2); //对参数进行校验
  CSharpAccess obj = (CSharpAccess)ToLua.CheckObject(L, 1, typeof(CSharpAccess));//获取目标对象并转换格式
  string arg0 = ToLua.CheckString(L, 2);//获取特定值
  obj.PrintArg(arg0);//调用对象方法
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e);
 }
 }

 public void PrintArg(string arg)
 {
 Debug.Log("C#输出变量值:" + arg);
 }

 [System.NonSerialized]
 public string AccessVar = "++这是初始值++";
 private int GetAccesVar(System.IntPtr L)
 {
 object o = null;

 try
 {
  o = ToLua.ToObject(L, 1); //获得变量实例
  CSharpAccess obj = (CSharpAccess)o; //转换目标格式
  string ret = obj.AccessVar; //获取目标值
  ToLua.Push(L, ret);//将目标对象传入虚拟机
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e, o, "attempt to index AccessVar on a nil value");
 }
 }
 private int SetAccesVar(System.IntPtr L)
 {
 object o = null;

 try
 {
  o = ToLua.ToObject(L, 1);//获得变量实例
  CSharpAccess obj = (CSharpAccess)o;//转换目标格式
  obj.AccessVar = ToLua.ToString(L, 2);//将要修改的值进行设定
  return 1;
 }
 catch (System.Exception e)
 {
  return LuaDLL.toluaL_exception(L, e, o, "attempt to index AccessVar on a nil value");
 }
 }
}

运行结果

Lua调用C#

那么最后,我们回到本节开始, @罗夏L的文章里是哪里出现了问题?

我在lua中加入了一行access:PrintArg("PrintArg")调用方法,发现Unity报了这样的错误:

直接调用方法名报错.png

说明单纯这样是做不到直接调用方法的,仔细看文章,我发现他有提到这样的内容:

首先将自己写的类 放到 CustomSettings 里 就是CallLuafunction

BindType[] customTypeList

放到这个数组里 注册进去供lua使用

这里是不是他说得不够详细?我找到这个类,发现这个类里记录了非常多的Unity自带类,这让我想起了第一次启动Lua时的提示,心里生出了一个疑问:这些数据是不是用于自动注册生成类的呢?

 //在这里添加你要导出注册到lua的类型列表
 public static BindType[] customTypeList =
 {
 _GT(typeof(LuaInjectionStation)),
 _GT(typeof(InjectType)),
 _GT(typeof(Debugger)).SetNameSpace(null), 

...以下部分省略

沿着调用链,我找到了这个变量的引用,果然,最这个数据是用于类型注册的。

我将这个类放到了数组的最后,点击Clear wrap files,完成后立即弹出了数据自动生成的对话框,点击确认,

重新生成注册

自动生成

接下来我重新运行了lua脚本:

print('--进入Lua调用--')
local go = UnityEngine.GameObject.Find("LuaScene")
local access=go:GetComponent("CSharpAccess")
access:Debug("Lua调用C#方法")
access.AccessVar="--这是修改值--"
print('--Lua调用结束--')
access:PrintArg("PrintArg")

成功运行

成功运行,说明ToLua实现了一整套绑定方案,只需要将所需要的内容配置完成即可。

6.总结

原本只是想简单写一写调用方式,最后又写成了一篇长文,但是从有计划开始到一整篇结束却花掉了近一整天的时间。

虽然如此,收获还是非常大的,对这套工具的使用熟练度又上了一个层次,以后也要加强总结。

以上这篇ToLua框架下C#与Lua代码的互调操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • C#泛型方法在lua中表示的一种设计详解

    前言 在进行lua方法注册的时候, 大多数解决方案直接否定了泛型方法, 因为在lua侧难以表达出泛型, 以及lua的函数重载问题, 函数重载问题可以通过一些特殊方法解决, 而泛型问题是主要问题, 以Unity + Slua的情况来说 比如下面的类: public class Foo { public static void GetTypeName(System.Type type) { Debug.Log(type.Name); } public static void GetTypeName<

  • C#和lua相互调用的方法教程

    前言 自从ulua在官网上出来后,lua 就被u3d开发人员喜爱.国内有几个高手把lua拿过来 接着进行了封装.很多都是新手转过来.lua语法一看遍知,但是大多数人还是不明白两个语言之间的互相调用是怎么一回事,这也是难点和重点.所以今天想跟大家分享一下这方面的知识,让大家少走弯路吧. Lua是一种很好的扩展性语言,Lua解释器被设计成一个很容易嵌入到宿主程序的库.LuaInterface则用于实现Lua和CLR的混合编程. C与lua交互面临以下几个问题: 1.由于lua里面的数据都是动态加载的

  • Lua Table转C# Dictionary的方法示例

    table特性 table是一个"关联数组",数组的索引可以是数字或者是字符串,所有索引值都需要用 "["和"]" 括起来:如果是字符串,还可以去掉引号和中括号: 即如果没有[]括起,则认为是字符串索引 table 的默认初始索引一般以 1 开始,如果不写索引,则索引就会被认为是数字,并按顺序自动从1往后编: table 的变量只是一个地址引用,对 table 的操作不会产生数据影响 table 不会固定长度大小,有新数据插入时长度会自动增长 t

  • ToLua框架下C#与Lua代码的互调操作

    Lua是目前国内使用最多的热更语言,基于Lua的热更框架也非常多,最近学习了一下ToLua的热更框架,主要使用的问题在于C#和Lua之间的互调,因此做一下学习记录以备后查. 所谓"互调",当然要包括两个方面,一是通过C#调用Lua代码,二是通过Lua代码调用C#脚本,第二点还包括注册在C#脚本里的Unity物体. 1. ToLua的简单实现原理 ToLua框架主要是通过静态绑定来实现C#与Lua之间的交互的,基本原理是通过建立一个Lua虚拟机来映射C#脚本,然后再通过这个虚拟机来运行L

  • 在Jpa框架下拼接原生sql 并执行的操作

    利用jpa的entityManager 执行sql 并执行 其中: EntityManager.createNativeQuery(SQL) 返回的是Object对象 entityManager.createNativeQuery(SQL,WebInfo.class) 返回的是映射后的实例对象 Query.getSingleResult()执行SQL语句,返回一个查询结果,常用的还有以下方法 Query.getResultList()执行SQL语句,返回一个List集合 Query.getFir

  • SSM框架下实现登录注册的示例代码

    基本配置:jdk1.8   tomcat 8  MyEclipse 先打好地基: spring配置文件 application.xml: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-

  • 详解在Spring Boot框架下使用WebSocket实现消息推送

    spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目以及怎样为我们的Project添加HTTPS的支持,在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket. 什么是WebSocket WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,服务器也可以给浏览器发送消息,目前主流浏览器的主流版本对WebSocket的支持都算是比较好的,但是在实际开发中使

  • 详细解读Python的web.py框架下的application.py模块

    本文主要分析的是web.py库的application.py这个模块中的代码.总的来说,这个模块主要实现了WSGI兼容的接口,以便应用程序能够被WSGI应用服务器调用.WSGI是Web Server Gateway Interface的缩写,具体细节可以查看WSGI的WIKI页面 接口的使用 使用web.py自带的HTTP Server 下面这个例子来自官方文档的Hello World,这个代码一般是应用入口的代码: import web urls = ("/.*", "he

  • 在Python的Flask框架下收发电子邮件的教程

     简述 在大多数此类教程中都会不遗余力的介绍如何使用数据库.今天我们对数据库暂且不表,而是来关注另一个在web应用中很重要的特性:如何推送邮件给用户. 在某个轻量级应用中我们可能会添加一个如下的邮件服务功能:当用户有了新的粉丝后,我们发送一封邮件通知用户.有很多方法可以实现这个特性,而我们希望提供出一种可复用的通用框架来处理.   Flask-Mail介绍 对于我们来说是幸运的,现在已经有很多外部插件来处理邮件,虽说不能百分百按照我们的想法去处理,但已经相当接近了. 在虚拟环境中安装 Flask

  • 编写高性能Lua代码的方法

    前言 Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面,尤其是游戏.像<魔兽世界>的插件,手机游戏<大掌门><神曲><迷失之地>等都是用Lua来写的逻辑. 所以大部分时候我们不需要去考虑性能问题.Knuth有句名言:"过早优化是万恶之源".其意思就是过早优化是不必要的,会浪费大量时间,而且容易导致代码混乱. 所以一个好的程序员在考虑优化性能前必须问自己两个问题:"我的程序真的需要优化吗?".如果答案为是,那么再

  • SpringMVC框架下JQuery传递并解析Json格式的数据是如何实现的

    json作为一种轻量级的数据交换格式,在前后台数据交换中占据着非常重要的地位.Json的语法非常简单,采用的是键值对表示形式.JSON 可以将 JavaScript 对象中表示的一组数据转换为字符串,然后就可以在函数之间轻松地传递这个字符串,或者在异步应用程序中将字符串从 Web 客户机传递给服务器端程序,也可以从服务器端程序传递json格式的字符串给前端并由前端解释.这个字符串是符合json语法的,而json语法又是 javascript语法的子集,所以javascript很容易解释它,而且

  • JQueryEasyUI框架下的combobox的取值和绑定的方法

    最近做的项目涉及到JQueryEasyUI框架的使用,EasyUI是什么?网上解释说它是来自W3C标准WEB前端专家DHTML精英俱乐部的简单易用功能强大的轻量级WEB前端JavaScript框架!从这句话的理解来说,我认为它是一种JavaScript框架. 对于最近的使用中,给我的感觉就是,借用官网JqueryEasyUI上的一句话,EasyUI是基于jQuery用户界面插件的集合,我把它理解成一种插件,不知道有没有问题.而使用easyui,不需要写很多的javascript代码,只需在定义的

  • thinkphp框架下实现登录、注册、找回密码功能

    本文实例为大家分享了thinkphp框架下使用ajax表单提交的登录.注册.找密码的实现方法,以及注册后的用户需后台审核. user表的字段为id.num.password.name.email.addtime.status 具体代码如下 <?php namespace Home\Controller; use Think\Controller; class LoginController extends Controller { //处理登录 public function signin(){

随机推荐