lua 给userdata设置元表_UE4热更新:基于UnLua的Lua编程指南
本片文章搬运自我自己的博客:原文链接: UE4热更新:基于UnLua的Lua编程指南 作者: ZhaLiPeng
UE使用的是C++这种编译型语言,在编译之后就成了二进制,只有通过玩家重新安装才能打到更新游戏的目的。但是对于游戏业务而言,对于需求调整和bug修复时间要求非常迫切,频繁地让玩家更新App是不能接受的,游戏项目一般使用Lua作为游戏业务的脚本语言,是为了把运行时不可变的C++代码变成运行时可更新的Lua代码。
UE官方没有提供Lua的支持,但是腾讯开源了UnLua,在我当前的项目使用了,这两天我梳理了一下UnLua的资料(主要是官方文档、issus、宣讲PPT),加上自己测试UnLua写了一个小Demo的感悟,形成了本篇UE结合UnLua的编程指南,主要是总结使用UnLua来写业务的一些基本方法和坑,方便查看,本篇文章会持续更新。
另外,Lua文件打包成Pak可以用我之前开源的UE资源打包工具:hxhb/HotPatcher,而且我基于UnLua自己修改了一个版本,添加了一些额外的优化,源码集成了Luasocket/Luapanda/
lpeg
/Sproto
/Luacrypt
库,可以直接使用LuaPanda调试,Github地址为:hxhb/debugable-unlua.
PS:我建了一个QQ群(958363331),可以交流一下UE4热更新的相关问题,或者对HotPatcher有什么反馈可以在群里提。
参考资料:
- UnLua_UE4下的Lua脚本插件PPT
- UnLua_Programming_Guide_EN
- Programming in Lua,1th
- Tencent/UnLua/issus
UnLua注意事项
这些是UnLua官方仓库里我摘录出来的一些可能有坑的地方。
- UnLua不支持多State,所以在PIE模式下运行多个Client会有问题。issus/78
- 不要在Lua里访问蓝图定义的结构体。issues/119 / issus/40
- UnLua里使用
self.Super
不会递归向下遍历继承层次中的所有基类。issus/131 - 非dynamic delegate不支持。issus/128
- 不可以直接导出类的static成员,但可以为它封装一个static方法。issus/22
- 不支持绑定lua脚本到对象实例,NewObject指定的脚本是绑定到UCLASS的。issus/134
调用父类函数
在UEC++中,当我们重写了一个父类的虚函数,可以通过调用Super::
来指定调用父类实现,但是在Lua中不同。
self.Super.ReceiveBeginPlay(self)
在Lua使用self.Super
来调用父类的函数。
注意:UnLua里的Super只是简单模拟了“继承”语义,在继承多层的情况下会有问题,Super不会主动向下遍历Super。“父类”不是Class()返回的表的元表,只设置在Super这个field上(元表在插件的c++代码里定义了,这个过程已经固化)。
对于基类的基类
的Super调用:
a.lua
local a = Class()function a:test()
endreturn a--------------------------------------b.lua
local b = Class("a")--------------------------------------c.lua
local c = Class("b")
function a:test()c.Super.Super.test(self)
end
该问题摘录于UnLua的issus:Class内的Super的使用问题
调用被覆写的方法
注意:Lua和原来的类并不是继承关系,而是依附关系,lua依赖于蓝图或者C++的类。Lua覆写的类的函数,相当于给当前类的函数换了一个实现。
当在Lua中重写了一个附属类的UFUNCTION函数时,可以通过下列方法调用,有点类似于Super
但要写成Overridden
:
function BP_Game_C:ReceiveBeginPlay()self.Overridden.ReceiveBeginPlay(self)
end
注意一定要传self
进去,不然Unlua调用的时候执行的参数检查会Crash,在UnLua中调用UE函数有点类似于拿到成员函数的原生指针,必须要手动传this进去。
调用UE的C++函数
UnLua在编译时可以开启是否启用UE的namespace(也就是UE的函数都需要加UE4前缀)。
调用方法为:
UE4.UKismetSystemLibrary.PrintString(self,"HelloWorld")
参数与C++调用的相匹配(具有默认参数的同样可以不写):
UKismetSystemLibrary::PrintString(this,TEXT("Hello"))
覆写多返回值的函数
蓝图
覆写的lua代码:
function LoadingMap_C:GetName(InString)UE4.UKismetSystemLibrary.PrintString(self,InString)return true,"helloworld"
end
C++
因为C++的多返回值是通过传递引用参数进去实现的,所以在Lua中这个不太一样。
如下面的C++函数:
// .h
UFUNCTION(BlueprintCallable, Category = "GameCore|Flib|GameFrameworkStatics", meta = (CallableWithoutWorldContext, WorldContext = "WorldContextObject"))
static bool LuaGetMultiReturnExample(UObject* WorldContextObject, FString& OutString, int32& OutInt, UObject*& OutGameInstance);
// .cpp
bool UFlibGameFrameworkStatics::LuaGetMultiReturnExample(UObject* WorldContextObject, FString& OutString, int32& OutInt, UObject*& OutGameInstance)
{bool bStatus = false;if (WorldContextObject){OutString = TEXT("HelloWorld");OutInt = 1111;OutGameInstance = UGameplayStatics::GetGameInstance(WorldContextObject);bStatus = true;}return bStatus;
}
这个函数接收WorldContextObject
的参数,并接收FString
/int32
/UObject*
这三个类型的引用类型,并返回一个bool,那么这个函数该怎么在lua中接收这些参数值的?
local ret1,ret2,ret3,ret4 = UE4.UFlibGameFrameworkStatics.LuaGetMultiReturnExample(self,nil,nil,nil)
这种local ret1,ret2=func()
的这种写法是Lua里的规则,可以看Programming in Lua,4th的第六章。
注意:接收引用参数的返回值和真正函数返回值的顺序是:ret1,ret2,ret3都是引用参数,最终函数的返回bool是最后一个ret4。
非const引用参数作为返回值需要注意的问题
注意:当调用UFUNCTION函数时,非const引用参数可以忽略,但是非UFUNCTION而是静态导出的函数则不行,因为UnLua对静态导出的函数有参数个数检查。
而且,使用引用作为返回参数的的使用方式需要考虑到下面两种情况:
- 非const引用作为纯输出
void GetPlayerBaseInfo(int32 &Level, float &Health, FString &Name)
{Level = 7;Health = 77;Name = "Marcus";
}
这种情况下返回值和传入值是没有任何关系的。在lua中可以这么使用:
local level,heath,name = self:GetPlayerBaseInfo(0,0,"");
- 非const引用参数既作为输入又作为输出
void GetPlayerBaseInfo(int32 &Level, float &Health, FString &Name)
{Level += 7;Health += 77;Name += "Marcus";
}
在这种情况下,返回值和输入是有直接关系的,所以不能像情况1中那样使用:
local level,heath,name
level,heath,name = self:GetPlayerBaseInfo(level,heath,name);
在这种情况下,在lua里调用传入的参数和返回的参数是都必须要传递和接收的,这样才会有正常的行为,如果不接收返回值:
local level,heath,name
self:GetPlayerBaseInfo(level,heath,name);
level,heath,name
这些传进去的对象的值并不会像C++中传递的引用那样值会改变。
所以函数怎么调用还是要看函数里是怎么写的。
这个在UnLua的issus里有提到:issus/25
获取TScriptInterface接口对象
当我们在C++中获得一个接口时,获得的类型是TScriptInterface<>
类型,本来以为还要自己导出TScriptInterface
才可以拿到接口,但是发现并不是这样,UnLua里可以直接拿到TScriptInterface<>
就像普通的UObject对象:
如下面这样一个函数:
UFUNCTION(BlueprintCallable, Category = "GameCore|Flib|GameFrameworkStatics", meta = (CallableWithoutWorldContext,WorldContext="WorldContextObject"))
static TScriptInterface<IINetGameInstance> GetNetGameInstance(UObject* WorldContextObject);
在Lua里调用:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
这个得到的类型在C++里是TScriptInterface<>
但在Lua里得到的就是该接口的UObject对象。
调用成员函数
上面讲了怎么得到TScriptInterface
的接口(在lua里得到的其实就是该接口的UObject),那么怎么通过它来调用接口的函数呢?有三种方法。
// This class does not need to be modified.
UINTERFACE(BlueprintType,MinimalAPI)
class UINetGameInstance : public UIBaseEntityInterface
{GENERATED_BODY()
};class GWORLD_API IINetGameInstance
{GENERATED_BODY()
public:UFUNCTION(Category = "GameCore|GamePlayFramework")virtual bool FindSubsystem(const FString& InSysName,TScriptInterface<IISubsystem>& OutSubsystem)=0;
};
通过对象调用函数
- 可以通过拿到实现接口的对象然后通过该对象调用函数(使用lua的
:
操作符):
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
local findRet1,findRet2 = GameInstance:FindSubsystem("TouchController")
指定类和函数名调用
- 也可以直接通过指定实现该接口的类型名字来调用,就像函数指针,需要把调用该函数的对象传递进去:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
local findRet1,findRet2 = UE4.UNetGameInstance.FindSubsystem(GameInstance,"TouchController")
指定接口的类和函数名调用
- 以及通过接口的类型调用(因为接口也是UClass,接口中的函数也都标记了UFUNCTION):
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
local findRet1,findRet2 = UE4.UINetGameInstance.FindSubsystem(GameInstance,"TouchController")
获取UClass
lua中获取uclass可以用于创建对象,其方法为:
local uclass = UE4.UClass.Load("/Game/Core/Blueprints/AI/BP_AICharacter.BP_AICharacter_C")
Load的路径是该类的PackagePath
。
如,在lua中加载UMG的类然后创建并添加至视口:
function LoadingMap_C:ReceiveBeginPlay()local UMG_C = UE4.UClass.Load("/Game/Test/BPUI_TestMain.BPUI_TestMain_C")local UMG_TestMain_Ins = UE4.UWidgetBlueprintLibrary.Create(self,UMG_C)UMG_TestMain_Ins:AddToViewport()
end
LoadObject
把资源加载到内存:
local Object = LoadObject("/Game/Core/Blueprints/AI/BT_Enemy")
比如加载某个材质球给模型:
function Cube3_Blueprint_C:ReceiveBeginPlay()local MatIns = LoadObject("/Game/TEST/Cube_Mat_Ins")UE4.UPrimitiveComponent.SetMaterial(self.StaticMeshComponent,0,MatIns)
end
注:LuaObject在UnLua里对应的是LoadObject<Object>
:
int32 UObject_Load(lua_State *L)
{// ...UObject *Object = LoadObject<UObject>(nullptr, *ObjectPath);
// ...
}
创建对象
注意:Lua中创建的对象使用动态绑定是绑定到该类的UCLASS上,并不是绑定到该New出来的实例。
UnLua中对NewObject
处理的代码为Global_NewObject
:
FScopedLuaDynamicBinding Binding(L, Class, ANSI_TO_TCHAR(ModuleName), TableRef);
UObject *Object = StaticConstructObject_Internal(Class, Outer, Name);
SpawnActor
Lua中SpawnActor以及动态绑定:
local WeaponClass = UE4.UClass.Load("/Game/Core/Blueprints/Weapon/BP_DefaultWeapon.BP_DefaultWeapon")
local NewWeapon = World:SpawnActor(WeaponClass, self:GetTransform(), UE4.ESpawnActorCollisionHandlingMethod.AlwaysSpawn, self, self, "Weapon.BP_DefaultWeapon_C")
NewObject
lua中调用NewObject以及动态绑定:
local ProxyObj = NewObject(ObjClass, nil, nil, "Objects.ProxyObject")
UnLua中的NewObject
可以接收四个参数,依次是:创建的UClass、Outer、Name,以及动态绑定的Lua脚本。
Component
注意:原版UnLua只可以加载BP的UClass,这个需要做改动(修改
LuaLib_Class.cpp
中的UClass_Load
函数,检测传入是C++类时把添加_C
后缀的逻辑去掉), 而且创建Component时也需要对其调用OnComponentCreated
和RegisterComponent
,这两个函数不是UFUNCTION,需要手动导出。
导出ActorComponent中OnComponentCreated
和RegisterComponent
等函数:
// Export Actor Component
BEGIN_EXPORT_REFLECTED_CLASS(UActorComponent)ADD_FUNCTION(RegisterComponent)ADD_FUNCTION(OnComponentCreated)ADD_FUNCTION(UnregisterComponent)ADD_CONST_FUNCTION_EX("IsRegistered",bool, IsRegistered)ADD_CONST_FUNCTION_EX("HasBeenCreated",bool, HasBeenCreated)
END_EXPORT_CLASS()
IMPLEMENT_EXPORTED_CLASS(UActorComponent)
则使用时与C++的使用方法一致:
local StaticMeshClass = UE4.UClass.Load("/Script/Engine.StaticMeshComponent")
local MeshObject = LoadObject("/Engine/VREditor/LaserPointer/CursorPointer")
local StaticMeshComponent= NewObject(StaticMeshClass,self,"StaticMesh")
StaticMeshComponent:SetStaticMesh(MeshObject)
StaticMeshComponent:RegisterComponent()
StaticMeshComponent:OnComponentCreated()
self:ReceiveStaticMeshComponent(StaticMeshComponent)
-- StaticMeshComponent:K2_AttachToComponent(self.StaticMeshComponent,"",EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget)
UE4.UStaticMeshComponent.K2_AttachToComponent(StaticMeshComponent,self.StaticMeshComponent,"",EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget,EAttachmentRule.SnapToTarget)
UMG
创建UMG首先需要获取到UI的UClass,然后使用UWidgetBlueprintLibrary::Create
来创建,与C++一致:
local UMG_C = UE4.UClass.Load("/Game/Test/BPUI_TestMain.BPUI_TestMain_C")
local UMG_TestMain_Ins = UE4.UWidgetBlueprintLibrary.Create(self,UMG_C)
UMG_TestMain_Ins:AddToViewport()
绑定代理
动态多播代理
在C++代码中写了一个动态多播代理:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FGameInstanceDyDlg, const FString&,InString);// in class
UPROPERTY()
FGameInstanceDyDlg GameInstanceDyDlg;
在lua中绑定,可以绑定到lua的函数:
local GameInstance = UE4.UFlibGameFrameworkStatics.GetNetGameInstance(self);
GameInstance.GameInstanceDyMultiDlg:Add(self,LoadingMap_C.BindGameInstanceDyMultiDlg)-- test bind dynamic multicast delegate lua func
function LoadingMap_C:BindGameInstanceDyMultiDlg(InString)UE4.UKismetSystemLibrary.PrintString(self,InString)
end
同样也可以对该代理进行调用、清理、移除:
-- remove
GameInstance.GameInstanceDyMultiDlg:Remove(self,LoadingMap_C.BindGameInstanceDyDlg)
-- Clear
GameInstance.GameInstanceDyMultiDlg:Clear()
-- broadcast
GameInstance.GameInstanceDyMultiDlg:Broadcast("66666666")
动态代理
C++中有如下动态代理声明:
DECLARE_DYNAMIC_DELEGATE_OneParam(FGameInstanceDyDlg,const FString&,InString);// in class
UPROPERTY()
FGameInstanceDyDlg GameInstanceDyDlg;
在lua中绑定:
GameInstance.GameInstanceDyDlg:Bind(self,LoadingMap_C.BindGameInstanceDyMultiDlg)-- test bind dynamic delegate lua func
function LoadingMap_C:BindGameInstanceDyDlg(InString)UE4.UKismetSystemLibrary.PrintString(self,InString)
end
动态代理支持Bind
/Unbind
/Execute
操作:
-- bind
GameInstance.GameInstanceDyDlg:Bind(self,LoadingMap_C.BindGameInstanceDyMultiDlg)
-- UnBind
GameInstance.GameInstanceDyDlg:Unbind()
-- Execute
GameInstance.GameInstanceDyDlg:Execute("GameInstanceDyMultiDlg")
不支持非Dynamic Delegate
因为BindStatic
/BindRaw
/BindUFunction
这些都是模板函数,UnLua的静态导出方案不支持将他们导出。
官方issus:如何正确静态导出继承自FScriptDelegate的普通委托
使用异步事件
Delay
如果想要使用类似Delay的函数:
/** * Perform a latent action with a delay (specified in seconds). Calling again while it is counting down will be ignored.* * @param WorldContextWorld context.* @param Duration length of delay (in seconds).* @param LatentInfo The latent action.*/
UFUNCTION(BlueprintCallable, Category="Utilities|FlowControl", meta=(Latent, WorldContext="WorldContextObject", LatentInfo="LatentInfo", Duration="0.2", Keywords="sleep"))
static voidDelay(UObject* WorldContextObject, float Duration, struct FLatentActionInfo LatentInfo );
在lua中可以通过协程(coroutine)
来实现:
function LoadingMap_C:DelayFunc(Induration)coroutine.resume(coroutine.create(function(WorldContectObject,duration) UE4.UKismetSystemLibrary.Delay(WorldContectObject,duration)UE4.UKismetSystemLibrary.PrintString(WorldContectObject,"Helloworld")end),self,Induration)
end
就是通过coroutine.create
绑定上一个函数,可以直接在coroutine.create
里写,或者绑定上一个已有的函数:
function LoadingMap_C:DelayFunc(Induration)coroutine.resume(coroutine.create(LoadingMap_C.DoDelay),self,self,Induration)
endfunction LoadingMap_C:DoDelay(WorldContectObject,duration) UE4.UKismetSystemLibrary.Delay(WorldContectObject,duration)UE4.UKismetSystemLibrary.PrintString(WorldContectObject,"Helloworld")
end
但是要注意一点:在绑定已有的lua函数时,传递的参数需要多一个self
,标识调用指定函数的调用者。
coroutine.resume(coroutine.create(LoadingMap_C.DoDelay),self,self,Induration)
这里的第一个self
,就是在通过self
调用LoadingMap_C.DoDelay
,后面的两个参数才作为传递给协程函数的参数。
调用代码为:
function LoadingMap_C:ReceiveBeginPlay()-- 5s后输出HelloWorldself:DelayFunc(5.0)
end
注意:对于直接是UFUNCTION但是带有
FLatentActionInfo
的函数可以直接使用上面的方法,但是对于UE封装的异步节点,不是函数而是一个类的节点需要自己导出。
AsyncLoadPrimaryAsset
在C++里可以使用UAsyncActionLoadPrimaryAsset::AsyncLoadPrimaryAsset
来异步加载资源:
/** * Load a primary asset into memory. The completed delegate will go off when the load succeeds or fails, you should cast the Loaded object to verify it is the correct type.* If LoadBundles is specified, those bundles are loaded along with the asset*/
UFUNCTION(BlueprintCallable, meta=(BlueprintInternalUseOnly="true", Category = "AssetManager", AutoCreateRefTerm = "LoadBundles", WorldContext = "WorldContextObject"))
static UAsyncActionLoadPrimaryAsset* AsyncLoadPrimaryAsset(UObject* WorldContextObject, FPrimaryAssetId PrimaryAsset, const TArray<FName>& LoadBundles);
想要在Lua中使用的话需要把FPrimaryAssetId
这个结构导出:
#include "UnLuaEx.h"
#include "LuaCore.h"
#include "UObject/PrimaryAssetId.h"BEGIN_EXPORT_CLASS(FPrimaryAssetId,const FString&)ADD_FUNCTION_EX("ToString",FString, ToString)ADD_STATIC_FUNCTION_EX("FromString",FPrimaryAssetId, FromString,const FString&)ADD_FUNCTION_EX("IsValid", bool, IsValid)
END_EXPORT_CLASS()
IMPLEMENT_EXPORTED_CLASS(FPrimaryAssetId)
然后就可以在Lua中使用了,如异步加载关卡资源,加载完成后打开:
function Cube_Blueprint_C:ReceiveBeginPlay()local Map = UE4.FPrimaryAssetId("Map:/Game/Test/LoadingMap")local AsyncActionLoadPrimaryAsset = UE4.UAsyncActionLoadPrimaryAsset.AsyncLoadPrimaryAsset(self,Map,nil)AsyncActionLoadPrimaryAsset.Completed:Add(self,Cube_Blueprint_C.ReceiveLoadedMap)AsyncActionLoadPrimaryAsset:Activate()
endfunction Cube_Blueprint_C:ReceiveLoadedMap(Object)UE4.UGameplayStatics.OpenLevel(self,"/Game/Test/LoadingMap",true)
end
lua 给userdata设置元表_UE4热更新:基于UnLua的Lua编程指南相关推荐
- lua 给userdata设置元表_提高Lua语言开发效率的简单方法
概述 首先,lua是一门高效的(efficient).轻量级(lightweight)的嵌入式脚本语言(embeddable scripting language),这是它的官方网站的标语. 其次,l ...
- lua 给userdata设置元表_lua学习之复习汇总篇
第六日笔记 1. 基础概念 程序块 定义 在 lua 中任何一个源代码文件或在交互模式中输入的一行代码 程序块可以是任意大小的 程序块可以是一连串语句或一条命令 也可由函数定义构成,一般将函数定义写在 ...
- UE4热更新:基于UnLua的Lua编程指南
UE4热更新:基于UnLua的Lua编程指南 https://imzlp.me/posts/36659/ https://imzlp.me/posts/36659/ Z's Blog 首页 归档 分类 ...
- unity 热更新:C#与Lua相互调用(转载)
一.基本原理 简单地说,c#调用lua, 是c# 通过Pinvoke方式调用了lua的dll(一个C库),然后这个dll执行了lua脚本. ULua = Lua + LuaJit(解析器.解释器) + ...
- 腾讯开源手游热更新方案,Unity3D下的Lua编程
写在前面 \\ xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能.易用性.扩展性而广受好评.现在,腾讯已经将xLua开源到GitHub. ...
- 【文集】Unity的热更新
热更新对游戏很重要,但是unity自身是不支持热更新的,因此这方面还是有不少方案和文章 Unity热更新之LuaInterface(上篇) Unity热更新之LuaInterface(下篇) Lua ...
- 【热更新】游戏热更新方案
游戏热更新方案 热更新演化 热更新方案 [1] 进程切换 1.1 利用fork.exec切换 1.2 利用网关切换 1.3 微服务 - 进程切换注意要点 [2] 动态库替换 [3] 脚本语言热更新 热 ...
- UE4热更新:需求分析与方案设计
UE4热更新:需求分析与方案设计 https://zhuanlan.zhihu.com/p/141360286 老样子,本篇文章搬运自我的博客,原文链接: UE4热更新:需求分析与方案设计 作者: 查 ...
- Unity中的热更新的基础知识,Xlua与ILRuntime基础知识
1.什么是热更新 热更新是指在不需要重新编译打包游戏的情况下,在线更新游戏中的一些非核心代码和资源,比如活动运营和打补丁.热更新分为资源热更新和代码热更新两种,代码热更新实际上也是把代码当成资源的一种 ...
最新文章
- 《需求工程——软件建模与分析》阅读笔记二
- java基础输入输出语句
- xvid 详解 代码分析 编译等
- 深度学习的实用层面 —— 1.7 理解Dropout
- 设计模式之二装饰者模式
- python 文本框内容变化_监听文本输入框内容值的改变——4种方法
- bzoj 1072: [SCOI2007]排列perm(状压dp)
- Android Instant Apps教程
- ios并发会造成什么问题_女生月经不调会引起什么并发症?
- ibatis简例1-用ibator插件自动生成sqlmap
- 四、StackPanel控件
- WDS+MDT全自动部署系统、自动加域、自动计算机取名(SN序列号或MAC地址)
- android高通camera驱动调试,高通Camera模块驱动指南资料
- linux 中 etc fstab目录,Linux学习— /etc/fstab文件详解
- ACM—数论—费马大定理 (数学史上著名的定理)
- 魅族pro5 刷机 android,魅族 PRO5中文Recovery刷机教程
- 如何清理电脑系统缓存
- 微信小程序---显示与隐藏hidden
- DXP中关于PCB及原理绘制那些高级玩意总结
- JAVA洛谷B2117 整理药名
热门文章
- python爬虫爬取雪球网_Python爬虫爬取天天基金网基金排行
- Linux仿真运算集群,fluent DPM Linux计算集群运行报错 - 计算模拟 - 小木虫 - 学术 科研 互动社区...
- Linux pwn入门教程,i春秋linux_pwn入门教程复现之栈溢出基础
- python super详解_Python super 详解
- 华为鸿蒙还会不会推出,华为如果把鸿蒙独立出来,让小米、魅族和蓝绿厂参股进来,会不会超越安卓?...
- [有问有答] 如何用邮件安全地传递密码
- apache2.2.15与PHP5.3.3安装设置完成后,apache启动失败
- 【hdu3294】Girls' research
- nodejs websocket server
- LeetCode Sort Colors