5.5 彙編語言:函數調用約定

来源:https://www.cnblogs.com/LyShark/archive/2023/08/22/17648828.html
-Advertisement-
Play Games

函數是任何一門高級語言中必須要存在的,使用函數式編程可以讓程式可讀性更高,充分發揮了模塊化設計思想的精髓,今天我將帶大家一起來探索函數的實現機理,探索編譯器到底是如何對函數這個關鍵字進行實現的,並使用彙編語言模擬實現函數編程中的參數傳遞調用規範等。說到函數我們必須要提起調用約定這個名詞,而調用約定離... ...


函數是任何一門高級語言中必須要存在的,使用函數式編程可以讓程式可讀性更高,充分發揮了模塊化設計思想的精髓,今天我將帶大家一起來探索函數的實現機理,探索編譯器到底是如何對函數這個關鍵字進行實現的,並使用彙編語言模擬實現函數編程中的參數傳遞調用規範等。

說到函數我們必須要提起調用約定這個名詞,而調用約定離不開棧的支持,棧在記憶體中是一塊特殊的存儲空間,遵循先進後出原則,使用push與pop指令對棧空間執行數據壓入和彈出操作。棧結構在記憶體中占用一段連續存儲空間,通過esp與ebp這兩個棧指針寄存器來保存當前棧起始地址與結束地址,每4個位元組保存一個數據。

當棧頂指針esp小於棧底指針ebp時,就形成了棧幀,棧幀中可以定址的數據有局部變數,函數返回地址,函數參數等。不同的兩次函數調用,所形成的棧幀也不相同,當由一個函數進入另一個函數時,就會針對調用的函數開闢出其所需的棧空間,形成此函數的獨有棧幀,而當調用結束時,則清除掉它所使用的棧空間,關閉棧幀,該過程通俗的講叫做棧平衡。而如果棧在使用結束後沒有恢復或過度恢復,則會造成棧的上溢或下溢,給程式帶來致命錯誤。

一般情況下在Win32環境預設遵循的就是STDCALL,而在Win64環境下使用的則是FastCALL,在Linux系統上則遵循SystemV的約定,這裡我整理了他們之間的異同點.

  • CDECL:C/C++預設的調用約定,調用方平棧,不定參數的函數可以使用,參數通過堆棧傳遞.
  • STDCALL:被調方平棧,不定參數的函數無法使用,參數預設全部通過堆棧傳遞.
  • FASTCALL32:被調方平棧,不定參數的函數無法使用,前兩個參數放入(ECX, EDX),剩下的參數壓棧保存.
  • FASTCALL64:被調方平棧,不定參數的函數無法使用,前四個參數放入(RCX, RDX, R8, R9),剩下的參數壓棧保存.
  • System V:類Linux系統預設約定,前八個參數放入(RDI,RSI, RDX, RCX, R8, R9),剩下的參數壓棧保存.

首先先來寫一段非函數版的堆棧使用案例,案例中模擬了編譯器如何生成Main函數棧幀以及如何對棧幀初始化和使用的流程,筆者通過自己的理解寫出了Debug版本的一段仿寫代碼。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  main PROC
    push ebp                   ; 保存棧底指針ebp
    mov ebp,esp                ; 調整當前棧底指針到棧頂
    
    sub esp,0e4h               ; 抬高棧頂esp開闢局部空間
    
    push ebx                   ; 保存寄存器
    push esi
    push edi
    
    lea edi,dword ptr [ ebp - 0e4h ]  ; 取出當前函數可用棧空間首地址
    
    mov ecx,39h                       ; 填充長度
    mov eax,0CCCCCCCCh                ; 填充四位元組數據
    rep stosd                         ; 將當前函數局部空間填充初始值
    
    ; 使用當前函數可用局部空間
    xor eax,eax
    
    mov dword ptr [ ebp - 08h ],1
    mov dword ptr [ ebp - 014h ],2       ; 使用局部變數
    mov dword ptr [ ebp - 020h ],3
    
    mov eax,dword ptr [ ebp - 014h ]
    add eax,dword ptr [ ebp - 020h ]
    
    ; 如果指令影響了堆棧平衡,則需要平棧
    push 4                ; 此情況,由於入棧時沒有修改過,平棧只需add esp,12
    push 5
    push 6                ; 如果代碼沒有自動平棧,則需要手動平
    add esp,12            ; 每個指令4位元組 * 多少條影響
    
    push 10
    push 20
    push 30               ; 使用3條指令影響堆棧
    pop eax
    pop ebx               ; 彈出兩條
    add esp,4             ; 修複堆棧時只需要平一個變數
    
    pop edi               ; 恢復寄存器
    pop esi
    pop ebx
    
    add esp,0e4h        ; 降低棧頂esp開闢的局部空間,局部空間被釋放
    
    cmp ebp,esp         ; 檢測堆棧是否平衡,不平衡則直接停機
    jne error
    
    pop ebp
    mov esp,ebp         ; 恢復基址指針
    int 3
  
  error:
    int 3
  
  main ENDP
END main

5.1 CDECL

CDECL是C/C++中的一種預設調用約定(調用者平棧)。這種調用方式規定函數調用者在將參數壓入棧中後,再將控制權轉移到被調用函數,被調用函數通過棧頂指針ESP來訪問這些參數。函數返回時,由調用者程式負責將堆棧平衡清除。CDECL調用約定的特點是簡單易用,但相比於其他調用約定,由於棧平衡的操作需要在函數返回後再進行,因此在一些情況下可能會帶來一些性能上的開銷。

該調用方式在函數內不進行任何平衡參數操作,而是在退出函數後對esp執行加4操作,從而實現棧平衡。該約定會採用覆寫傳播優化,將每次參數平衡的操作進行歸併,在函數結束後一次性平衡棧頂指針esp,且不定參數函數也可使用此約定。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0cch
    push ebx
    push esi
    push edi
    
    lea edi,dword ptr [ ebp - 0cch ]     ; 初始化局部變數
    mov ecx,33h
    mov eax,0CCCCCCCCh
    rep stosd

    mov eax,dword ptr [ ebp + 08h ]       ; 第一個變數(傳入參數1)
    add eax,dword ptr [ ebp + 0Ch ]       ; 第二個變數(傳入參數2)
    add eax,dword ptr [ ebp + 10h ]       ; 第三個變數(傳入參數3)
    
    mov dword ptr [ ebp - 08h ],eax       ; 將結果放入到局部變數
    mov eax,dword ptr [ ebp - 08h ]       ; 給eax寄存器返回
    
    pop edi
    pop esi
    pop ebx
    mov esp,ebp
    pop ebp
    ret
  function endp

  main PROC
    ; 單獨調用並無優勢
    push 3
    push 2
    push 1
    call function          ; __cdecl functin(1,2,3)
    add esp,12
    
    ; 連續調用則可體現出優勢
    push 5
    push 4
    push 3
    call function          ; __cdecl function(3,4,5)
    mov ebx,eax
    
    push 6
    push 7
    push 8
    call function          ; __cdecl function(8,7,6)
    mov ecx,eax
    
    add esp,24             ; 一次性平兩次棧
    
    int 3
  main ENDP
END main

5.2 STDCALL

STDCALL 調用約定規定由被調用者負責將堆棧平衡清除。STDCALL是一種被調用者平棧的約定,這意味著,在函數調用過程中,被調用函數使用棧來存儲傳遞的參數,併在函數返回之前移除這些參數,這種方式可以使調用代碼更短小簡潔。STDCALL與CDECL只在參數平衡上有所不同,其餘部分都一樣,但該約定不定參數函數無法使用。

通過以上分析發現_cdecl_stdcall兩者只在參數平衡上有所不同,其餘部分都一樣,但經過優化後_cdecl調用方式的函數在同一作用域內多次使用,會在效率上比_stdcall髙,這是因為_cdecl可以使用覆寫傳播優化,而_stdcall的平棧都是在函數內部完成的,無法使用覆寫傳播這種優化方式。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0cch
    push ebx
    push esi
    push edi
    
    lea edi,dword ptr [ ebp - 0cch ]     ; 初始化局部變數
    mov ecx,33h
    mov eax,0CCCCCCCCh
    rep stosd

    mov eax,dword ptr [ ebp + 08h ]       ; 第一個變數(傳入參數1)
    add eax,dword ptr [ ebp + 0Ch ]       ; 第二個變數(傳入參數2)
    add eax,dword ptr [ ebp + 10h ]       ; 第三個變數(傳入參數3)
    
    mov dword ptr [ ebp - 08h ],eax       ; 將結果放入到局部變數
    mov eax,dword ptr [ ebp - 08h ]       ; 給eax寄存器返回
    
    pop edi
    pop esi
    pop ebx
    mov esp,ebp
    pop ebp
    
    ret 12                                ; 應用stdcall時,通過ret對目標平棧
  function endp

  main PROC
    push 3
    push 2
    push 1
    call function          ; __stdcall functin(1,2,3)
    mov ebx,eax            ; 獲取返回值
    
    push 4
    push 5
    push 6
    call function          ; __stdcall function(6,5,4)
    mov ecx,eax            ; 獲取返回值
    
    add ebx,ecx            ; 結果相加
    int 3
  main ENDP
END main

5.3 FASTCALL

FASTCALL是一種針對寄存器的調用約定。它通常採用被調用者平衡堆棧的方式,類似於STDCALL調用約定。但是,FASTCALL約定規定函數的前兩個參數在ECX和EDX寄存器中傳遞,節省了壓入堆棧所需的指令。此外,函數使用堆棧來傳遞其他參數,併在返回之前使用類似於STDCALL約定的方式來平衡堆棧。

FASTCALL的優點是可以在發生大量參數傳遞時加快函數的處理速度,因為使用寄存器傳遞參數比使用堆棧傳遞參數更快。但是,由於FASTCALL約定使用的寄存器數量比CDECL和STDCALL約定多,因此它也有一些限制,例如不支持使用浮點數等實現中需要使用多個寄存器的數據類型。

FASTCALL效率最高,其他兩種調用方式都是通過棧傳遞參數,唯獨_fastcall可以利用寄存器傳遞參數,一般前兩個或前四個參數用寄存器傳遞,其餘參數傳遞則轉換為棧傳遞,此約定不定參數函數無法使用。

  • 對於32位來說使用ecx,edx傳遞前兩個參數,後面的用堆棧傳遞。
  • 對於64位則會使用RCX,RDX,R8,R9傳遞前四個參數,後面的用堆棧傳遞。
  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0e4h
    push ebx
    push esi
    push edi
    push ecx
    
    lea edi,dword ptr [ ebp - 0e4h ]     ; 初始化局部變數
    mov ecx,39h
    mov eax,0CCCCCCCCh
    rep stosd
    pop ecx
    
    mov dword ptr [ ebp - 14h ],edx      ; 讀入第二個參數放入局部變數
    mov dword ptr [ ebp - 8h ],ecx       ; 讀入第一個參數放入局部變數
    
    mov eax,dword ptr [ ebp - 8h ]       ; 從局部變數內讀入第一個參數
    add eax,dword ptr [ ebp - 14h ]      ; 從局部變數內讀入第二個參數
    
    add eax,dword ptr [ ebp + 8h ]       ; 從堆棧中讀入第三個參數
    add eax,dword ptr [ ebp + 0ch ]      ; 從堆棧中讀入第四個參數
    add eax,dword ptr [ ebp + 10h ]      ; 從堆棧中讀入第五個參數
    add eax,dword ptr [ ebp + 14h ]      ; 從堆棧中讀入第六個參數
    
    mov dword ptr [ ebp - 20h ],eax      ; 將結果給第三個局部變數
    mov eax,dword ptr [ ebp - 20h ]      ; 返回數據
    
    pop edi
    pop esi
    pop ebx
    mov esp,ebp
    pop ebp
    
    ret 16                               ; 平棧
  function endp

  main PROC
    push 6
    push 5
    push 4
    push 3
    mov edx,2
    mov ecx,1         ; __fastcall function(1,2,3,4,5,6)
    call function     ; 調用函數
    
    int 3
  main ENDP
END main

5.4 使用ESP寄存器定址

編譯器開啟了O2優化模式選項,則為了提高程式執行效率,只要棧頂是穩定的,編譯器編譯時就不再使用ebp指針了,而是利用esp指針直接訪問局部變數,這樣可節省一個寄存器資源。

在程式編譯時編譯器會自動為我們計算ESP基地址與傳入變數的參數偏移,使用esp定址後,不必每次進入函數後都調整棧底ebp,從而減少了ebp的使用,因此可以有效提升程式執行效率。但如果在函數執行過程中esp發生了變化,再次訪問變數就需要重新計算偏移了。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0ch
    push esi
    
    ; 動態計算出四個參數
    lea eax,dword ptr [ esp - 4h + 01ch ]    ; 計算參數1 [esp+18]
    lea ebx,dword ptr [ esp - 0h + 01ch ]    ; 計算參數2 [esp+1c]
    lea ecx,dword ptr [ esp + 4h + 01ch ]    ; 計算參數3 [esp+20]
    lea edx,dword ptr [ esp + 8h + 01ch ]    ; 計算參數4 [esp+24]
    
    ; 如果ESP被干擾則需要動態調整
    lea eax,dword ptr [ esp - 4h + 01ch ]          ; 當前參數1的地址
    push ebx
    push ecx                                       ; 指令讓ESP被減去8
    lea eax,dword ptr [ esp - 4h + 01ch  + 8h ]    ; 此處需要+8h修正堆棧
    
    add esp,0ch
    pop esi
    mov esp,ebp
    pop ebp
    ret
  function endp

  main PROC
    push 5
    push 3
    push 4
    push 1
    call function
    int 3
  main ENDP
END main

5.5 使用數組指針傳值

這裡我們以一維數組為例,二維數組的傳遞其實和一維數組是相通的,只不過在定址方式上要使用二維數組的定址公式,此外傳遞數組其實本質上就是傳遞指針,所以數組與指針的傳遞方式也是相通的。

使用彙編仿寫數組傳遞方式,在main函數內我們動態開闢一塊棧空間,並將數組元素依次排列在棧內,參數傳遞時通過lea eax,dword ptr [ ebp - 18h ]獲取到數組棧地址空間,由於main函數並不會被釋放所以它的棧也是穩定的,調用function函數時只需要將棧首地址通過push eax的方式傳遞給function函數內,併在函數內通過mov ecx,dword ptr [ ebp + 8h ]獲取到函數基地址,通過比例因數定位棧空間。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0cch
    push ebx
    push esi
    push edi
    lea edi,dword ptr [ ebp - 0cch ]
    mov ecx,33h
    mov eax,0CCCCCCCCh
    rep stosd
    
    ; 檢索數組第一個元素
    mov eax,1
    mov ecx,dword ptr [ ebp + 8h ]          ; 定位數組基地址
    mov edx,dword ptr [ ecx + eax * 4 ]     ; 定位元素
    
    ; 檢索數組第二個元素
    mov eax,2
    mov ecx,dword ptr [ ebp + 8h ]
    mov edx,dword ptr [ ecx + eax * 4 ]
    
    pop edi
    pop esi
    pop ebx
    add esp,0cch
    mov esp,ebp
    pop ebp
    ret
  function ENDP

  main PROC
    push ebp
    mov ebp,esp
    sub esp,0dch
    push ebx
    push esi
    push edi

    lea edi,dword ptr [ ebp - 0dch ]
    mov ecx,37h
    mov eax,0CCCCCCCCh
    rep stosd 
    
    mov dword ptr [ ebp - 18h ],1        ; 局部空間存儲數組元素
    mov dword ptr [ ebp - 14h ],2
    mov dword ptr [ ebp - 10h ],3
    mov dword ptr [ ebp - 0ch ],4
    mov dword ptr [ ebp - 8h ],5
    
    push 5
    lea eax,dword ptr [ ebp - 18h ]      ; 取數組首地址併入棧
    push eax
    call function                        ; 調用函數 function(5,eax)
    add esp,8                            ; 平棧

    pop edi
    pop esi
    pop ebx
    add esp,0dch
    mov esp,ebp
    pop ebp
    ret
  main ENDP
END main

5.6 指向函數的指針

程式通過CALL指令跳轉到函數首地址執行代碼,既然是地址那就可以使用指針變數來存儲函數的首地址,該指針變數被稱作函數指針。

在編譯時編譯器為函數代碼分配一段存儲空間,這段存儲空間的起始地址就是這個函數的指針,我們可以調用這個指針實現間接調用指針所指向的函數。

#include <iostream>

void __stdcall Show(int x, int y)
{
  printf("%d --> %d \n",x,y);
}

int __stdcall ShowPrint(int nShow, int nCount)
{
  int ref = nShow + nCount;
  return ref;
}

int main(int argc, char* argv[])
{
  // 空返回值調用
  void(__stdcall *pShow)(int,int) = Show;
  pShow(1,2);

  // 帶參數調用返回
  int(__stdcall *pShowPrint)(int, int) = ShowPrint;
  int Ret = pShowPrint(2, 4);
  printf("返回值 = %d \n", Ret);

  return 0;
}

首先我們使用彙編仿寫ShowPrint函數以及該函數所對應的int(__stdcall *pShowPrint)(int, int)函數指針,看一下在彙編層面該如何實現這個功能。

  .386p
  .model flat,stdcall
  option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

.code
  function PROC
    push ebp
    mov ebp,esp
    sub esp,0cch
    push ebx
    push esi
    push edi
    lea edi,dword ptr [ ebp - 0cch ]
    mov ecx,33h
    mov eax,0CCCCCCCCh
    rep stosd
    
    mov eax,dword ptr [ ebp + 4h ]    ; 此處+4得到的是返回後上一條指令地址
    mov eax,dword ptr [ ebp + 8h ]    ; 得到第一個堆棧傳入參數地址
    mov ebx,dword ptr [ ebp + 0ch ]   ; 得到第二個堆棧傳入參數地址
    add eax,ebx                       ; 遞增並返回到EAX
    
    pop edi
    pop esi
    pop ebx
    add esp,0cch
    mov esp,ebp
    pop ebp
    ret
  function ENDP

  main PROC
    push ebp
    mov ebp,esp
    sub esp,0d8h
    push ebx
    push esi
    push edi

    lea edi,dword ptr [ ebp - 0d8h ]
    mov ecx,36h
    mov eax,0CCCCCCCCh
    rep stosd 
    
    lea eax,function                     ; 獲取函數指針
    mov dword ptr [ ebp - 8h ],eax       ; 將指針放入局部空間
    
    push 4
    push 2                               ; 傳入參數
    call dword ptr [ ebp - 8h ]          ; 調用函數
    add esp,8                            ; 平棧

    pop edi
    pop esi
    pop ebx
    add esp,0d8h
    mov esp,ebp
    pop ebp
    ret
  main ENDP
END main

本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/17fb1a42.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Infinispan 是一個基於分散式系統的記憶體數據存儲和緩存平臺,它的集群實現原理涉及到節點的發現和通信。在 Infinispan 中,集群是由多個節點組成的,每個節點都存儲著數據的一部分,並且通過通信來保持數據的一致性和可用性。 Infinispan 集群的實現原理主要包括以下幾個關鍵點: 1. ...
  • # 簡介 Spring Boot Admin(SBA)是一個針對spring-boot的actuator介面進行UI美化封裝的監控工具。它可以:在列表中瀏覽所有被監控spring-boot項目的基本信息,詳細的Health信息、記憶體信息、JVM信息、垃圾回收信息,還可以直接修改logger日誌的le ...
  • # 應用場景 * 用戶下單5分鐘後,給他發簡訊 * 用戶下單30分鐘後,如果用戶不付款就自動取消訂單 # kafka無死信隊列 kafka本身沒有這種延時隊列的機制,像rabbitmq有自己的死信隊列,當一些消息在一定時間不消費時會發到死信隊列,由死信隊列來處理它們,上面的兩個需求如果是rabbit ...
  • 本文翻譯自國外論壇 medium,原文地址:https://levelup.gitconnected.com/how-i-deleted-more-than-1000-lines-of-code-using-spring-retry-9118de29060 > 使用 Spring Retry 重構代 ...
  • 通過一張圖描述清楚TuGraph Analytics的整體架構和關鍵設計,幫助大家快速瞭解TuGraph Analytics項目輪廓。 ...
  • ![](https://img2023.cnblogs.com/other/1218593/202308/1218593-20230822164212978-1679813836.png) ### 背景 有時候我們需要進行遠程的debug,本文研究如何進行遠程debug,以及使用 IDEA 遠程de ...
  • ## 1 概要 通過引入結構化併發編程的API,簡化併發編程。結構化併發將在不同線程中運行的相關任務組視為單個工作單元,從而簡化錯誤處理和取消操作,提高可靠性,並增強可觀察性。這是一個預覽版的API。 ## 2 歷史 結構化併發是由JEP 428提出的,併在JDK 19中作為孵化API發佈。它在JD ...
  • [TOC] ## 一、mall開源項目 ### 1.1 來源 **mall學習教程**,架構、業務、技術要點全方位解析。mall項目(**50k+star**)是一套電商系統,使用現階段主流技術實現。涵蓋了SpringBoot 2.3.0、MyBatis 3.4.6、Elasticsearch 7. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...