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.Mult
        end,
        
        get_Item = function(self, index)
            return self.list[index + 1]
        end,

        set_Item = function(self, index, value)
            self.list[index + 1] = value
            self:notify({name = index, value = value})
        end,
        
        add_PropertyChanged = function(self, delegate)
            if self.notifylist == nil then
                self.notifylist = {}
            end
            table.insert(self.notifylist, delegate)
            print('add',delegate)
        end,
                                
        remove_PropertyChanged = function(self, delegate)
            for i=1, #self.notifylist do
                if CS.System.Object.Equals(self.notifylist[i], delegate) then
                    table.remove(self.notifylist, i)
                    break
                end
            end
            print('remove', delegate)
        end,

        notify = function(self, evt)
            if self.notifylist ~= nil then
                for i=1, #self.notifylist do
                    self.notifylist[i](self, evt)
                end
            end	
        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"); //constructor
    Debug.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_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
        var 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_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
        RealStatePtr 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_0
        var bridgeType = ce.EmitInterfaceImpl(interfaceType);
        creator = (int reference, LuaEnv luaenv) =>
        {
            return Activator.CreateInstance(bridgeType, new object[] { reference, luaEnv }) as LuaBase;
        };
        interfaceBridgeCreators.Add(interfaceType, creator);
#else
        throw 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方法中,进行最终的反射调用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/160681.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

wpf devexpress 创建布局

模板解决方案 例子是一个演示连接数据库连接程序。打开RegistrationForm.BaseProject项目和如下步骤 RegistrationForm.Lesson1 项目包含结果 审查Form设计 使用LayoutControl套件创建混合控件和布局 LayoutControl套件包含三个主控件&#xff1a; LayoutControl - 根布局…

反激变压器计算方法_笔记

反激变压器计算方法_笔记 匝数比原边电感选定磁芯线圈匝数线径 原视频链接 匝数比 5V 是想要得到的输出电压 0.7V为二极管导通的压降 185Vx根号2是有效值 最大占空比取0.4。得出最小匝数为30。 更改某些值可能得出来的匝数比就不一定是30了&#xff0c; 这其实也是反激变压器…

ubuntu中用docker部署jenkins,并和码云实现自动化部署

1.部署jenkins docker network create jenkins docker run --name jenkins-docker --rm --detach \--privileged --network jenkins --network-alias docker \--env DOCKER_TLS_CERTDIR/certs \--volume jenkins-docker-certs:/certs/client \--volume jenkins-data:/var/jen…

屏蔽bing搜索框的今日热点

中国版的Bing简直比百度还恶心了&#xff0c;“今日热点”要是在搜索设置里关闭了就没法提供搜索建议了&#xff0c;不关吧看着又烦人&#xff0c;就像下图这样。另外还有右上角的下载bing app和Rewards图标也闲着没啥用&#xff0c;Rewards图标还老有小红点&#xff0c;真受不…

6.8完全二叉树的节点个数(LC222-E)

算法&#xff1a; 如果不考虑完全二叉树的特性&#xff0c;直接把完全二叉树当作普通二叉树求节点数&#xff0c;其实也很简单。 递归法&#xff1a; 用什么顺序遍历都可以。 比如后序遍历&#xff08;LRV&#xff09;&#xff1a;不断遍历左右子树的节点数&#xff0c;最后…

Adversarial Attacks on Neural Networks for Graph Data

Adversarial Attacks on Neural Networks for Graph Data----《针对图数据的神经网络的对抗攻击》 论文提出了两个问题&#xff1a; 1、属性图的深度学习模型容易受攻击吗&#xff1f; 2、他们的结果可靠吗&#xff1f; 回答这两个问题需要考虑到GNN的特性&#xff1a; ①关…

2023.11.18 Hadoop之 YARN

1.简介 Apache Hadoop YARN &#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09;是一种新的 Hadoop 资源管理器&#xff0c;它是一个通用资源管理系统和调度平台&#xff0c;可为上层应用提供统一的资源管理和调度。支持多个数据处理框架&…

订阅号和服务号有什么区别

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;我们都知道&#xff0c;服务号一个月只能发4次文章&#xff0c;但是订阅号每天都能发文章。不过在接收消息这一方面&#xff0c;服务号群发的消息有消息提醒&#xff0c;并显示在对话框&#xff1b…

react实现步进器

创建一个步进器组件&#xff0c;包含当前步骤&#xff08;currentStep&#xff09;的状态以及前进和后退的操作&#xff1a; import React, { useState } from react;function Stepper() {const [currentStep, setCurrentStep] useState(1);const handleNext () > {setCu…

torch.nn.functional.log_softmax 函数解析

该函数将输出向量转化为概率分布&#xff0c;作用和softmax一致。 相比softmax&#xff0c;对较小的概率分布处理能力更好。 一、定义 softmax 计算公式&#xff1a; log_softmax 计算公式&#xff1a; 可见仅仅是将 softmax 最外层套上 log 函数。 二、使用场景 log_soft…

Linux通过端口号找到对应的服务及其安装位置

Linux服务器中&#xff0c;通过端口号找到对应的服务及其安装位置&#xff0c;需要两步操作&#xff0c;如下&#xff1a; 第一步&#xff1a;根据端口号&#xff0c;确定对应的进程号&#xff08;以redis服务为例&#xff09; netstat -antup|grep 6379第二步&#xff1a;通…

企业服务器中了babyk勒索病毒怎么办,babyk勒索病毒解密数据集恢复

网络技术的不断发展应用&#xff0c;为企业的生产生活提供了强有力帮助&#xff0c;企业也不断走向数字化办公模式&#xff0c;而对于企业来说&#xff0c;企业计算机存储的数据至关重要&#xff0c;如果不加以保护很容易造成数据丢失&#xff0c;近期&#xff0c;云天数据恢复…

盘点3种Python网络爬虫过程中的中文乱码的处理方法

网络爬虫过程中三种中文乱码的处理方案&#xff0c;希望对大家的学习有所帮助 一、思路 其实解决问题的关键点就是在于一点&#xff0c;就是将乱码的部分进行处理&#xff0c;而处理的方案主要可以从两个方面进行出发。其一是针对整体网页进行提前编码&#xff0c;其二是针对…

C++初阶 日期类的实现(上)

目录 一、前置准备 1.1获得每月的天数 1.2获得每年的天数 1.3构造函数&#xff0c;析构函数和拷贝构造函数 二、日期与天数的,-,,-实现 2.1运算符重载 2.2运算符的实现 2.3-运算符的实现 2.4-运算符的实现 三、&#xff0c;--的实现 3.1前置&#xff0c;后置的实现 …

搭建企业社区,如何激发员工互动?

本文是关于企业内部社区搭建后怎么运营&#xff0c;如何激发员工互动。 作为运营者&#xff0c;我们搭建企业内部员工的目的首先得明确下来&#xff0c;一般都是打造和宣扬企业内部文化&#xff0c;发布公司政策通知和行业动态、组织公司关键节点活动、以及员工经验分享资源分…

“贾维斯”落地国内头部手机厂商? 这个AI助手真顶顶顶顶顶!

一个新的“贾维斯”即将落地国内头部手机厂商&#xff1f; 大家好&#xff0c;我是卖萌酱。 就在近日&#xff0c;2023 OPPO开发者大会正式官宣发布自主训练的大模型AndesGPT全新小布智能助手&#xff0c;算是正式预告国内头部一线手机厂商已经几乎全部完成大模型终端的布局。…

Vue.js2+Cesium1.103.0 十四、绘制视锥,并可实时调整视锥姿态

Vue.js2Cesium1.103.0 十四、绘制视锥&#xff0c;并可实时调整视锥姿态 Demo <template><divid"cesium-container"style"width: 100%; height: 100%;"><divclass"control"style"position: absolute;right: 50px;top: 50px…

SecureCRT的“New line mode“

New line mode选中与不选中啥区别 在SecureCRT中&#xff0c;"New line mode"是一个关键配置项&#xff0c;主要用于解决不同操作系统之间的换行问题。当不选中"New line mode"时&#xff0c;SecureCRT会将接收到的数据按照原样发送&#xff0c;不会对数据…

【giszz笔记】产品设计标准流程【5】

&#xff08;续上回&#xff09; 目录 五、原型设计 1.写在前面的话 2.原型是什么 3.画原型的工具 4.产品经理的复合能力 5.关于原型图 PS&#xff1a;这个系列&#xff0c;主要讨论的是产品设计的一般标准流程。这个流程也许每天都发生在我们的身边&#xff0c;我们也常…

生成式AI模型量化简明教程

在不断发展的人工智能领域&#xff0c;生成式AI无疑已成为创新的基石。 这些先进的模型&#xff0c;无论是用于创作艺术、生成文本还是增强医学成像&#xff0c;都以产生非常逼真和创造性的输出而闻名。 然而&#xff0c;生成式AI的力量是有代价的—模型大小和计算要求。 随着生…