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

xlua源码分析(二)lua Call C#的无wrap实现

xlua源码分析(二)lua Call C#的无wrap实现

上一节我们主要分析了xlua中C# Call lua的实现思路,本节我们将根据Examples 03_UIEvent,分析lua Call C#的底层实现。例子场景里有一个简单的UI面板,面板中包含一个input field,一个button:

在这里插入图片描述

输入任意文本,点击button,就会打印出输入的内容:

在这里插入图片描述

响应点击事件的代码是在lua层,位于ButtonInteraction.lua.txt这个文件中,lua代码很简单,就是一个简单的函数:

function start()print("lua start...")self:GetComponent("Button").onClick:AddListener(function()print("clicked, you input is '" ..input:GetComponent("InputField").text .."'")end)
end

那么C#层从哪里读取到这个文件的呢?可以看到,Button这个GameObject上绑了上一节我们提到过的LuaBehaviour组件,而组件里设置的Lua Script就是这个文件了:

在这里插入图片描述

上一节我们说过,LuaBehaviour组件会在Awake的时候会执行lua代码,获取lua层写的start函数,然后在MonoBehaviour的Start中执行它。在lua层的start函数中,首先可以发现一个self,这个self也是在C#层Awake的时候设置的,对应的就是C#的LuaBehaviour对象。和tolua一样,xlua也会把C#对象当作userdata来处理,每个要push到lua层的C#类型都有唯一的type_id,对应到不同的metatable,用来定义userdata的行为。并且,除了值类型和枚举类型之外,所有push到lua层的C#对象,都会在C#层缓存,这一点也是和tolua一样的,甚至缓存的数据结构也大差不差。

public void Push(RealStatePtr L, object o)
{if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))){if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1){return;}}bool is_first;int type_id = getTypeId(L, type, out is_first);index = addObject(o, is_valuetype, is_enum);LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}

xlua_tryget_cachedud函数就是通过C#缓存拿到的index,去lua层的缓存去拿userdata,lua层的缓存与C#不同,它只负责查询,不负责存储,因此是一个value为弱引用的弱表,这一点和tolua也是一样的,xlua在初始化时就会将这个弱表准备好:

LuaAPI.lua_newtable(L);
LuaAPI.lua_newtable(L);
LuaAPI.xlua_pushasciistring(L, "__mode");
LuaAPI.xlua_pushasciistring(L, "v");
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_setmetatable(L, -2);
cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

由于这个缓存是弱表,意味着userdata在被真正gc之前,弱表里对应的值有可能已经不存在了。那么xlua_tryget_cachedud这个函数有可能是取不到userdata的:

LUA_API int xlua_tryget_cachedud(lua_State *L, int key, int cache_ref) {lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);lua_rawgeti(L, -1, key);if (!lua_isnil(L, -1)){lua_remove(L, -2);return 1;}lua_pop(L, 2);return 0;
}

取不到的话就通过xlua_pushcsobj这个函数新增一个userdata:

static void cacheud(lua_State *L, int key, int cache_ref) {lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);lua_pushvalue(L, -2);lua_rawseti(L, -2, key);lua_pop(L, 1);
}LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {int* pointer = (int*)lua_newuserdata(L, sizeof(int));*pointer = key;if (need_cache) cacheud(L, key, cache_ref);lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);lua_setmetatable(L, -2);
}

但是,xlua设置userdata metatable的做法和tolua完全不同。xlua使用delay wrap的策略,即只有某个C#类型的对象push到了lua层,才会将这个C#类型的信息,真正地加载到lua层,在此之前,这个metatable并不存在;而tolua默认是在一开始就wrap的,这样的话类型一多,初始化的时间就大大增加,而且根据二八定律,可能绝大部分的类型在一开始压根用不到。

那么,这个delay wrap具体是怎么实现的呢?既然它是在C#对象push到lua层触发的,那么显而易见,在获取这个类的type_id时,就要把C#类的信息加载进来了:

internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{int type_id;if (!typeIdMap.TryGetValue(type, out type_id)) // no reference{LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta{LuaAPI.lua_pop(L, 1);if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type)){LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);}else{throw new Exception("Fatal: can not load metatable of type:" + type);}}typeIdMap.Add(type, type_id);}return type_id;
}

负责这件事情的函数就是TryDelayWrapLoader。在例子中,由于我们没有生成过类的wrap,默认就会使用反射的方式来注册各种C#方法与成员。具体实现的逻辑比较复杂,主要在ReflectionWrap这个函数中:

public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
{LuaAPI.lua_checkstack(L, 20);int top_enter = LuaAPI.lua_gettop(L);ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);//create obj meta tableLuaAPI.luaL_getmetatable(L, type.FullName);if (LuaAPI.lua_isnil(L, -1)){LuaAPI.lua_pop(L, 1);LuaAPI.luaL_newmetatable(L, type.FullName);}LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());LuaAPI.lua_pushnumber(L, 1);LuaAPI.lua_rawset(L, -3);int obj_meta = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int cls_meta = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int obj_field = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int obj_getter = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int obj_setter = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int cls_field = LuaAPI.lua_gettop(L);//set cls_field to namespaceSetCSTable(L, type, cls_field);//finish set cls_field to namespaceLuaAPI.lua_newtable(L);int cls_getter = LuaAPI.lua_gettop(L);LuaAPI.lua_newtable(L);int cls_setter = LuaAPI.lua_gettop(L);LuaCSFunction item_getter;LuaCSFunction item_setter;makeReflectionWrap(L, type, cls_field, cls_getter, cls_setter, obj_field, obj_getter, obj_setter, obj_meta,out item_getter, out item_setter, privateAccessible ? (BindingFlags.Public | BindingFlags.NonPublic) : BindingFlags.Public);// init obj metatableLuaAPI.xlua_pushasciistring(L, "__gc");LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);LuaAPI.lua_rawset(L, obj_meta);LuaAPI.xlua_pushasciistring(L, "__tostring");LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);LuaAPI.lua_rawset(L, obj_meta);LuaAPI.xlua_pushasciistring(L, "__index");LuaAPI.lua_pushvalue(L, obj_field);LuaAPI.lua_pushvalue(L, obj_getter);translator.PushFixCSFunction(L, item_getter);translator.PushAny(L, type.BaseType());LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);LuaAPI.lua_pushnil(L);LuaAPI.gen_obj_indexer(L);//store in lua indexs function tablesLuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);translator.Push(L, type);LuaAPI.lua_pushvalue(L, -3);LuaAPI.lua_rawset(L, -3);LuaAPI.lua_pop(L, 1);LuaAPI.lua_rawset(L, obj_meta); // set __indexLuaAPI.xlua_pushasciistring(L, "__newindex");LuaAPI.lua_pushvalue(L, obj_setter);translator.PushFixCSFunction(L, item_setter);translator.Push(L, type.BaseType());LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);LuaAPI.lua_pushnil(L);LuaAPI.gen_obj_newindexer(L);//store in lua newindexs function tablesLuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);translator.Push(L, type);LuaAPI.lua_pushvalue(L, -3);LuaAPI.lua_rawset(L, -3);LuaAPI.lua_pop(L, 1);LuaAPI.lua_rawset(L, obj_meta); // set __newindex//finish init obj metatableLuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");translator.PushAny(L, type);LuaAPI.lua_rawset(L, cls_field);if (type != null && type.IsEnum()){LuaAPI.xlua_pushasciistring(L, "__CastFrom");translator.PushFixCSFunction(L, genEnumCastFrom(type));LuaAPI.lua_rawset(L, cls_field);}//init class metaLuaAPI.xlua_pushasciistring(L, "__index");LuaAPI.lua_pushvalue(L, cls_getter);LuaAPI.lua_pushvalue(L, cls_field);translator.Push(L, type.BaseType());LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);LuaAPI.gen_cls_indexer(L);//store in lua indexs function tablesLuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);translator.Push(L, type);LuaAPI.lua_pushvalue(L, -3);LuaAPI.lua_rawset(L, -3);LuaAPI.lua_pop(L, 1);LuaAPI.lua_rawset(L, cls_meta); // set __index LuaAPI.xlua_pushasciistring(L, "__newindex");LuaAPI.lua_pushvalue(L, cls_setter);translator.Push(L, type.BaseType());LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);LuaAPI.gen_cls_newindexer(L);//store in lua newindexs function tablesLuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);translator.Push(L, type);LuaAPI.lua_pushvalue(L, -3);LuaAPI.lua_rawset(L, -3);LuaAPI.lua_pop(L, 1);LuaAPI.lua_rawset(L, cls_meta); // set __newindexLuaCSFunction constructor = typeof(Delegate).IsAssignableFrom(type) ? translator.metaFunctions.DelegateCtor : translator.methodWrapsCache.GetConstructorWrap(type);if (constructor == null){constructor = (RealStatePtr LL) =>{return LuaAPI.luaL_error(LL, "No constructor for " + type);};}LuaAPI.xlua_pushasciistring(L, "__call");translator.PushFixCSFunction(L, constructor);LuaAPI.lua_rawset(L, cls_meta);LuaAPI.lua_pushvalue(L, cls_meta);LuaAPI.lua_setmetatable(L, cls_field);LuaAPI.lua_pop(L, 8);System.Diagnostics.Debug.Assert(top_enter == LuaAPI.lua_gettop(L));
}

相比于tolua只使用两个table,xlua使用了若干的table来辅助索引查找C#的方法和成员。从代码中可以看出,cls_meta,cls_field,cls_getter和cls_setter是用直接给类访问用的,比如一些静态的方法与成员,lua层可以通过namespace和类名直接访问。而相应地,obj_meta,obj_field,obj_getter和obj_setter是给userdata访问用的,对应C#层实例方法与成员。从命名中也可看出,field对应的是C#的字段和方法,getter对应的是C#的get属性,setter对应的是set属性,meta就是对外设置的metatable了。cls_meta中包含__index__newindex__call这三个元方法,这样lua层就可以通过类名创建一个C#对象;obj_meta中包含__index__newindex__gc__tostring这四个元方法,并且它就是userdata的type_id。__index__newindex这两个元方法,还会通过registry表,记录对应的type,来进行额外的缓存,这么做的目的主要是为了基类查找,xlua不像tolua一样,嵌套使用多个metatable来实现继承机制。

那么field,getter,setter这三种table是如何跟meta进行关联的呢?xlua使用了一种非常巧妙的机制,以userdata的__index为例,它其实对应着一个函数,这个函数使用包含field,getter,setter这三种table在内,以及其他的一些参数,作为upvalue来引用。

LUA_API int gen_obj_indexer(lua_State *L) {lua_pushnil(L);lua_pushcclosure(L, obj_indexer, 7);return 0;
}

obj_indexer这个函数持有了7个upvalue,是有点多,注释里也标明了每个upvalue的用途:

//upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
//param   --- [1]: obj, [2]: key
LUA_API int obj_indexer(lua_State *L) {	if (!lua_isnil(L, lua_upvalueindex(1))) {lua_pushvalue(L, 2);lua_gettable(L, lua_upvalueindex(1));if (!lua_isnil(L, -1)) {//has methodreturn 1;}lua_pop(L, 1);}if (!lua_isnil(L, lua_upvalueindex(2))) {lua_pushvalue(L, 2);lua_gettable(L, lua_upvalueindex(2));if (!lua_isnil(L, -1)) {//has getterlua_pushvalue(L, 1);lua_call(L, 1, 1);return 1;}lua_pop(L, 1);}if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) {lua_pushvalue(L, lua_upvalueindex(6));lua_pushvalue(L, 1);lua_pushvalue(L, 2);lua_call(L, 2, 1);return 1;}if (!lua_isnil(L, lua_upvalueindex(3))) {lua_pushvalue(L, lua_upvalueindex(3));lua_pushvalue(L, 1);lua_pushvalue(L, 2);lua_call(L, 2, 2);if (lua_toboolean(L, -2)) {return 1;}lua_pop(L, 2);}if (!lua_isnil(L, lua_upvalueindex(4))) {lua_pushvalue(L, lua_upvalueindex(4));while(!lua_isnil(L, -1)) {lua_pushvalue(L, -1);lua_gettable(L, lua_upvalueindex(5));if (!lua_isnil(L, -1)) // found{lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]lua_pop(L, 1);break;}lua_pop(L, 1);lua_getfield(L, -1, "BaseType");lua_remove(L, -2);}lua_pushnil(L);lua_replace(L, lua_upvalueindex(4));//base = nil}if (!lua_isnil(L, lua_upvalueindex(7))) {lua_settop(L, 2);lua_pushvalue(L, lua_upvalueindex(7));lua_insert(L, 1);lua_call(L, 2, 1);return 1;} else {return 0;}
}

我们着重看一下第4个upvalue的情况,走到这里说明在当前类中没有查找到,例子中的GetComponent方法是在Component类里,在LuaBehaviour类里自然是查找不到的,那么就需要不断地往父类查找。第4个upvalue是当前类的基类类型base type,第5个upvalue就是缓存了当前所有type的__index元方法函数,那么自然而然就要去这个缓存中查找base type的__index元方法,然后把事情直接交给它做就好了,这其实就是一个递归的做法。为了避免下次还要从缓存中查找基类,这里直接把第4个upvalue置为空,然后把基类的__index元方法缓存到第7个upvalue上。

那问题来了,我们之前提到xlua是delay wrap的,在访问C#对象的时候,它的基类信息很可能还没wrap到lua层。所以这里也需要获取一下基类的type_id。在从缓存中获取__index元方法时,代码中使用的是:

lua_gettable(L, lua_upvalueindex(5));

lua_gettable是会触发metatable的,这个缓存table在xlua初始化时就设置了一个metatable:

LuaAPI.lua_newtable(rawL); //metatable of indexs and newindexs functions
LuaAPI.xlua_pushasciistring(rawL, "__index");
LuaAPI.lua_pushstdcallcfunction(rawL, StaticLuaCallbacks.MetaFuncIndex);
LuaAPI.lua_rawset(rawL, -3);LuaAPI.xlua_pushasciistring(rawL, Utils.LuaIndexsFieldName);
LuaAPI.lua_newtable(rawL);
LuaAPI.lua_pushvalue(rawL, -3);
LuaAPI.lua_setmetatable(rawL, -2);
LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);

因此如果基类信息还没wrap,就会触发到C#层的MetaFuncIndex方法:

public static int MetaFuncIndex(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);Type type = translator.FastGetCSObj(L, 2) as Type;if (type == null){return LuaAPI.luaL_error(L, "#2 param need a System.Type!");}translator.GetTypeId(L, type);LuaAPI.lua_pushvalue(L, 2);LuaAPI.lua_rawget(L, 1);return 1;}catch (System.Exception e){return LuaAPI.luaL_error(L, "c# exception in MetaFuncIndex:" + e);}
}

这个函数首先会从lua层获取当前要wrap的type,生成唯一的type_id,并把类型信息wrap到lua层,然后再使用一次rawget把__index方法放回lua层,这样lua层就可以继续递归查找了。在例子中,想要调用到GetComponent得沿着LuaBehaviour=>MonoBehaviour=>Behaviour=>Component这条链一直查找3次才能找到。

最后,push到lua层的这些C#函数,都是使用PushFixCSFunction这个方法完成的,这个方法把push到lua层的函数统一放到一个list中管理,实际调用时根据list中的索引,触发具体的某个函数:

internal void PushFixCSFunction(RealStatePtr L, LuaCSFunction func)
{if (func == null){LuaAPI.lua_pushnil(L);}else{LuaAPI.xlua_pushinteger(L, fix_cs_functions.Count);fix_cs_functions.Add(func);LuaAPI.lua_pushstdcallcfunction(L, metaFunctions.FixCSFunctionWraper, 1);}
}static int FixCSFunction(RealStatePtr L)
{try{ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1));LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx);return func(L);}catch (Exception e){return LuaAPI.luaL_error(L, "c# exception in FixCSFunction:" + e);}
}

推测这么做的原因可能是为了少一些MonoPInvokeCallback吧:)

相关文章:

xlua源码分析(二)lua Call C#的无wrap实现

xlua源码分析(二)lua Call C#的无wrap实现 上一节我们主要分析了xlua中C# Call lua的实现思路,本节我们将根据Examples 03_UIEvent,分析lua Call C#的底层实现。例子场景里有一个简单的UI面板,面板中包含一个input fie…...

MySql优化经验分享

一条sql的具体执行过程 连接 我们怎么查看MySQL当前有多少个连接? 可以用show status命令,模糊匹配Thread, Show global status like "Thread%" show global variables like wait timeout;—非交互式超时时间,如JDBC…...

【Linux】:使用git命令行 || 在github创建项目 || Linux第一个小程序——进度条(进阶版本)

在本章开始之前还是先给大家分享一张图片 这是C的笔试题 感兴趣的同学可以去试一试 有难度的哟 也可以直接在牛客网直接搜索这几道题目哈 好了今天我们正式进入我们的正题部分 🕖1.使用git命令行 安装git yum install git🕠2.在github创建项目 使用…...

Kotlin apply 交换两个数

代码: fun main() {var a 1var b 2a b.apply {b aprintln("$b")println("$this")}println("$a $b") }打印结果: 1 2 2 1原理分析: /*** Calls the specified function [block] with this value as its r…...

Android jetpack : Navigation 导航 路由 、 单个Activity嵌套多个Fragment的UI架构方式

Android Navigation 如何动态的更换StartDestination &&保存Fragment状态 Navigation(一)基础入门 google 官网 : Navigation 导航 路由 讨论了两年的 Navigation 保存 Fragment 状态问题居然被关闭了 Navigation是一种导航的概念,即把Activ…...

【react】在react中祖父、父亲、孙子组件层层解构其余属性props时报错children.forEach is not function

起因 报错children.forEacht is not function 分析原因 由于地址组件本身存在options,此时父组件又传递…otherProps,且解构了父级组件的otherProps,其中others解构后的属性就有options,因此产生了属性冲突,导致属性…...

P9831 [ICPC2020 Shanghai R] Gitignore

P9831 [ICPC2020 Shanghai R] Gitignore - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 只看题意翻译这道题是做不出来的,还要去看英文里面的规定(这里就不放英文了),主要问题是不要公用子文件夹。 例如: 1 / a / 2 2 / a / 3…...

LinkList集合方法(自写)

在使用以下方法时需要定义一个LinkNode类来定义变量,new一个新对象进行调用,输出时需要定义输出方法 public class ListNode {int value;ListNode next;//public ListNode(int value) {this.value value;}public String toString(){return "ListN…...

Ansible playbook自动化运维工具详解

Ansible playbook自动化运维工具详解 一、playbook的相关知识1.1、playbook 的简介1.2、playbook的 各部分组成 二、基础的playbook剧本编写实例三、 playbook的定义、引用变量3.1、基础变量的定义与引用3.2、引用fact信息中的变量 四、playbook中的when条件判断和变量循环使用…...

图像切分:将一张长图片切分为指定长宽的多张图片

1.需求 比如有一张很长的图片其大小为宽度779,高度为122552,那我想把图片切分为779乘以1280的格式。 步骤如下: 使用图像处理库(如PIL或OpenCV)加载原始图片。确定子图片的宽度和高度。计算原始图片的宽度和高度&am…...

ROS学习笔记(5):ros_control

1.ros_control简介 ros_control - ROS Wiki ros_control是为ROS提供的机器人控制包,包含一系列控制器接口、传动装置接口、控制器工具箱等,有效帮助机器人应用功能包快速落地,提高开发效率。 2.ros_control框架 ros_control总体框架: 针对…...

《008.Springboot+vue之自习室选座系统》

[火]《008.Springbootvue之自习室选座系统》 项目简介 [1]本系统涉及到的技术主要如下: 推荐环境配置:DEA jdk1.8 Maven MySQL 前后端分离; 后台:SpringBootMybatisredis; 前台:vueElementUI; [2]功能模块展示: 前端…...

道可云元宇宙每日资讯|5G数智新时代元宇宙发展论坛在厦门举办

道可云元宇宙每日简报(2023年11月6日)讯,今日元宇宙新鲜事有: 5G数智新时代元宇宙发展论坛在厦门举办 3日,由2023年中国金鸡百花电影节执委会主办、厦门电影节有限公司协办的“5G数智新时代元宇宙发展论坛暨‘中国白德…...

使用 Go 写入文件

在本教程中,我们将学习如何使用 Go 将数据写入文件。我们还将学习如何同时写入文件。 本教程有以下部分 将字符串写入文件将字节写入文件逐行将数据写入文件附加到文件同时写入文件 由于 Playground 不支持文件操作,请在本地系统中运行本教程的所有程…...

调用DeleteLocalRef的正确姿势

做安卓jni相关开发的总会在涉及到jni变量释放时怀疑人生,what? where? when? who? why? how? how much? 最近碰到一个比较奇怪的问题,有一个jni方法的耗时在随着调用次数的增加而上涨,但是没有明显的内存泄漏,经过我缜密分…...

抖音小店从0到1起店流程,实操经验分享!

我是电商珠珠 很多人在开店之后,并不知道怎么做。往往会有人跑来问我说,开店之后怎么做啊,流程方面我还不是很熟悉啊等等。 这份起店流程备好了,将来对你有用。 第一步,店铺基础设置 在店铺开好之后,不…...

MySQL权限

权限 MySQL 允许客户端用户连接到服务器并访问服务器管理数据,MySQL 用户权限系统的主要功能是对给定主机连接的用户进行身份验证,并将该用户与数据库的权限相关联。 在 MySQL8 之前,授权表使用 MyISAM 并且是非事务性的,在 MyS…...

Nginx服务器安装证书并启用SSL(acme.sh)

前提 您已购置vps服务器,例如阿里云全球站ecs、AWS EC2、Azure VM、GCP Compute等安全组已开启80、443端口,且访问源设置为0.0.0.0/0域名已设置A记录指向当前操作服务器,若您使用aws ec2,有公有 IPv4 DNS,可供使用 安…...

c++实现观察者模式

前言 我觉得这是最有意思的模式&#xff0c;其中一个动&#xff0c;另外的自动跟着动。发布-订阅&#xff0c;我觉得很巧妙。 代码 头文件 #pragma once #include<vector> #include<string> #include<iostream>// 抽象观察者 class Aobserver { public:v…...

C 语言左移位操作在kernel驱动子系统中的特殊用途

文章目录 前言一、C语言左移位操作介绍1. 左移位二、左移位操作在kernel 驱动子系统中的应用1. 左移位操作在 V4L2, Media 子系统中的应用实例2.左移位操作在 DRM 子系统中的应用实例2.1 左移位操作在struct drm_crtc 中的应用2.2 左移位操作在struct drm_encoder 中的应用总结…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

AtCoder 第409​场初级竞赛 A~E题解

A Conflict 【题目链接】 原题链接&#xff1a;A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串&#xff0c;只有在同时为 o 时输出 Yes 并结束程序&#xff0c;否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...

在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module

1、为什么要修改 CONNECT 报文&#xff1f; 多租户隔离&#xff1a;自动为接入设备追加租户前缀&#xff0c;后端按 ClientID 拆分队列。零代码鉴权&#xff1a;将入站用户名替换为 OAuth Access-Token&#xff0c;后端 Broker 统一校验。灰度发布&#xff1a;根据 IP/地理位写…...

在四层代理中还原真实客户端ngx_stream_realip_module

一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡&#xff08;如 HAProxy、AWS NLB、阿里 SLB&#xff09;发起上游连接时&#xff0c;将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后&#xff0c;ngx_stream_realip_module 从中提取原始信息…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...

上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式

简介 在我的 QT/C 开发工作中&#xff0c;合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式&#xff1a;工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...

ArcPy扩展模块的使用(3)

管理工程项目 arcpy.mp模块允许用户管理布局、地图、报表、文件夹连接、视图等工程项目。例如&#xff0c;可以更新、修复或替换图层数据源&#xff0c;修改图层的符号系统&#xff0c;甚至自动在线执行共享要托管在组织中的工程项。 以下代码展示了如何更新图层的数据源&…...

边缘计算网关提升水产养殖尾水处理的远程运维效率

一、项目背景 随着水产养殖行业的快速发展&#xff0c;养殖尾水的处理成为了一个亟待解决的环保问题。传统的尾水处理方式不仅效率低下&#xff0c;而且难以实现精准监控和管理。为了提升尾水处理的效果和效率&#xff0c;同时降低人力成本&#xff0c;某大型水产养殖企业决定…...