XPConnect 遇到的小問題

在 XPCOM 的世界裡,透過 XPConnect 的幫忙,C 和 JavaScript 實作的元件可以互相地呼叫,因此我們可以自由選用合適的語言來開發各自的元件。
但是因為中間包了一層 wrapper,所以在某些特殊的情況下,程式的運作可能會不如你的預期:例如在 跨越語言的邊界 – 淺談 JS API 與 XPConnect 有提到,動態改變 object property 時只會改變到 wrapper 而不會改變原本的物件。
這在 XPCOM 以 interface 為溝通管道的方式之下,也通常沒有什麼問題,不過今天要討論的,是一個會發生問題的情形。

首先,我們實作了一組 nsIDownloader 介面,然後因為下載通常很慢,所以這個介面使用了一個 nsIDownloadEventListener 的介面來通知下載的結果:

這一切看起來是如此地美好,那我們就用 C 來實作這組介面吧(因為篇幅的關係,以下程式碼會省略部份錯誤檢查):

在這裡, nsDownloader 使用了一個 nsCOMPtr 來存放 listener 的指標。
然後在另一個 class 裡,如果要使用 nsDownloader 的話,可能會這麼寫:

大功告成,一切正常…正當你這麼以為的時候,reviewer 就跳出來告訴你:這段程式碼會有 memory leak。

因為 nsFoo 握有 nsDownloader ,而 nsDownloader 也握有 nsFoo ,這個問題就是在 說說 nsCOMPtr 這東西 裡面提過的環狀參照。
怎麼辦呢?有兩個解法:說說 nsCOMPtr 這東西 告訴我們如果參照的對象有實作 nsIWeakReference 的話,就可以使用 nsWeakPtr 來解決這個問題,或是使用 淺談 Cycle Collection 提到的 Cycle Collection 機制。

我們先用 nsWeakPtr 來試著一解決問題,所以 nsDownloader 就變成這樣:

但是 nsFoo 沒有實作 nsIWeakReference 呀!怎麼辦?
當然,我們可以修改 <span class="lang:default decode:true casino pa natet crayon-inline “>nsFoo 讓它實作 nsIWeakReference ,當你這樣告訴 nsFoo 的作者時,他可能會說因為 nsDownloader 的生命週期被包含在 nsFoo 的裡面,也就是說 nsDownloader 裡面的 mListener 不會是一個 dangling pointer,於是他讓你用 raw pointer 直接指向 listener,然後就有了下列的修改。

因為 raw pointer 也算是一種 weak reference,這麼一來,環狀參照的問題解決了,程式似乎也正常地執行了。

故事就這麼結束了嗎?
過了幾天,另外一位仁兄用 JavaScript 寫了另一份 nsFoo 的實作。

然後就悲劇了…

為什麼呼叫 nsIDownloader.download 會失敗呢?這個 NS_ERROR_UNEXPECTED 又是哪裡來的?且讓我們繼續看下去。

XPConnect wrappers 告訴我們,從 C 的 code 要執行 JavaScript 的程式碼,會透過 nsXPCWrappedJS 來操作,另外在 XPCWrappedJS.cpp 裡面有一段註解提到 nsXPCWrappedJS object 的生命週期,意思是說當 nsXPCWrappedJS object 的參考計數降為 1 的話,表示只有它握有的那個 JavaScript object 指向它,這時候如果也沒有任何一個 weak reference 指向這個 nsXPCWrappedJS object 的話,表示 C 這邊已經不會再有人會存取這個物件了,那麼這個物件會立刻被銷毀!也就是說,使用 raw pointer 存的 listener 不管它實際上的 JavaScript object 是不是還活著,它的 nsXPCWrappedJS object 都會失效,而解法就是把 raw pointer 改成 nsWeakPtr

這就是最有趣的地方了,當你因為某種需求而要握有一個物件的參考時,你必須要求它支援 nsWeakPtr ,不然就只能使用 strong reference。你不能使用 raw pointer,否則若該物件是用 C 實作的話沒事,當那個物件如果是用 JavaScript 來實作的話就會發生 NS_ERROR_UNEXPECTED 錯誤。

好,為了避免上述的問題,這時候我們先修改 nsDownlaoder ,讓它改用 nsWeakPtr

要讓 nsFooJS 支援 nsWeakPtr ,在 Bug 66950 裡面有提到,只要讓 JavaScript object 在 QueryInterface 裡宣告有實作 nsISupportsWeakReference 即可,然後你會發現其實有很多 JavaScript 實作的元件都有宣告這個介面。

如果要讓 nsFoo 支援 nsWeakPtr 的話,可以參考 Weak Reference 的介紹。
可是如果要考慮並不是所有的對象都可以支援 nsWeakPtr 的話,那麼就只好使用 nsCOMPtr ,然後用 Cycle Collection 來處理可能發生的環狀參照問題了。

圖片出處

[1] 跨越語言的邊界 – 淺談 JS API 與 XPConnect
[2] 說說 nsCOMPtr 這東西
[3] 淺談 Cycle Collection
[4] XPConnect wrappers
[5] XPCWrappedJS.cpp
[6] Bug 66950
[7] Weak Reference

 

您可能也會喜歡

目前找不到相關文章

對此文章發表回應

你的電子郵件位址並不會被公開。 必要欄位標記為 *