Skip to Content
Plugin 開發版本號與 ABI 相容

版本號與 ABI 相容

Plugin 身上有兩種版本,很容易搞混:

是什麼誰決定用途
Plugin 版本號你的 Plugin 自己的 1.0.0你使用者看的版本、線上目錄比對「有沒有更新」
ABI 版本主程式與 Plugin 之間的介面記憶體佈局是第幾版,目前是 4.1主程式的 SDK 標頭決定這顆 DLL 主程式載不載得進來

兩者互不相干:改你自己的功能不會動 ABI 版本,主程式升級介面也不會動你的 Plugin 版本號。

Plugin 版本號:只改一個地方

每顆 Plugin 在 src\ 下放一個 plugin_version.h,只有四個 #define:

#define PLUGIN_VER_MAJOR 1 #define PLUGIN_VER_MINOR 0 #define PLUGIN_VER_PATCH 0 #define PLUGIN_VER_STR "1.0.0" /* 必須和上面三個數字逐字對得上 */

三個數字各是 0~65535。範本與四顆範例都已經附好,你複製範本時就有。

這份檔案同時餵給兩個地方,所以不會走樣:

  1. .c:NexusPluginGetInterface() 把 IcPlugin 的 version_major/version_minor/version_patch 設成這三個巨集(version_rsv 填 0)。
  2. .rc:VERSIONINFO 資源用同一份巨集組出 FileVersion/ProductVersion, 所以在檔案總管對 DLL 按右鍵 →「內容」→「詳細資料」就看得到版本,不必啟動主程式。

改版只改 plugin_version.h 這一份,.c 與 .rc 都是引用巨集。改完兩種架構(x86 與 x64)都要重新建置(見架構要對:x86/x64)。

plugin_version.h 有兩個限制:

  • 必須是純 ASCII,而且只有 include guard 和 #define:資源編譯器(rc.exe、windres)也會讀它,它看不懂 C 宣告,也用系統的 ANSI 字碼頁解析。
  • 0.0.0 保留表示「未宣告」:三段全是 0 的話,主程式視為版本未知,顯示 (-)。請從 1.0.0(至少 0.0.1)開始編號, 不要拿全零當起點——否則一顆忘記填版本號的 Plugin 在 Plugin Manager 眼中永遠「可以更新」。複製範本忘了改版本號是最常見的原因,見把 Plugin 交給使用者。

在主程式裡怎麼看

> plugin list PLUGIN: 2 -> canvas_demo(1.0.0) hello_ic(1.0.0) > plugin version hello_ic hello_ic 1.0.0
  • plugin list 在每顆 Plugin 名稱後面直接接版本號;讀不到版本(更舊 ABI 的 Plugin,或 0.0.0)顯示 (-),不會讓整條指令失敗。
  • plugin list 的輸出有長度上限(192 位元組),Plugin 多了會被截斷;只想查一顆就用 plugin version <名稱>: 已知版本印 名稱 x.y.z,版本未知印 名稱 -(版本未知不是錯誤),名稱根本沒有註冊過才回錯誤(not found)。
  • 主程式自己的版本與 ABI 版本用 ver 看:
> ver nexus_tui 1.0.0.8 (abi 4.1)

ABI 版本:什麼情況載得進來

ABI 版本有主版本與次版本(現在是 4.1)。你的 Plugin 在 IcPlugin 前三個欄位填 abi_major、abi_minor、size_of_struct(範例都用 SDK 的常數與 sizeof(IcPlugin)):

情況結果
主版本不同拒絕載入。Log 記一則錯誤:plugin dll rejected: xxx.dll abi major 3 (expected 4); plugin abi 3.1 size ..., host abi 4.1 size ...,同時印出雙方的版本與結構大小;接著另有一行說明方向:DLL 比主程式舊(例如 3.x 的 Plugin 載入 4.0 的主程式)要「用目前主版本的 PDK 重新編譯」,DLL 比主程式新則要「更新主程式」
主版本相同、次版本比主程式舊載入。新加在結尾的欄位主程式視為不存在,行為依 size_of_struct 降級
主版本相同、次版本比主程式新載入,但記一則警告 ... declares abi minor N, newer than host M; fields beyond host size ... are ignored:超出主程式認得範圍的欄位不會生效
size_of_struct 太小(連 cleanup 都沒涵蓋)拒絕載入,Log 記錯誤

主版本只在「刪掉或改動既有欄位」這種會讓舊 DLL 讀寫到錯位置的變更時才增加。 次版本只在結構結尾追加新欄位時增加。這就是規則:新欄位只加在結尾。

所以你用 SDK 編出的 Plugin,只要主版本相同,就能在同主版本、較新或較舊次版本的主程式上載入。

實例:4.1 在結尾追加了 on_int_event 與五支 Dongle 服務(見 Dongle 專屬功能)。4.0 的 Plugin 不用重編就能在 4.1 主程式上跑,只是看不到這些欄位;反過來,4.1 的 Plugin 在 4.0 主程式上要先用 NX_ABI_HAS_FIELD 確認再呼叫。

用到比較新的欄位:NX_ABI_HAS_FIELD

如果你的 Plugin 想用比你編譯時的基準更晚才新增的 NxHostApi 欄位,要先檢查對方是不是真的有這個欄位:

if (NX_ABI_HAS_FIELD(host->size_of_struct, NxHostApi, SomeNewService) && host->SomeNewService != 0) { /* 可以用 */ } else { /* 較舊的主程式:退回別的做法 */ }

兩道檢查缺一不可:NX_ABI_HAS_FIELD 確認那個欄位完整落在對方宣告的 size_of_struct 之內;然後再檢查函式指標本身不是 NULL(主程式也可能合法地把它填成 NULL,表示不提供)。

絕對不要用 abi_minor 的大小判斷「有沒有某個欄位」。 abi_minor 只說明契約是哪一版,size_of_struct 才說明對方實際填了多少。 主程式與 Plugin 各自獨立升版時,用 minor 判斷會讀到沒初始化的記憶體。

(abi_minor 的正當用途是判斷「對方的行為約定是哪一版」,例如 NexusPluginGetInterface(host_abi_major, host_abi_minor) 收到的參數。)

NexusPluginGetInterface 裡怎麼寫

範例的寫法是:主版本不符就回 NULL(Plugin 自己拒絕載入),不要在這裡比較次版本——次版本不同不是拒載的理由:

if (host_abi_major != NX_PLUGIN_ABI_MAJOR) { return (struct IcPlugin *)0; }

之後所有欄位用具名賦值填寫(不要用位置初始化式,欄位順序將來若有增加,位置初始化式會靜默錯位)。

主程式與 PDK 的版本

PDK 的版本號就是主程式的版本號:那一版主程式配那一版 PDK,sdk\ 裡的 ABI 才會相符。 要支援還停在舊版主程式的客戶,請下載對應版本的 PDK 來編譯。

接下來