多线程与单线程的区别:

  • 单线程顺序执行:ABCDEFG,HIJKLMN
  • 单线程异步执行:ABC,KLMN,EFGHI,错开执行避免I/O之类的长时间等待卡住线程【WinWait】
  • 多线程:一个进程内可有多个线程ABCDEFG同时执行互不干扰
  • 多进程:创建多个进程ABCDEFG并互相通信,来达到仿多线程的效果。

 

不控制主进程的话,使用方法如下:

新建进程示例,可以是代码,也可以是路径:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
进程1 := ComProcess("C:\Program Files\AutoHotkey\Lib\FindText.ahk")
进程1 := ComProcess("C:\Program Files\AutoHotkey\Lib\FindText.ahk")
进程1 := ComProcess("C:\Program Files\AutoHotkey\Lib\FindText.ahk")

 

在目标进程中调用一个名为 "Function" 的函数:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
进程1.Function(params)
进程1.Function(params)
进程1.Function(params)

 

获取进程1的一个全局变量:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
value := 进程1["VarName"]
value := 进程1.VarName
value := 进程1["VarName"] value := 进程1.VarName
value := 进程1["VarName"]
value := 进程1.VarName

 

对进程1的一个全局变量赋值:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
进程1["VarName"] := value
进程1.VarName := value
进程1["VarName"] := value 进程1.VarName := value
进程1["VarName"] := value
进程1.VarName := value

 

对进程1创建一个Class的实例:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
value := 进程1["__ComProcess"].__New("ClassName", params)
value := 进程1["__ComProcess"].__New("ClassName", params)
value := 进程1["__ComProcess"].__New("ClassName", params)

 

另一个精简但不能共享对象的多进程:多进程代替多线程函数 – 精简版

 

尝试双向通信的多进程库,ComProcess函数用来控制和调用新进程很方便。但是新进程向主进程发送控制,我写了一个Class 变量赋值回调来实现。

 

完整的双向通信示例:

Plain text
复制到剪贴板
Open code in new window
EnlighterJS 3 Syntax Highlighter
#Requires AutoHotkey v1.1.33+
#SingleInstance Force
SetBatchLines -1
Gosub 加载新进程标签
进程1.进程共享变量 := New 变量赋值回调("新进程触发回调函数")
Gui -MinimizeBox -MaximizeBox +AlwaysOnTop
Gui Add, Edit, w300 R2 v通信显示 g同步发送
Gui Show, x850 w330 y400, P主进程 - 互相通信同步演示,请输入文字
Loop {
Sleep 80
MouseGetPos, x, y
ToolTip % "P-主进程持续运算演示-" A_Index, x+10, y-70
}
Return
同步发送:
GuiControlGet, 获取编辑框内容,, 通信显示
进程1.Gui同步更新(获取编辑框内容, "通信显示")
Return
GuiClose:
ExitApp
Return
; F3键做新进程的开、关、一键开关的演示【只有主进程端能够新建进程】
F3::
; 进程1 := ComProcess(新代码1)
进程1 := ""
; 进程1:=(Toggle:=!Toggle) ? ComProcess(新代码1) : ""
Return
新进程触发回调函数(newValue) {
GuiControl,, 通信显示, %newValue%
}
class 变量赋值回调 {
__New(changeCallback) {
this._onChangeCallback := Func(changeCallback)
}
回调变量[] {
get {
return this._value
}
set {
this._value := value
, (this._onChangeCallback && this._onChangeCallback.Call(this._value))
return this._value
}
}
}
加载新进程标签:
新代码1=
( ` %
Gui -MinimizeBox -MaximizeBox +AlwaysOnTop
Gui Add, Edit, w300 R2 v通信显示 g同步发送
Gui Show, x850 w330 y500, F3新进程 - 互相通信同步演示,请输入文字
Loop {
Sleep 80
MouseGetPos, x, y
ToolTip % "F3-新进程持续运算演示-" A_Index, x+10, y-30
}
Return
同步发送:
GuiControlGet, 获取编辑框内容,, 通信显示
进程共享变量.回调变量 := 获取编辑框内容
Return
Gui同步更新(Value, ControlID) {
GuiControl,, %ControlID%, %Value%
}
GuiClose:
ExitApp
Return
)
进程1 := ComProcess(新代码1)
Return
/*
在目标进程中调用一个名为 "Function" 的函数。
进程1.Function(params)
获取进程1的一个全局变量
value := 进程1["VarName"]
value := 进程1.VarName
对进程1的一个全局变量赋值
进程1["VarName"] := value
进程1.VarName := value
创建一个Class的实例
value := 进程1["__ComProcess"].__New("ClassName", params)
已知限制:
如果脚本在运行远程脚本调用的方法时退出,则远程脚本将收到错误。
AutoHotkey 如何在本地与 COM 对象交互以及 AutoHotkey 对象如何通过 COM 接口响应请求也存在一些限制。
变量无法通过 ByRef 传递到远程对象。
有些调用是不明确的;例如,foo.bar 触发器 foo.__Call 进而foo.__Get。
*/
; By dbgba Thank thqby
ComProcess(PathsOrCode, v1Interpreter="", Timeout=30, NoTrayIcon="#NoTrayIcon") {
Static IDispatch, ComProcessNum := 0
, _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, "int64"), "int64"))
, PIDLabel := DllCall("GetCurrentProcessId")
if (v1Interpreter="") {
if A_Is64bitOS
SetRegView 64
RegRead, InstallDir, HKLM\SOFTWARE\AutoHotkey, InstallDir
v1Interpreter := InstallDir="" ? A_AhkPath : InstallDir "\AutoHotkey.exe"
}
if !FileExist(v1Interpreter)
throw Exception("请将参数2的AHK解释器路径设置正确!")
lresult := DllCall("oleacc\LresultFromObject", "Ptr", &IDispatch, "Ptr", 0, "Ptr", &(client := { proxy: 0 }), "Ptr")
if IsObject(PathsOrCode) {
t := PathsOrCode, PathsOrCode := ""
For __, p in t
PathsOrCode .= "`n#include " p
}
v1script := Format("
(
#Persistent
" NoTrayIcon "
class __ComProcess {
__New(name, args*) {
static _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, ""int64""), ""int64""), DllCall(""oleacc\ObjectFromLresult"", ""ptr"", {}, ""ptr"", &IDispatch, ""ptr"", 0, ""ptr*"", idisp := 0), ComObject(9, idisp).proxy := new __ComProcess(), ObjRelease(idisp)), hMapping := DllCall(""kernel32\CreateFileMapping"", ""Ptr"", -1, ""Ptr"", 0, ""UInt"", 0x4, ""UInt"", 0, ""UInt"", 4, ""Str"", ""AHKLoadStatus" PIDLabel . ++ComProcessNum """), pView := DllCall(""kernel32\MapViewOfFile"", ""Ptr"", hMapping, ""UInt"", 0x2, ""UInt"", 0, ""UInt"", 0, ""UInt"", 4)
return IsObject(name) ? new name(args*) : new %name%(args*)
}
__Call(name, args*) {
Global
if (IsObject(args) && %name%!="""")
return __ComProcessVar__ := %name%
return %name%(args*)
}
; __Get(name) COM机制无法触发,故删掉
__Set(name, val) {
Global
return %name% := val
}
__Delete() {
SetTimer __ComProcessExitApp__, -1
return
__ComProcessExitApp__:
ExitApp
}
}
{}
)", lresult, FileExist(PathsOrCode) ? "" : PathsOrCode)
if (v1Interpreter ~= "i)\.dll$") {
if (!DllCall("GetModuleHandle", "str", v1Interpreter) && !DllCall("LoadLibrary", "str", v1Interpreter)) {
DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp)
throw Exception("load AutoHotkey.dll fail")
}
if !(threadID := DllCall(v1Interpreter "\NewThread", "str", v1script "`n" (FileExist(PathsOrCode) ? "#Include " PathsOrCode : ""), "str", "", "str", "", "cdecl uint")) {
DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp)
throw Exception("Failed to load script")
}
_exec := {ProcessID : DllCall("GetCurrentProcessId")}
} else {
if FileExist(PathsOrCode)
_exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /include """ PathsOrCode """ *")
else
_exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /CP0 *")
_exec.StdIn.Write(v1script), _exec.StdIn.Close()
}
Loop % Timeout*1000//15 {
Sleep 15
hMap := DllCall("kernel32\OpenFileMapping", "UInt", 0x2, "Int", 0, "Str", "AHKLoadStatus" PIDLabel . ComProcessNum)
, pView := DllCall("kernel32\MapViewOfFile", "Ptr", hMap, "UInt", 0x2, "UInt", 0, "UInt", 0, "UInt", 4)
} Until ((isLoaded := NumGet(pView+0, 0, "Int"))=0)
if (isLoaded!=0)
Throw Exception("AHK新进程启动超时或故障!")
Sleep 10
return client.proxy
}
#Requires AutoHotkey v1.1.33+ #SingleInstance Force SetBatchLines -1 Gosub 加载新进程标签 进程1.进程共享变量 := New 变量赋值回调("新进程触发回调函数") Gui -MinimizeBox -MaximizeBox +AlwaysOnTop Gui Add, Edit, w300 R2 v通信显示 g同步发送 Gui Show, x850 w330 y400, P主进程 - 互相通信同步演示,请输入文字 Loop { Sleep 80 MouseGetPos, x, y ToolTip % "P-主进程持续运算演示-" A_Index, x+10, y-70 } Return 同步发送: GuiControlGet, 获取编辑框内容,, 通信显示 进程1.Gui同步更新(获取编辑框内容, "通信显示") Return GuiClose: ExitApp Return ; F3键做新进程的开、关、一键开关的演示【只有主进程端能够新建进程】 F3:: ; 进程1 := ComProcess(新代码1) 进程1 := "" ; 进程1:=(Toggle:=!Toggle) ? ComProcess(新代码1) : "" Return 新进程触发回调函数(newValue) { GuiControl,, 通信显示, %newValue% } class 变量赋值回调 { __New(changeCallback) { this._onChangeCallback := Func(changeCallback) } 回调变量[] { get { return this._value } set { this._value := value , (this._onChangeCallback && this._onChangeCallback.Call(this._value)) return this._value } } } 加载新进程标签: 新代码1= ( ` % Gui -MinimizeBox -MaximizeBox +AlwaysOnTop Gui Add, Edit, w300 R2 v通信显示 g同步发送 Gui Show, x850 w330 y500, F3新进程 - 互相通信同步演示,请输入文字 Loop { Sleep 80 MouseGetPos, x, y ToolTip % "F3-新进程持续运算演示-" A_Index, x+10, y-30 } Return 同步发送: GuiControlGet, 获取编辑框内容,, 通信显示 进程共享变量.回调变量 := 获取编辑框内容 Return Gui同步更新(Value, ControlID) { GuiControl,, %ControlID%, %Value% } GuiClose: ExitApp Return ) 进程1 := ComProcess(新代码1) Return /* 在目标进程中调用一个名为 "Function" 的函数。 进程1.Function(params) 获取进程1的一个全局变量 value := 进程1["VarName"] value := 进程1.VarName 对进程1的一个全局变量赋值 进程1["VarName"] := value 进程1.VarName := value 创建一个Class的实例 value := 进程1["__ComProcess"].__New("ClassName", params) 已知限制: 如果脚本在运行远程脚本调用的方法时退出,则远程脚本将收到错误。 AutoHotkey 如何在本地与 COM 对象交互以及 AutoHotkey 对象如何通过 COM 接口响应请求也存在一些限制。 变量无法通过 ByRef 传递到远程对象。 有些调用是不明确的;例如,foo.bar 触发器 foo.__Call 进而foo.__Get。 */ ; By dbgba Thank thqby ComProcess(PathsOrCode, v1Interpreter="", Timeout=30, NoTrayIcon="#NoTrayIcon") { Static IDispatch, ComProcessNum := 0 , _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, "int64"), "int64")) , PIDLabel := DllCall("GetCurrentProcessId") if (v1Interpreter="") { if A_Is64bitOS SetRegView 64 RegRead, InstallDir, HKLM\SOFTWARE\AutoHotkey, InstallDir v1Interpreter := InstallDir="" ? A_AhkPath : InstallDir "\AutoHotkey.exe" } if !FileExist(v1Interpreter) throw Exception("请将参数2的AHK解释器路径设置正确!") lresult := DllCall("oleacc\LresultFromObject", "Ptr", &IDispatch, "Ptr", 0, "Ptr", &(client := { proxy: 0 }), "Ptr") if IsObject(PathsOrCode) { t := PathsOrCode, PathsOrCode := "" For __, p in t PathsOrCode .= "`n#include " p } v1script := Format(" ( #Persistent " NoTrayIcon " class __ComProcess { __New(name, args*) { static _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, ""int64""), ""int64""), DllCall(""oleacc\ObjectFromLresult"", ""ptr"", {}, ""ptr"", &IDispatch, ""ptr"", 0, ""ptr*"", idisp := 0), ComObject(9, idisp).proxy := new __ComProcess(), ObjRelease(idisp)), hMapping := DllCall(""kernel32\CreateFileMapping"", ""Ptr"", -1, ""Ptr"", 0, ""UInt"", 0x4, ""UInt"", 0, ""UInt"", 4, ""Str"", ""AHKLoadStatus" PIDLabel . ++ComProcessNum """), pView := DllCall(""kernel32\MapViewOfFile"", ""Ptr"", hMapping, ""UInt"", 0x2, ""UInt"", 0, ""UInt"", 0, ""UInt"", 4) return IsObject(name) ? new name(args*) : new %name%(args*) } __Call(name, args*) { Global if (IsObject(args) && %name%!="""") return __ComProcessVar__ := %name% return %name%(args*) } ; __Get(name) COM机制无法触发,故删掉 __Set(name, val) { Global return %name% := val } __Delete() { SetTimer __ComProcessExitApp__, -1 return __ComProcessExitApp__: ExitApp } } {} )", lresult, FileExist(PathsOrCode) ? "" : PathsOrCode) if (v1Interpreter ~= "i)\.dll$") { if (!DllCall("GetModuleHandle", "str", v1Interpreter) && !DllCall("LoadLibrary", "str", v1Interpreter)) { DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp) throw Exception("load AutoHotkey.dll fail") } if !(threadID := DllCall(v1Interpreter "\NewThread", "str", v1script "`n" (FileExist(PathsOrCode) ? "#Include " PathsOrCode : ""), "str", "", "str", "", "cdecl uint")) { DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp) throw Exception("Failed to load script") } _exec := {ProcessID : DllCall("GetCurrentProcessId")} } else { if FileExist(PathsOrCode) _exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /include """ PathsOrCode """ *") else _exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /CP0 *") _exec.StdIn.Write(v1script), _exec.StdIn.Close() } Loop % Timeout*1000//15 { Sleep 15 hMap := DllCall("kernel32\OpenFileMapping", "UInt", 0x2, "Int", 0, "Str", "AHKLoadStatus" PIDLabel . ComProcessNum) , pView := DllCall("kernel32\MapViewOfFile", "Ptr", hMap, "UInt", 0x2, "UInt", 0, "UInt", 0, "UInt", 4) } Until ((isLoaded := NumGet(pView+0, 0, "Int"))=0) if (isLoaded!=0) Throw Exception("AHK新进程启动超时或故障!") Sleep 10 return client.proxy }
#Requires AutoHotkey v1.1.33+
#SingleInstance Force
SetBatchLines -1

Gosub 加载新进程标签

进程1.进程共享变量 := New 变量赋值回调("新进程触发回调函数")

Gui -MinimizeBox -MaximizeBox +AlwaysOnTop
Gui Add, Edit, w300 R2 v通信显示 g同步发送
Gui Show, x850 w330 y400, P主进程 - 互相通信同步演示,请输入文字

Loop {
  Sleep 80
  MouseGetPos, x, y
  ToolTip % "P-主进程持续运算演示-" A_Index, x+10, y-70
}
Return


同步发送:
GuiControlGet, 获取编辑框内容,, 通信显示
进程1.Gui同步更新(获取编辑框内容, "通信显示")
Return

GuiClose:
  ExitApp
Return


; F3键做新进程的开、关、一键开关的演示【只有主进程端能够新建进程】
F3::
  ; 进程1 := ComProcess(新代码1)
  进程1 := ""
  ; 进程1:=(Toggle:=!Toggle) ? ComProcess(新代码1) : ""
Return


新进程触发回调函数(newValue) {
  GuiControl,, 通信显示, %newValue%
}

class 变量赋值回调 {
  __New(changeCallback) {
    this._onChangeCallback := Func(changeCallback)
  }
  回调变量[] {
    get {
      return this._value
    }
    set {
      this._value := value
      , (this._onChangeCallback && this._onChangeCallback.Call(this._value))
      return this._value
    }
  }
}


加载新进程标签:
新代码1=
( ` %
Gui -MinimizeBox -MaximizeBox +AlwaysOnTop
Gui Add, Edit, w300 R2 v通信显示 g同步发送
Gui Show, x850 w330 y500, F3新进程 - 互相通信同步演示,请输入文字

Loop {
  Sleep 80
  MouseGetPos, x, y
  ToolTip % "F3-新进程持续运算演示-" A_Index, x+10, y-30
}
Return

同步发送:
GuiControlGet, 获取编辑框内容,, 通信显示
进程共享变量.回调变量 := 获取编辑框内容
Return

Gui同步更新(Value, ControlID) {
  GuiControl,, %ControlID%, %Value%
}

GuiClose:
  ExitApp
Return
)

进程1 := ComProcess(新代码1)
Return

/*
在目标进程中调用一个名为 "Function" 的函数。
进程1.Function(params)

获取进程1的一个全局变量
value := 进程1["VarName"]
value := 进程1.VarName

对进程1的一个全局变量赋值
进程1["VarName"] := value
进程1.VarName := value

创建一个Class的实例
value := 进程1["__ComProcess"].__New("ClassName", params)

已知限制:
如果脚本在运行远程脚本调用的方法时退出,则远程脚本将收到错误。
AutoHotkey 如何在本地与 COM 对象交互以及 AutoHotkey 对象如何通过 COM 接口响应请求也存在一些限制。
  变量无法通过 ByRef 传递到远程对象。
  有些调用是不明确的;例如,foo.bar 触发器 foo.__Call  进而foo.__Get。
*/

; By dbgba   Thank thqby
ComProcess(PathsOrCode, v1Interpreter="", Timeout=30, NoTrayIcon="#NoTrayIcon") {
  Static IDispatch, ComProcessNum := 0
  , _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, "int64"), "int64"))
  , PIDLabel := DllCall("GetCurrentProcessId")

  if (v1Interpreter="") {
    if A_Is64bitOS
      SetRegView 64
    RegRead, InstallDir, HKLM\SOFTWARE\AutoHotkey, InstallDir
    v1Interpreter := InstallDir="" ? A_AhkPath : InstallDir "\AutoHotkey.exe"
  }

  if !FileExist(v1Interpreter)
    throw Exception("请将参数2的AHK解释器路径设置正确!")

  lresult := DllCall("oleacc\LresultFromObject", "Ptr", &IDispatch, "Ptr", 0, "Ptr", &(client := { proxy: 0 }), "Ptr")
  if IsObject(PathsOrCode) {
    t := PathsOrCode, PathsOrCode := ""
    For __, p in t
      PathsOrCode .= "`n#include " p
  }

  v1script := Format("
  (
    #Persistent
    " NoTrayIcon "
    class __ComProcess {
      __New(name, args*) {
        static _ := (VarSetCapacity(IDispatch, 16), NumPut(0x46000000000000c0, NumPut(0x20400, IDispatch, ""int64""), ""int64""), DllCall(""oleacc\ObjectFromLresult"", ""ptr"", {}, ""ptr"", &IDispatch, ""ptr"", 0, ""ptr*"", idisp := 0), ComObject(9, idisp).proxy := new __ComProcess(), ObjRelease(idisp)), hMapping := DllCall(""kernel32\CreateFileMapping"", ""Ptr"", -1, ""Ptr"", 0, ""UInt"", 0x4, ""UInt"", 0, ""UInt"", 4, ""Str"", ""AHKLoadStatus" PIDLabel . ++ComProcessNum """), pView := DllCall(""kernel32\MapViewOfFile"", ""Ptr"", hMapping, ""UInt"", 0x2, ""UInt"", 0, ""UInt"", 0, ""UInt"", 4)
        return IsObject(name) ? new name(args*) : new %name%(args*)
      }
      __Call(name, args*) {
        Global
        if (IsObject(args) && %name%!="""")
          return __ComProcessVar__ := %name%
        return %name%(args*)
      }
      ; __Get(name) COM机制无法触发,故删掉
      __Set(name, val) {
        Global
        return %name% := val
      }
      __Delete() {
        SetTimer __ComProcessExitApp__, -1
        return
        __ComProcessExitApp__:
          ExitApp
      }
    }
    {}
  )", lresult, FileExist(PathsOrCode) ? "" : PathsOrCode)

  if (v1Interpreter ~= "i)\.dll$") {
    if (!DllCall("GetModuleHandle", "str", v1Interpreter) && !DllCall("LoadLibrary", "str", v1Interpreter)) {
      DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp)
      throw Exception("load AutoHotkey.dll fail")
    }
    if !(threadID := DllCall(v1Interpreter "\NewThread", "str", v1script "`n" (FileExist(PathsOrCode) ? "#Include " PathsOrCode : ""), "str", "", "str", "", "cdecl uint")) {
      DllCall("oleacc\ObjectFromLresult", "ptr", lresult, "ptr", &IDispatch, "ptr", 0, "ptr*", idsp := 0), ObjRelease(idisp)
      throw Exception("Failed to load script")
    }
    _exec := {ProcessID : DllCall("GetCurrentProcessId")}
  } else {
    if FileExist(PathsOrCode)
      _exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /include """ PathsOrCode """ *")
    else
      _exec := ComObjCreate("WScript.Shell").Exec("""" v1Interpreter """ /CP0 *")
    _exec.StdIn.Write(v1script), _exec.StdIn.Close()
  }

  Loop % Timeout*1000//15 {
    Sleep 15
    hMap := DllCall("kernel32\OpenFileMapping", "UInt", 0x2, "Int", 0, "Str", "AHKLoadStatus" PIDLabel . ComProcessNum)
    , pView := DllCall("kernel32\MapViewOfFile", "Ptr", hMap, "UInt", 0x2, "UInt", 0, "UInt", 0, "UInt", 4)
  } Until ((isLoaded := NumGet(pView+0, 0, "Int"))=0)
  if (isLoaded!=0)
    Throw Exception("AHK新进程启动超时或故障!")
  Sleep 10
  return client.proxy
}

 

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。