tft每日頭條

 > 科技

 > unity協程相關學習筆記

unity協程相關學習筆記

科技 更新时间:2025-08-19 03:51:10

unity協程相關學習筆記?在上一篇關于P/INVOKE的文章中,我們學習了如何從Unity内部調用非托管方法,以及如何跨互操作屏障傳遞參數和返回值,今天小編就來聊一聊關于unity協程相關學習筆記?接下來我們就一起去研究一下吧!

unity協程相關學習筆記(使用PInvoke)1

unity協程相關學習筆記

在上一篇關于P/INVOKE的文章中,我們學習了如何從Unity内部調用非托管方法,以及如何跨互操作屏障傳遞參數和返回值。

現在讓我們開始DllImport在我們的代碼庫中到處撒播以獲得樂趣和利潤!對?好…

  • 這種做法将違反單一責任原則
  • 多平台支持會有很多代碼重複
  • 這将使任何使用DllImport更難或不可能進行單元測試的類
  • 我們還沒有真正的策略來安全地管理非托管對象的生命周期——基本上避免内存洩漏。

那麼讓我們來看看我們是如何在 Baracoda 的項目中應對這些挑戰的!

支持多種目标平台

假設您有一個針對靜态庫編譯的 iOS 代碼的現有版本,libios_plugin.a. 因此,讓我們添加插件的 Android 版本libandroid_plugin.aar,其中包含内部libandroid_plugin.so.

然後在嘗試執行本機代碼時觀察它失敗:

E/Unity: DllNotFoundException: __Internal

DllImport 和程序集命名

我們的第一個問題是,在 iOS 上,我們使用的是靜态鍊接庫,它要求傳遞給的名稱DllImport是__Internal.

但是,我們有一個名為 的 Android 版本的動态鍊接庫libandroid_plugin.so,并且該名稱需要在DllImport.

我們可以使用///指令和Unity的平台腳本符号來使用條件編譯#if,如#elif或選擇#else将基于當前平台編譯的屬性的版本。#endifUNITY_ANDROIDUNITY_IOS

萬歲,它有效!但是,呃……如果我們需要更多方法,那就太冗長了,而且我們現在隻支持 2 個平台。那麼我們能做些什麼來避免重複這個巨大的塊呢?

好吧,庫名稱必須是一個常量字符串,所以const string也可以。讓我們重構:

現在聲明一個新方法隻是堅持LIBNAME下去的問題,而支持另一個平台隻是#if所有方法中的一個例子!

SRP和DRY,讓代碼更好

我們編寫的内容适用于單個類,但随着 API 表面變大,我們可能希望将這些本地方法分組為對象——根據單一責任原則——每個對象代表我們想要的不同服務訪問。

但是因為這個LIBNAME變量現在是私有的,所以我們必須在每個類中複制/粘貼指令,這與D on't R epeat Y ourself原則相矛盾。所以讓我們創建另一個類來為我們保存它!

導入現在看起來像這樣:

不太冗長,易于閱讀,易于擴展。現在我們肯定完成了,對吧?

非托管代碼和單元測試

那麼,你能為最終調用那些非托管方法的特性編寫單元測試嗎?

單元測試是對抗回歸的好工具,可以确保你的代碼符合你的預期,如果寫得好,甚至可以作為可運行的文檔。

因此,它們對于确保和維護我們的軟件和遊戲的健康至關重要。

因此,也許您可​以針對實際實現編寫測試,因為本機庫隻是提供一些業務邏輯,但也許您首先擁有它的原因是因為它使您可以訪問外部資源?也許沒有可用的桌面版本的庫,測試甚至無法在編輯器中運行?

無論如何,這個非托管代碼應該被考慮在被測單元之外,但是您仍然需要訪問它在真實代碼中提供的服務。

在這種情況下,最好的解決方案通常是将服務的實現與其接口分離。

現在我們可以為測試實例化一個假的,但仍然将真實的實現用于生産!

非托管對象和生命周期管理

這仍然是一個玩具示例,但是因為我們沒有創建非托管對象的實例,而隻是在讨論似乎是自由函數或靜态方法的東西。

有時隻有一個服務實例可以與之對話是有意義的,有時則不然,您需要能夠動态地創建新實例。那麼,我們如何從 C# 中與它們交互呢?

内部指針

在 C 或 C 中動态創建對象時,程序将分配一些内存,在其中構造對象,并返回指向它的指針

等等,别跑!沒關系!

在編組指向 C# 的非托管指針時,運行時可以将其轉換為IntPtr. 您可以将其視為非托管對象的不透明句柄,除了将其交還給非托管端外,您不能直接使用它做很多事情。

所以現在創建 C# 類的新實例也會創建非托管對象的新實例,然後我們可以對其進行方法調用。甜的!

我們隻是忘記了一個細節:我們創建了一個非托管對象,這意味着 GC 不知道如何回收它,甚至默認都不嘗試!

因此,當 C# 類被垃圾回收時,讓我們停止洩漏該非托管對象。

所有權

在 C# 中,類發出需要清理步驟的信号的首選方式是實現IDisposable接口。

決定何時以及從何處調用Dispose()超出了本文的範圍,但請注意,出色的Extenject 框架能夠自動處理它創建的對象。

假設有一個函數用于銷毀我們的非托管對象,它的 API 如下所示:

現在實現Dispose()非常簡單:

随着這一變化,我們現在可以很好地管理我們的資源并執行必要的清理工作。

随着static extern方法數量的增加,類趨于混亂。将它們提取到側面的靜态類中以保持業務邏輯和非托管方法聲明分開通常是一個好主意。請參閱下面的示例。

然而,既然我們已經引入了手動資源管理,我們就會冒着嘗試引用已被釋放的非托管對象的風險,所以讓我們讓它更安全!

自定義手柄

幸運的是,C# 标準庫正是我們所需要的:SafeHandle!它本來是用來保持 Win32 句柄的,但它的 API 和終結保證使它非常适合我們的目的。

它還具有抽象的額外好處,因此您必須自己繼承它,從而啟用類型檢查,而IntPtr對于編譯器來說,任何一個看起來都像其他任何一個。

從 繼承時SafeHandle,需要做 3 件事。

  • 告訴内部句柄的無效值是什麼,以及通過其構造函數SafeHandle是否是該句柄的唯一所有者
  • 實現抽象屬性IsInvalid。對于構造函數中給出的無效值,它應該返回 true。為什麼沒有默認實現令人驚訝……
  • ReleaseHandle()實現當句柄被處理或垃圾收集時調用的抽象方法。顯然,它應該釋放這個句柄持有的資源。

因此,這就是SafeHandle我們示例中自定義的樣子:

現在我們隻需要在任何地方都替換IntPtr為 with CameraServiceHandle,除了在銷毀方法中仍然需要一個IntPtr.

對于非托管方法聲明,将它們提取到自己的靜态類中後,它将如下所示:

我們的 C# 端服務現在在内部使用句柄:

我們已經做到了!

我們現在已經從到處添加臨時[DllImport] static extern方法(冗長、難以測試且不一定資源安全)轉變為專門設計的方法。

我們有一些小包裝;它們封裝良好,不會阻止對依賴它們的代碼進行測試,易于添加跨平台支持,并且我們現在有系統的方法來保證與非托管對象交互時的資源和類型安全!

這就是我們在 Baracoda 如何使用 P/Invoke 的導覽!

利用 P/Invoke 使我們能夠編寫跨平台庫并與之交互,從而将我們研發團隊在機器學習和計算機視覺方面的内部知識帶到我們的 Unity 遊戲中!

我們計劃發布更多 Unity 開發者内容,敬請期待!

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关科技资讯推荐

热门科技资讯推荐

网友关注

Copyright 2023-2025 - www.tftnews.com All Rights Reserved