xlua源码分析(三)C#访问lua的映射
xlua源码分析(三)C#访问lua的映射
上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过,C#使用LuaTable类持有lua层的table,以及使用Action委托持有lua层的function。而在xlua的官方文档中,推荐使用interface和delegate访问lua层数据结构:
映射到一个interface
这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
映射到delegate
这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。
delegate要怎样声明呢? 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理?从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
参数、返回值类型支持哪些呢?都支持,各种复杂类型,out,ref修饰的,甚至可以返回另外一个delegate。
delegate的使用就更简单了,直接像个函数那样用就可以了。
那么这一节我们就对照着Examples 04_LuaObjectOrented,来看一下如何把包含任意数据的lua table和包含任意参数的lua function映射到C#,让C#可以直接访问。
首先看一下例子中用到的lua代码:
local calc_mt = {__index = {Add = function(self, a, b)return (a + b) * self.Multend,get_Item = function(self, index)return self.list[index + 1]end,set_Item = function(self, index, value)self.list[index + 1] = valueself:notify({name = index, value = value})end,add_PropertyChanged = function(self, delegate)if self.notifylist == nil thenself.notifylist = {}endtable.insert(self.notifylist, delegate)print('add',delegate)end,remove_PropertyChanged = function(self, delegate)for i=1, #self.notifylist doif CS.System.Object.Equals(self.notifylist[i], delegate) thentable.remove(self.notifylist, i)breakendendprint('remove', delegate)end,notify = function(self, evt)if self.notifylist ~= nil thenfor i=1, #self.notifylist doself.notifylist[i](self, evt)endend end,}
}Calc = {New = function (mult, ...)print(...)return setmetatable({Mult = mult, list = {'aaaa','bbbb','cccc'}}, calc_mt)end
}
这个例子很简单,就是定义了一个Calc.New的函数,这个函数会使用传入的参数构建一个新的table,并设置calc_mt作为它的metatable。calc_mt的__index表中定义了若干供C#访问的函数,如Add,get_Item,set_Item,add_PropertyChanged和remove_PropertyChanged。
回到C#,C#层如果想要访问lua层的Calc.New,就需要定义一个和该函数匹配的委托。这个委托定义如下:
[CSharpCallLua]
public delegate ICalc CalcNew(int mult, params string[] args);
委托有一个int类型的参数mult和不定数量的string类型参数args,int和string类型都可以很容易地从C#类型转换到对应的lua类型。再看返回值,这里的返回类型是一个ICalc的interface,它其实映射就是lua层的table,也就是Calc.New所返回的那个table。为了让xlua识别CalcNew这个委托类型是用来映射lua函数的,也就是要使用这个委托调用lua层函数,需要给CalcNew类型打上CSharpCallLua的标签,这样xlua就会生成代码来完成这一工作。
映射lua table的ICalc定义如下:
[CSharpCallLua]
public interface ICalc
{event EventHandler<PropertyChangedEventArgs> PropertyChanged;int Add(int a, int b);int Mult { get; set; }object this[int index] { get; set; }
}
接口类中包含了一个PropertyChanged的event,一个Add方法,一个Multi属性,还实现了下标操作符。那么想必大家都能猜出来,这里就是分别对应了lua层calc_mt的__index表中定义的若干函数。同样地,我们也需要为这个interface打上[CSharpCallLua]标签,这样xlua就会生成一个具体实现该接口的类。
在理解映射思路之后,我们再看下测试代码:
void Test(LuaEnv luaenv)
{luaenv.DoString(script);CalcNew calc_new = luaenv.Global.GetInPath<CalcNew>("Calc.New");ICalc calc = calc_new(10, "hi", "john"); //constructorDebug.Log("sum(*10) =" + calc.Add(1, 2));calc.Mult = 100;Debug.Log("sum(*100)=" + calc.Add(1, 2));Debug.Log("list[0]=" + calc[0]);Debug.Log("list[1]=" + calc[1]);calc.PropertyChanged += Notify;calc[1] = "dddd";Debug.Log("list[1]=" + calc[1]);calc.PropertyChanged -= Notify;calc[1] = "eeee";Debug.Log("list[1]=" + calc[1]);
}void Notify(object sender, PropertyChangedEventArgs e)
{Debug.Log(string.Format("{0} has property changed {1}={2}", sender, e.name, e.value));
}
运行之后输出结果如下:

可以看到,我们通过映射的方式,访问到了lua的函数和table,而且很重要的一点是,测试代码中C#和lua实现了解耦,这种做法也是xlua的官方文档中所推荐的:
使用建议
- 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
- 如果lua侧的实现的部分都以delegate和interface的方式提供,使用方可以完全和xLua解耦:由一个专门的模块负责xlua的初始化以及delegate、interface的映射,然后把这些delegate和interface设置到要用到它们的地方。
那么现在,我们开始,跟着测试代码,一步步地研究背后的实现吧。
第一步,就是调用了GetInPath,通过变量的名称获取到lua函数,再将其转换为CalcNew委托类型:
public T GetInPath<T>(string path)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifvar L = luaEnv.L;var translator = luaEnv.translator;int oldTop = LuaAPI.lua_gettop(L);LuaAPI.lua_getref(L, luaReference);if (0 != LuaAPI.xlua_pgettable_bypath(L, -1, path)){luaEnv.ThrowExceptionFromError(oldTop);}LuaTypes lua_type = LuaAPI.lua_type(L, -1);if (lua_type == LuaTypes.LUA_TNIL && typeof(T).IsValueType()){throw new InvalidCastException("can not assign nil to " + typeof(T).GetFriendlyName());}T value;try{translator.Get(L, -1, out value);}catch (Exception e){throw e;}finally{LuaAPI.lua_settop(L, oldTop);}return value;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}
重点需要关注的其实就是这句translator.Get(L, -1, out value);,它负责对lua栈上的函数进行类型转换。这个委托类型并不是实现注册好的类型,那么就会走到通用的GetObject函数:
public void Get<T>(RealStatePtr L, int index, out T v)
{Func<RealStatePtr, int, T> get_func;if (tryGetGetFuncByType(typeof(T), out get_func)){v = get_func(L, index);}else{v = (T)GetObject(L, index, typeof(T));}
}
这个GetObject函数我们在前面的章节中也分析过,对于不是userdata的lua对象,它会寻找一个caster函数进行转换,如果找不到,则会通过一系列规则生成一个caster:
public ObjectCast GetCaster(Type type)
{if (type.IsByRef) type = type.GetElementType();Type underlyingType = Nullable.GetUnderlyingType(type);if (underlyingType != null){return genNullableCaster(GetCaster(underlyingType)); }ObjectCast oc;if (!castersMap.TryGetValue(type, out oc)){oc = genCaster(type);castersMap.Add(type, oc);}return oc;
}
这里的委托类型是我们自定义的,默认的castersMap中显然不包含,那么xlua就会为我们生成一个:
ObjectCast fixTypeGetter = (RealStatePtr L, int idx, object target) =>
{if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA){object obj = translator.SafeGetCSObj(L, idx);return (obj != null && type.IsAssignableFrom(obj.GetType())) ? obj : null;}return null;
}; if (typeof(Delegate).IsAssignableFrom(type))
{return (RealStatePtr L, int idx, object target) =>{object obj = fixTypeGetter(L, idx, target);if (obj != null) return obj;if (!LuaAPI.lua_isfunction(L, idx)){return null;}return translator.CreateDelegateBridge(L, type, idx);};
}
这里的关键也是在translator.CreateDelegateBridge这句,这个函数之前我们也分析过,它负责生成一个DelegateBridge对象。这个对象就是指代lua函数用的,它自身可以与多个C#的委托绑定。
bridge = new DelegateBridge(reference, luaEnv);
try {var ret = getDelegate(bridge, delegateType);bridge.AddDelegate(delegateType, ret);delegate_bridges[reference] = new WeakReference(bridge);return ret;
}
catch(Exception e)
{bridge.Dispose();throw e;
}
getDelegate这个函数,会根据传入的delegateType,调用DelegateBridgeBase.GetDelegateByType生成对应类型的Delegate对象,它是个virtual方法,我们在生成代码之后,就会产生继承自它的DelegateBridge.GetDelegateByTypeoverride方法,这段生成代码位于DelegatesGenBridge.cs这个文件里:
public partial class DelegateBridge : DelegateBridgeBase
{public override Delegate GetDelegateByType(Type type){if (type == typeof(System.Action)){return new System.Action(__Gen_Delegate_Imp0);}if (type == typeof(UnityEngine.Events.UnityAction)){return new UnityEngine.Events.UnityAction(__Gen_Delegate_Imp0);}if (type == typeof(System.Func<double, double, double>)){return new System.Func<double, double, double>(__Gen_Delegate_Imp1);}if (type == typeof(System.Action<string>)){return new System.Action<string>(__Gen_Delegate_Imp2);}if (type == typeof(System.Action<double>)){return new System.Action<double>(__Gen_Delegate_Imp3);}if (type == typeof(XLuaTest.IntParam)){return new XLuaTest.IntParam(__Gen_Delegate_Imp4);}if (type == typeof(XLuaTest.Vector3Param)){return new XLuaTest.Vector3Param(__Gen_Delegate_Imp5);}if (type == typeof(XLuaTest.CustomValueTypeParam)){return new XLuaTest.CustomValueTypeParam(__Gen_Delegate_Imp6);}if (type == typeof(XLuaTest.EnumParam)){return new XLuaTest.EnumParam(__Gen_Delegate_Imp7);}if (type == typeof(XLuaTest.DecimalParam)){return new XLuaTest.DecimalParam(__Gen_Delegate_Imp8);}if (type == typeof(XLuaTest.ArrayAccess)){return new XLuaTest.ArrayAccess(__Gen_Delegate_Imp9);}if (type == typeof(System.Action<bool>)){return new System.Action<bool>(__Gen_Delegate_Imp10);}if (type == typeof(Tutorial.CSCallLua.FDelegate)){return new Tutorial.CSCallLua.FDelegate(__Gen_Delegate_Imp11);}if (type == typeof(Tutorial.CSCallLua.GetE)){return new Tutorial.CSCallLua.GetE(__Gen_Delegate_Imp12);}if (type == typeof(XLuaTest.InvokeLua.CalcNew)){return new XLuaTest.InvokeLua.CalcNew(__Gen_Delegate_Imp13);}return null;}
}
得到Delegate之后,这里会将其进行缓存,这样下次遇到相同类型直接取出该委托即可。DelegateBridgeBase类缓存Delegate的数据结构比较有意思,它有一对firstKey和firstValue,然后一个Dictionary<Type, Delegate>的字典所组成,缓存时会优先将数据保存到firstKey和firstValue上,这样取出的时候就无需对字典进行查找,查找效率更高。
public bool TryGetDelegate(Type key, out Delegate value)
{if(key == firstKey){value = firstValue;return true;}if (bindTo != null){return bindTo.TryGetValue(key, out value);}value = null;return false;
}public void AddDelegate(Type key, Delegate value)
{if (key == firstKey){throw new ArgumentException("An element with the same key already exists in the dictionary.");}if (firstKey == null && bindTo == null) // nothing {firstKey = key;firstValue = value;}else if (firstKey != null && bindTo == null) // one key existed{bindTo = new Dictionary<Type, Delegate>();bindTo.Add(firstKey, firstValue);firstKey = null;firstValue = null;bindTo.Add(key, value);}else{bindTo.Add(key, value);}
}
就这样,这个新生成的委托经过辗转终于返回到了测试代码,也就是calc_new对象,那么我们就可以直接通过委托的方式调用它,此时就会触发生成的__Gen_Delegate_Imp13函数了,我们来看看生成的代码长什么样:
public XLuaTest.InvokeLua.ICalc __Gen_Delegate_Imp13(int p0, string[] p1)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifRealStatePtr L = luaEnv.rawL;int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);ObjectTranslator translator = luaEnv.translator;LuaAPI.xlua_pushinteger(L, p0);if (p1 != null) { for (int __gen_i = 0; __gen_i < p1.Length; ++__gen_i) LuaAPI.lua_pushstring(L, p1[__gen_i]); };PCall(L, 1 + (p1 == null ? 0 : p1.Length), 1, errFunc);XLuaTest.InvokeLua.ICalc __gen_ret = (XLuaTest.InvokeLua.ICalc)translator.GetObject(L, errFunc + 1, typeof(XLuaTest.InvokeLua.ICalc));LuaAPI.lua_settop(L, errFunc - 1);return __gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}
代码逻辑很简单,就是准备调用环境,然后把C#的参数push到lua层,然后pcall调用,然后从lua栈中取出返回的结果,由于lua是弱类型的,无法事先知道返回值的类型,所以这里只能使用通用的GetObject函数对lua的返回值进行类型转换。
同样,ICalc类型是我们自定义的,默认的castersMap是不包含的,也需要生成一个caster:
return (RealStatePtr L, int idx, object target) =>
{object obj = fixTypeGetter(L, idx, target);if (obj != null) return obj;if (!LuaAPI.lua_istable(L, idx)){return null;}return translator.CreateInterfaceBridge(L, type, idx);
};
那么,这里的关键就是在translator.CreateInterfaceBridge上了,与委托非常类似,这里会根据interface的类型,寻找负责生成interface对象的函数:
public object CreateInterfaceBridge(RealStatePtr L, Type interfaceType, int idx)
{Func<int, LuaEnv, LuaBase> creator;if (!interfaceBridgeCreators.TryGetValue(interfaceType, out creator)){
#if (UNITY_EDITOR || XLUA_GENERAL) && !NET_STANDARD_2_0var bridgeType = ce.EmitInterfaceImpl(interfaceType);creator = (int reference, LuaEnv luaenv) =>{return Activator.CreateInstance(bridgeType, new object[] { reference, luaEnv }) as LuaBase;};interfaceBridgeCreators.Add(interfaceType, creator);
#elsethrow new InvalidCastException("This type must add to CSharpCallLua: " + interfaceType);
#endif}LuaAPI.lua_pushvalue(L, idx);return creator(LuaAPI.luaL_ref(L), luaEnv);
}
往interfaceBridgeCreators注册creator的逻辑就是在生成代码中完成的,位于XLuaGenAutoRegister.cs中:
static void Init(LuaEnv luaenv, ObjectTranslator translator)
{wrapInit0(luaenv, translator);translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.IExchanger), XLuaTestIExchangerBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(Tutorial.CSCallLua.ItfD), TutorialCSCallLuaItfDBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.InvokeLua.ICalc), XLuaTestInvokeLuaICalcBridge.__Create);}
XLuaTestInvokeLuaICalcBridge是继承自ICalc接口的类,它负责实现ICalc的功能,也就是我们一开始提到的一个PropertyChanged的event +=和-=操作,一个Add方法,一个Multi属性,以及下标操作符。__Create方法就是简单了返回了一个XLuaTestInvokeLuaICalcBridge对象:
public class XLuaTestInvokeLuaICalcBridge : LuaBase, XLuaTest.InvokeLua.ICalc
{public static LuaBase __Create(int reference, LuaEnv luaenv){return new XLuaTestInvokeLuaICalcBridge(reference, luaenv);}
}
有了ICalc对象后,我们再次回到例子中,例子中接下来调用了Add方法与Multi的set属性,XLuaTestInvokeLuaICalcBridge类对它们的实现都比较简单,这里就不再赘述了。接下来是下标访问,对于get来说会去尝试访问lua层的get_item函数,而对于set来说则会去访问lua层的set_item函数。例子里还往PropertyChanged事件中注册了一个Notify方法,这时则会触发lua层的add_PropertyChanged函数,把C#的Notify方法push到lua层。
上一节我们提到,把C#对象push到lua层时,会调用到xlua的getTypeId方法,用来获取表示对象类的唯一ID,对于Notify方法来说,它就是一个委托,而委托实质上使用的是同一个type id:
if (typeof(MulticastDelegate).IsAssignableFrom(type))
{if (common_delegate_meta == -1) throw new Exception("Fatal Exception! Delegate Metatable not inited!");TryDelayWrapLoader(L, type);return common_delegate_meta;
}
TryDelayWrapLoader我们上一节分析过,这里就不展开了,由于没有wrap,还是通过反射生成类的各种table。最终lua层缓存了一个表示C# Notify方法的userdata。
此时再对table进行set_item,就会触发Notify方法调用了,对于delegate来说,xlua在初始化时就往metatable里设置了__call元方法:
public void CreateDelegateMetatable(RealStatePtr L)
{Utils.BeginObjectRegister(null, L, this, 3, 0, 0, 0, common_delegate_meta);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__call", StaticLuaCallbacks.DelegateCall);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__add", StaticLuaCallbacks.DelegateCombine);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__sub", StaticLuaCallbacks.DelegateRemove);Utils.EndObjectRegister(null, L, this, null, null,typeof(System.MulticastDelegate), null, null);
}[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int DelegateCall(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);object objDelegate = translator.FastGetCSObj(L, 1);if (objDelegate == null || !(objDelegate is Delegate)){return LuaAPI.luaL_error(L, "trying to invoke a value that is not delegate nor callable");}return translator.methodWrapsCache.GetDelegateWrap(objDelegate.GetType())(L);}catch (Exception e){return LuaAPI.luaL_error(L, "c# exception in DelegateCall:" + e);}
}
GetDelegateWrap方法就是根据委托的类型,反射取出它的Inovke方法,然后包装到MethodWrap的Call方法中,进行最终的反射调用。
相关文章:
xlua源码分析(三)C#访问lua的映射
xlua源码分析(三)C#访问lua的映射 上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过,C#使用LuaTable类持有lua层的table,以及使用Action委托持有lua层的function。而在xlua的官方文档中,推荐使…...
2023 极术通讯-汽车“新四化”路上,需要一片安全山海
导读:极术社区推出极术通讯,引入行业媒体和技术社区、咨询机构优质内容,定期分享产业技术趋势与市场应用热点。 芯方向 【Armv9】-动态TrustZone技术的介绍 动态 TrustZone 是提供多租户安全媒体 pipeline 的绝佳工具。完全不受操作系统、虚…...
Spring Boot接口设计规范
接口参数处理及统一结果响应 1、接口参数处理 1、普通参数接收 这种参数接收方式是比较常见的,由于是GET请求方式,所以在传参时直接在路径后拼接参数和参数值即可。 例如:localhost:8080/api/product/list?key1value1&key2value2 /…...
美创科技与南京大数据安全技术有限公司达成战略合作
近日,美创科技与南京大数据安全技术有限公司正式签署战略合作协议,优势力量共享、共拓共创共赢。 美创科技CEO柳遵梁、副总裁罗亮亮、副总裁王利强,南京大数据安全技术有限公司总经理潘杰、市场总监刘莉莎、销售总监王皓月、技术总监薛松等出…...
2.4路由日志管理
2.4路由/日志管理 一、静态路由和动态路由 路由器在转发数据时,需要现在路由表中查找相应的路由,有三种途径 (1)直连路由:路由器自动添加和自己直连的路由 (2)静态路由:管理员手动…...
归并排序详解:递归实现+非递归实现(图文详解+代码)
文章目录 归并排序1.递归实现2.非递归实现3.海量数据的排序问题 归并排序 时间复杂度:O ( N * logzN ) 每一层都是N,有log2N层空间复杂度:O(N),每个区间都会申请内存,最后申请的数组大小和array大小相同稳定…...
DataBinding原理
1、MainActivity首先使用DataBindingUtil.setContentView设置布局文件activity_main.xml。 2、随后,经过一系列函数调用,ActivityMainBindingImpl对象最终会实例化,并与activity_main.xml进行绑定。 3、实例化后的ActivityMainBindingImpl对象…...
docker更换国内源
docker更换国内源 1、编辑Docker配置文件 在终端中执行以下命令,编辑Docker配置文件: vi /etc/docker/daemon.json2、添加更新源 在打开的配置文件中,添加以下内容: {"registry-mirrors": ["https://hub-mirror…...
【咖啡品牌分析】Google Maps数据采集咖啡市场数据分析区域分析热度分布分析数据抓取瑞幸星巴克
引言 咖啡作为一种受欢迎的饮品,已经成为我们生活中不可或缺的一部分。随着国内外咖啡品牌的涌入,新加坡咖啡市场愈加多元化和竞争激烈。 本文对新加坡咖啡市场进行了全面的品牌门店数占比分析,聚焦于热门品牌的地理分布、投资价值等。通过…...
【Java】异常处理(一)
🌺个人主页:Dawn黎明开始 🎀系列专栏:Java ⭐每日一句:什么都不做,才会来不及 📢欢迎大家:关注🔍点赞👍评论📝收藏⭐️ 文章目录 📋前…...
【高级程序设计】Week2-4Week3-1 JavaScript
一、Javascript 1. What is JS 定义A scripting language used for client-side web development.作用 an implementation of the ECMAScript standard defines the syntax/characteristics of the language and a basic set of commonly used objects such as Number, Date …...
PHP笔记-->读取JSON数据以及获取读取到的JSON里边的数据
由于我以前是写C#的,现在学一下PHP, 在读取json数据的时候被以前的思维卡住了。 以前用C#读取的时候,是先定义一个数组,将反序列化的json存到数组里面,在从数组里面获取jaon中的“data”数据。 其实PHP的思路也是一样…...
【Spring Boot】如何集成Redis
在pom.xml文件中导入spring data redis的maven坐标。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> 在application.yml文件中加入redis相关配置。 spr…...
Elasticsearch备份与还原:使用elasticdump
在数据管理的世界里,备份和还原数据是重中之重的日常工作,特别是对于Elasticsearch这样的强大而复杂的搜索引擎。备份不仅可以用于灾难恢复,还可以在数据迁移、测试或者升级等场景中发挥重要作用。 在本博客中,我们将会重点介绍如…...
给大伙讲个笑话:阿里云服务器开了安全组防火墙还是无法访问到服务
铺垫: 某天我在阿里云上买了一个服务器,买完我就通过MobaXterm进行了ssh(这个软件是会保存登录信息的) 故事开始: 过了n天之后我想用这个服务器来部署流媒体服务,咔咔两下就部署好了流媒体服务器&#x…...
js:react使用zustand实现状态管理
文档 https://www.npmjs.com/package/zustandhttps://github.com/pmndrs/zustandhttps://docs.pmnd.rs/zustand/getting-started/introduction 安装 npm install zustand示例 定义store store/index.js import { create } from "zustand";export const useCount…...
vue3+vite+SQL.js 读取db3文件数据
前言:好久没写博客了,最近一直在忙,没时间梳理。最近遇到一个需求是读取本地SQLite文件,还是花费了点时间才实现,没怎么看到vite方面写这个的文章,现在分享出来完整流程。 1.pnpm下载SQL.js(什么都可以下)…...
微信小程序 限制字数文本域框组件封装
微信小程序 限制字数文本域框 介绍:展示类组件 导入 在app.json或index.json中引入组件 "usingComponents": {"text-field":"/pages/components/text-field/index"}代码使用 <text-field maxlength"500" bindtabsIt…...
阿里国际站(直通车)
1.国际站流量 2.直通车即P4P(pay for performance点击付费) 2.1直通的含义:按点击付费,通过自助设置多维度展示产品信息,获得大量曝光吸引潜在买家。 注意:中国大陆和尼日利尼地区点击不扣费。 2.2扣费规…...
C# GC机制
在C#中,垃圾回收(Garbage Collection,简称GC)是CLR(公共语言运行时)的一个重要部分,用于自动管理内存。它会自动释放不再使用的对象所占用的内存,避免内存泄漏,减少程序员…...
Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
React19源码系列之 事件插件系统
事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...
c#开发AI模型对话
AI模型 前面已经介绍了一般AI模型本地部署,直接调用现成的模型数据。这里主要讲述讲接口集成到我们自己的程序中使用方式。 微软提供了ML.NET来开发和使用AI模型,但是目前国内可能使用不多,至少实践例子很少看见。开发训练模型就不介绍了&am…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
R语言速释制剂QBD解决方案之三
本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
掌握 HTTP 请求:理解 cURL GET 语法
cURL 是一个强大的命令行工具,用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中,cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...
