当前位置: 首页 > news >正文

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#访问的函数,如Addget_Itemset_Itemadd_PropertyChangedremove_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));
}

运行之后输出结果如下:

xlua源码分析(三)1

可以看到,我们通过映射的方式,访问到了lua的函数和table,而且很重要的一点是,测试代码中C#和lua实现了解耦,这种做法也是xlua的官方文档中所推荐的:

使用建议

  1. 访问lua全局数据,特别是table以及function,代价比较大,建议尽量少做,比如在初始化时把要调用的lua function获取一次(映射到delegate)后,保存下来,后续直接调用该delegate即可。table也类似。
  2. 如果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源码分析&#xff08;三&#xff09;C#访问lua的映射 上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过&#xff0c;C#使用LuaTable类持有lua层的table&#xff0c;以及使用Action委托持有lua层的function。而在xlua的官方文档中&#xff0c;推荐使…...

2023 极术通讯-汽车“新四化”路上,需要一片安全山海

导读&#xff1a;极术社区推出极术通讯&#xff0c;引入行业媒体和技术社区、咨询机构优质内容&#xff0c;定期分享产业技术趋势与市场应用热点。 芯方向 【Armv9】-动态TrustZone技术的介绍 动态 TrustZone 是提供多租户安全媒体 pipeline 的绝佳工具。完全不受操作系统、虚…...

Spring Boot接口设计规范

接口参数处理及统一结果响应 1、接口参数处理 1、普通参数接收 这种参数接收方式是比较常见的&#xff0c;由于是GET请求方式&#xff0c;所以在传参时直接在路径后拼接参数和参数值即可。 例如&#xff1a;localhost:8080/api/product/list?key1value1&key2value2 /…...

美创科技与南京大数据安全技术有限公司达成战略合作

近日&#xff0c;美创科技与南京大数据安全技术有限公司正式签署战略合作协议&#xff0c;优势力量共享、共拓共创共赢。 美创科技CEO柳遵梁、副总裁罗亮亮、副总裁王利强&#xff0c;南京大数据安全技术有限公司总经理潘杰、市场总监刘莉莎、销售总监王皓月、技术总监薛松等出…...

2.4路由日志管理

2.4路由/日志管理 一、静态路由和动态路由 路由器在转发数据时&#xff0c;需要现在路由表中查找相应的路由&#xff0c;有三种途径 &#xff08;1&#xff09;直连路由&#xff1a;路由器自动添加和自己直连的路由 &#xff08;2&#xff09;静态路由&#xff1a;管理员手动…...

归并排序详解:递归实现+非递归实现(图文详解+代码)

文章目录 归并排序1.递归实现2.非递归实现3.海量数据的排序问题 归并排序 时间复杂度&#xff1a;O ( N * logzN ) 每一层都是N,有log2N层空间复杂度&#xff1a;O&#xff08;N&#xff09;&#xff0c;每个区间都会申请内存&#xff0c;最后申请的数组大小和array大小相同稳定…...

DataBinding原理

1、MainActivity首先使用DataBindingUtil.setContentView设置布局文件activity_main.xml。 2、随后&#xff0c;经过一系列函数调用&#xff0c;ActivityMainBindingImpl对象最终会实例化&#xff0c;并与activity_main.xml进行绑定。 3、实例化后的ActivityMainBindingImpl对象…...

docker更换国内源

docker更换国内源 1、编辑Docker配置文件 在终端中执行以下命令&#xff0c;编辑Docker配置文件&#xff1a; vi /etc/docker/daemon.json2、添加更新源 在打开的配置文件中&#xff0c;添加以下内容&#xff1a; {"registry-mirrors": ["https://hub-mirror…...

【咖啡品牌分析】Google Maps数据采集咖啡市场数据分析区域分析热度分布分析数据抓取瑞幸星巴克

引言 咖啡作为一种受欢迎的饮品&#xff0c;已经成为我们生活中不可或缺的一部分。随着国内外咖啡品牌的涌入&#xff0c;新加坡咖啡市场愈加多元化和竞争激烈。 本文对新加坡咖啡市场进行了全面的品牌门店数占比分析&#xff0c;聚焦于热门品牌的地理分布、投资价值等。通过…...

【Java】异常处理(一)

&#x1f33a;个人主页&#xff1a;Dawn黎明开始 &#x1f380;系列专栏&#xff1a;Java ⭐每日一句&#xff1a;什么都不做&#xff0c;才会来不及 &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️ 文章目录 &#x1f4cb;前…...

【高级程序设计】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#的&#xff0c;现在学一下PHP&#xff0c; 在读取json数据的时候被以前的思维卡住了。 以前用C#读取的时候&#xff0c;是先定义一个数组&#xff0c;将反序列化的json存到数组里面&#xff0c;在从数组里面获取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

在数据管理的世界里&#xff0c;备份和还原数据是重中之重的日常工作&#xff0c;特别是对于Elasticsearch这样的强大而复杂的搜索引擎。备份不仅可以用于灾难恢复&#xff0c;还可以在数据迁移、测试或者升级等场景中发挥重要作用。 在本博客中&#xff0c;我们将会重点介绍如…...

给大伙讲个笑话:阿里云服务器开了安全组防火墙还是无法访问到服务

铺垫&#xff1a; 某天我在阿里云上买了一个服务器&#xff0c;买完我就通过MobaXterm进行了ssh&#xff08;这个软件是会保存登录信息的&#xff09; 故事开始&#xff1a; 过了n天之后我想用这个服务器来部署流媒体服务&#xff0c;咔咔两下就部署好了流媒体服务器&#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文件数据

前言&#xff1a;好久没写博客了&#xff0c;最近一直在忙&#xff0c;没时间梳理。最近遇到一个需求是读取本地SQLite文件&#xff0c;还是花费了点时间才实现&#xff0c;没怎么看到vite方面写这个的文章&#xff0c;现在分享出来完整流程。 1.pnpm下载SQL.js(什么都可以下)…...

微信小程序 限制字数文本域框组件封装

微信小程序 限制字数文本域框 介绍&#xff1a;展示类组件 导入 在app.json或index.json中引入组件 "usingComponents": {"text-field":"/pages/components/text-field/index"}代码使用 <text-field maxlength"500" bindtabsIt…...

阿里国际站(直通车)

1.国际站流量 2.直通车即P4P&#xff08;pay for performance点击付费&#xff09; 2.1直通的含义&#xff1a;按点击付费&#xff0c;通过自助设置多维度展示产品信息&#xff0c;获得大量曝光吸引潜在买家。 注意&#xff1a;中国大陆和尼日利尼地区点击不扣费。 2.2扣费规…...

C# GC机制

在C#中&#xff0c;垃圾回收&#xff08;Garbage Collection&#xff0c;简称GC&#xff09;是CLR&#xff08;公共语言运行时&#xff09;的一个重要部分&#xff0c;用于自动管理内存。它会自动释放不再使用的对象所占用的内存&#xff0c;避免内存泄漏&#xff0c;减少程序员…...

wpf devexpress在未束缚模式中生成Tree

TreeListControl 可以在未束缚模式中没有数据源时操作&#xff0c;这个教程示范如何在没有数据源时创建tree 在XAML生成tree 创建ProjectObject类实现数据对象显示在TreeListControl: public class ProjectObject {public string Name { get; set; }public string Executor {…...

Python利器:os与chardet读取多编码文件

在数据处理中会遇到读取位于不同位置的文件,每个文件所在的层级不同,而且每个文件的编码类型各不相同,那么如何高效地读取文件呢? 在读取文件时首先需要获取文件的位置信息,然后根据文件的编码类型来读取文件。本文将使用os获取文件路径,使用chardet得到文件编码类型。 …...

微服务和注册中心

微服务和注册中心是紧密相关的概念&#xff0c;可以说注册中心是微服务架构中必不可少的一部分。 在微服务架构中&#xff0c;系统被拆分成了若干个独立的服务&#xff0c;因此服务之间需要进行通信和协作。为了实现服务的发现和调用&#xff0c;需要一个中心化的注册中心来进…...

吴恩达《机器学习》9-1-9-3:反向传播算法、反向传播算法的直观理解

一、正向传播的基础 在正向传播中&#xff0c;从神经网络的输入层开始&#xff0c;通过一层一层的计算&#xff0c;最终得到输出层的预测结果。这是一种前向的计算过程&#xff0c;即从输入到输出的传播。 二、反向传播算法概述 反向传播算法是为了计算代价函数相对于模型参数…...

Java 算法篇-链表的经典算法:判断回文链表、判断环链表与寻找环入口节点(“龟兔赛跑“算法实现)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 链表的创建 2.0 判断回文链表说明 2.1 快慢指针方法 2.2 使用递归方式实现反转链表方法 2.3 实现判断回文链表 - 使用快慢指针与反转链表方法 3.0 判断环链表说明…...

【JS】Chapter13-构造函数数据常用函数

站在巨人的肩膀上 黑马程序员前端JavaScript入门到精通全套视频教程&#xff0c;javascript核心进阶ES6语法、API、js高级等基础知识和实战教程 &#xff08;十三&#xff09;构造函数&数据常用函数 1. 深入对象 1.1 创建对象三种方式 利用对象字面量创建对象const o {…...

06-流媒体-YUV数据在SDL控件显示

整体方案&#xff1a; 采集端&#xff1a;摄像头采集&#xff08;YUV&#xff09;->编码&#xff08;YUV转H264&#xff09;->写封装&#xff08;&#xff28;264转FLV&#xff09;->RTMP推流 客户端&#xff1a;RTMP拉流->解封装&#xff08;FLV转H264&#xff09…...

对象和数据结构

文章目录 前言一、从链式调用说起二、数据抽象三、数据、对象的反对称性四、得墨忒尔律五、数据传送对象总结 前言 代码整洁之道读书随笔&#xff0c;第六章 一、从链式调用说起 面向对象语言中常用的一种调用形式&#xff0c;链式调用&#xff0c;是一种较受推崇的编码风格&…...

ESP32-BLE基础知识

一、存储模式 两种存储模式&#xff1a; 大端存储&#xff1a;低地址存高字节&#xff0c;如将0x1234存成[0x12,0x34]。小端存储&#xff1a;低地址存低字节&#xff0c;如将0x1234存成[0x34,0x12]。 一般来说&#xff0c;我们看到的一些字符串形式的数字都是大端存储形式&a…...

vscode终端npm install报错

报错如下&#xff1a; npm WARN read-shrinkwrap This version of npm is compatible with lockfileVersion1, but package-lock.json was generated for lockfileVersion2. Ill try to do my best with it! npm ERR! code EPERM npm ERR! syscall open npm ERR! errno -4048…...