SQLite3 在其介面中使用許多 C 結構體,其中大部分是「不透明的」。也就是說,它們的內容對用戶端程式碼是不可見的,並且它們可能會在任何給定的程式庫版本中更改,而不會違反相容性限制。然而,有些結構體不是不透明的,並且被 sqlite3 API 用於實現與用戶端程式碼的雙向通訊。例如 sqlite3_module 和 sqlite3_vfs。
像 sqlite3
和 sqlite3_stmt
這樣的非透明類型,在此 API 中僅以其 WASM 指標值(整數)表示。非透明類型在此 API 中可能有兩種不同的表示形式:
- 它們的 WASM 指標。
- 一個 JavaScript 層級的包裝器,它反映了 C 層級對應結構體的結構,並允許檢查和操作個別結構體實例的內容。
此程式庫中如何完成後者,在 Jaccwabyt 的文件中詳細介紹,這是這個專案的一個衍生子專案,它是為了支援這個程式庫而建立的,但它並不依賴於這個專案(它可以被任意的 WASM/JS 用戶端使用)。本文檔提供了該支援的高層級概述,並描述了如何在程式庫中使用此類繫結。它沒有涵蓋整個 Jaccwabyt API,僅涵蓋可能與 sqlite3 用戶端相關的部分。
簡而言之,此功能的工作原理是:在 C 語言中建立 C 結構體的 JSON 格式描述,將它們導入 JS,然後產生使用 JS 屬性攔截器和 JS 的 DataView
API 通過 WASM 堆積區代理存取 C 層級記憶體的映射。
以下非透明的 sqlite3 結構體被映射到 JS:
- VFS相關:
- 虛擬表格相關:
sqlite3_module
sqlite3_vtab_cursor
sqlite3_vtab
sqlite3_index_info
及其內部類型sqlite3_index_constraint
sqlite3_index_orderby
sqlite3_index_constraint_usage
除了 sqlite3_index_info
的內部類別(它們都是 sqlite3_index_info
的屬性)之外,所有這些在 JS 中都以名為 sqlite3.capi.TheStructName
的建構函數提供1。
實例的(釋放)分配和包裝現有實例
每個 JS 包裝的結構體在此程式庫中有兩種不同的用途:
- 不帶參數地呼叫建構函數會在 WASM 堆積區中建立一個新實例,以便將其連接到 C。在這種情況下,用戶端 JavaScript 程式碼擁有該實例的記憶體,除非某些 API 明確地接管它。
- 將 WASM 指標傳遞給建構函數會為現有的結構體實例(無論它來自 C 還是 JS)建立一個 JS 層級的包裝器,而不會接管該記憶體的所有權。這允許 JS 操作在 C 中建立的實例,而無需接管它們的記憶體2。
這兩種用法都相當普遍,它們之間的區別僅在於它們如何管理(或不管理)結構體的記憶體。
只要結構體實例處於活動狀態,它的 pointer
屬性就會解析為其 WASM 堆積區記憶體位址。該值可以傳遞給任何接受該類型指標的 C 函數。例如:
const m = new MyStruct();
functionTakingMyStructPointers( m.pointer );
當用戶端程式碼使用完一個實例,且沒有 C 層級程式碼正在使用其記憶體時,必須透過呼叫 theStruct.dispose()
來清理該結構實例。多次呼叫 dispose()
並無害處 - 第一次呼叫之後的呼叫都是無操作。對於*包裝*的實例,呼叫 dispose()
並非*嚴格*必要,因為它們的 WASM 堆積記憶體是由其他地方所擁有,但呼叫它是良好的實務做法,因為每個實例可能擁有*結構記憶體以外*的記憶體,詳見下一節。
自定義清理:ondispose
如果指定的 JS 端結構實例具有名為 ondispose
的屬性,則在呼叫 dispose()
時會使用該屬性來釋放可能與該結構關聯的任何額外資源(例如 C 分配的字串或其他結構實例)。
ondispose
預設不會設定,但用戶端可以將其設定為以下其中一項:
- 如果是函式,則會以被釋放的物件作為其
this
值,且不帶任何參數呼叫。它可以執行任意清理工作。 - 如果是陣列,則陣列的每個項目可以是以下任何一項:
- 函式會如上所述被呼叫。
- 任何 JS 繫結的結構實例都會呼叫其
dispose()
方法。 - 數字會被假定為 WASM 指標,並使用
sqlite3.wasm.dealloc()
釋放。 - 任何其他值類型都會被忽略。有時使用字串項目註釋陣列有助於理解程式碼。例如:
x.ondispose = ["Wrapper for this.$next:", y]
ondispose
回呼所拋出的任何例外都會被忽略,但可能會在控制台中產生警告。
用戶端程式碼可以呼叫 aStructInstance.addOnDispose()
將一個或多個參數推送到釋放清單中。該函式會在需要時建立 ondispose
陣列,或者將非陣列的 ondispose
值移入新建立的 ondispose
陣列中。它會返回其 this
值。
訪問結構體成員
可以使用常用的 JS 屬性存取運算子從 JS 存取 C 結構成員。
C 結構及其 JS 對應項之間的一個明顯差異是,C 層級結構成員在 JS 中都有一個 $
名稱前綴。因此,C 中的 myVfs->xOpen
在 JS 中是 myVfs.$xOpen
。這個前綴的存在是為了讓 JS 程式碼的作者和讀者更容易區分 C 層級成員和 JS 層級成員,並避免 C 層級和傳統 JS 層級成員之間的任何命名衝突3。
當從 JS 存取結構層級成員時,屬性攔截器會擷取或指派底層 C 記憶體,並執行適當的類型和位元組順序轉換(如果指派的值無法合理轉換,則會拋出例外)。
存取 C 字串值
特別標記為 C 樣式字串的成員具有其他成員沒有的幾個選項:
structInstance.setMemberCString(memberName, jsString)
會*覆寫*(不釋放)該成員中的任何現有值,將其替換為新配置的 C 字串,並將該 C 字串儲存在實例的ondispose
狀態中,以便在呼叫dispose()
時進行清理。結構不知道在覆寫這些字串時釋放它們是否安全,因此會將以這種方式設定的每個字串添加到ondispose
清單中。structInstance.memberToJsString(memberName)
會擷取成員的值。如果它是 NULL,則返回null
,否則會假定它是有效的 C 樣式字串,並將其副本作為 JS 字串返回。structInstance.memberIsString(memberName)
如果指定的成員名稱被明確標記為字串,則返回 true。
以下限制適用於這些方法:
memberName
參數必須是 JS/C 繫結結構成員的名稱。它可以選擇包含$
前綴字元。- 任何未知的結構成員名稱都會觸發例外。
- 在低層級結構描述中未明確標記為字串的成員會在
setMemberCString()
和memberToJsString()
中觸發例外,但不會在memberIsString()
中觸發例外。
sqlite3 特定的 API 擴展
上述 API 和功能都是 Jaccwabyt 框架的一部分。本節涵蓋專為 sqlite3 API 添加到 struct 框架的功能。
安裝 JS 函數作為方法指標
所有 StructType 實例都繼承以下方法,以協助安裝指向 JavaScript 函數的 C 端成員函數指標。
installMethod()
用法
function installMethod(name, func, applyArgcCheck = false)
structTypeInstance installMethod(methodsObject, applyArgcCheck = false)
第二種形式的行為與 installMethods()
完全相同。
在此物件中安裝指定名稱和函數的 StructBinder 綁定函數指標成員。
它會為指定的函數建立一個 WASM 代理,並安排在呼叫 this.dispose()
時清除該代理。 一旦出現任何錯誤跡象,例如指定的名稱未對應到結構綁定成員,就會拋出錯誤。
作為一種特殊情況,如果指定的函數是一個指標,則會使用 wasm.functionEntry()
來驗證它是否為已知函數。如果是,則按原樣使用它,無需額外的代理或清除層級;否則,會拋出例外。傳遞值 0 是合法的,表示 NULL 指標,但需要注意的是,0 *在* WASM 中是合法的函數指標,但*在這裡*不會被接受為函數指標。(理由:地址為零的函數必須是最初來自 WASM 模組的函數,而不是我們想要綁定到客戶端層級擴充程式碼的方法。)
此函數返回一個綁定到 this
並接受 2 個參數 (name, func) 的自身的代理。該函數返回與此函數相同的結果,允許鏈式呼叫。
如果只使用 1 個參數呼叫,它沒有任何副作用,但會返回一個與上述描述具有相同簽章的函數。
⚠**注意:**⚠ 由於我們無法通用地知道如何將 JS 例外轉換為結果代碼,因此已安裝的函數不會自動攔截例外。為了避免 C 層中的未定義行為,透過此函數對應的方法*不得*拋出例外,這一點至關重要。這個規則的例外是……
如果 applyArgcCheck
為 true,則每個 JS 函數(與函數指標相對)都會被包裝在一個代理中,該代理會斷言傳遞給它的參數數量與預期相符,如果參數數量不符則拋出例外。*這僅用於開發時的健全性檢查*,因為例外傳遞通過此類方法會使 C 環境處於未定義狀態。
installMethods()
structBinderInstance installMethods(methods, applyArgcCheck = false)
將方法安裝到此 StructType 類型的實例中。指定 methods 物件中的每個項目都必須對應到指定 StructType 的已知成員,否則會觸發例外。有關詳細資訊,包括第二個參數的語義,請參閱 installMethod()
。
作為上述的例外,如果 methods 物件中的任何兩個或多個方法是完全相同的函數,則*不會*為第二個和後續實例呼叫 installMethod()
,而是將這些實例分配給為第一個實例建立的相同方法指標。此優化主要是為了適應 sqlite3_module::xConnect
和 xCreate
方法的特殊處理。
成功時,返回此物件。出錯時拋出例外。
- ^ 在 C 語言中,這些結構是在
sqlite3_index_info
中內聯定義的,因此將它們作為該 JS 類別的成員屬性似乎是合適的。 - ^ 注意,如果需要,*可以*使用
ondispose
機制有效地將記憶體傳輸到 JS,但在實務中,這從來沒有必要。 - ^ 須注意,用戶端程式碼可以自由新增帶有 `$` 前綴的純 JavaScript 屬性,但這樣做可能會造成日後程式碼維護上的混淆。