1. 程式人生 > >基於 wke 的瀏覽器:如何實現 js 和 c++ 的互相呼叫

基於 wke 的瀏覽器:如何實現 js 和 c++ 的互相呼叫

一、引言

最近,老大給了我一個學習研討任務,也就是如何讓 js 和 C++ 進行互調使用。比如我可以在網頁中,使用 js 程式碼呼叫 c++ 函式,也可以在 c++ 函式中呼叫 js 對於介面進行控制。

這是為後期的軟體接入 Html5 做技術調研。

那麼如何實現呢?

這裡需要感謝 Redrain 的部落格給Webkit核心的瀏覽器控制元件增加互交功能,通過參考他的部落格,加之我自己的探索,一步一坑終於完成了這個 demo。

如果想要直接看大神的部落格可以點選上面那個連結,如果你是個跟我一樣在之間一無所知的小白,那就可以看看我作為小白的一步一步的探索吧,應該會比上面大神的部落格詳細一些。

二、基於 SOUI 的瀏覽器控制元件

我這個瀏覽器 demo 是基於 SOUI 介面庫,而 Redrain 的 demo 是基於 duilib 的。其中 SOUI 介面庫是我工作中接觸到的第一個開源的介面庫,功能非常強大,感興趣的同學可以點選這裡 UI神奇-SOUI

其實用的什麼介面庫在這裡真的沒有什麼大的影響,因為都是同樣封裝了 wke 的瀏覽器核心來呼叫而已。那麼 wke 是什麼呢? wke 是 BlzFans (國內大神)對於 webkit 瀏覽器核心的一個小巧而又強大的封裝庫,可以實現瀏覽器核心的相關功能。

那麼,怎麼開始呢?我們就假設你已經有了一個可以訪問網頁的瀏覽器 demo 了,並且使用的是 wke 核心。接下來我們就一步一步的實現 js 和 c++ 之間的互動吧。

三、c++ 呼叫 js

首先,我們來實現稍微簡單點的,c++ 呼叫 js。

如何實現呢?其實我們仔細翻看 wke.h 檔案,就可以看到其實 blzFans 已經封裝好了。我們開啟 wke.h 檔案,可以看到以下的兩行程式碼:

virtual jsValue runJS(const utf8* script) = 0;
virtual jsValue runJS(const wchar_t* script) = 0;

通過函式名稱,我們就可以推測這是呼叫 js 的方法,那麼我們只需要在封裝瀏覽器控制元件的地方,封裝一個函式方法,呼叫 runJS() 方法即可。

這裡我使用的是 SOUI 介面庫,在 demo 裡面已經封裝好了一個基本成型的瀏覽器控制元件,這裡直接在 SWkeWebkit

類中新增 RunJS() 方法即可:

SStringW SWkeWebkit::RunJS(SStringW strValue)
{
    if (m_pWebView == NULL) return SStringW(L"wke 物件為空");
    jsValue jsRet = m_pWebView->runJS(strValue);
    return jsToStringW(m_pWebView->globalExec(), jsRet);
}

這裡需要解釋一下,SStringW是 SOUI 介面庫封裝的字串類,我們將 wke 物件設定為 SWkeWebkit類的私有變數進行儲存,這裡由 m_pWebView就可以來呼叫 runJS() 方法呼叫 js 程式碼了。

值得一說的是最後的 return 句,這裡是參考 Redrain 的程式碼寫的,剛開始也不知道是什麼含義,經過多次測試後發現,這一句可以用來返回可能需要返回的 js 程式碼結果,比如尋找一個元素後返回某些屬性值什麼的。

比如說,我在 demo 裡面寫了一個 index.html ,其中有這麼一句程式碼:

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />

這個 img 元素的 id 值為 img_track_event_id,name 值為 img_track_event_name,然後我們可以嘗試用 c++ 呼叫 js 程式碼:

document.getElementById("img_track_event_id").name

來返回我們需要返回的 img 控制元件的 name 值。

c++呼叫js程式碼返回值測試

這裡可以看到,我在瀏覽器位址列輸入了 js 程式碼,並點選 run js 後,響應按鈕資訊,從位址列獲取到了 js 程式碼,執行後獲取到了 js 返回的值,並以彈框形式顯示出來。

這裡我感覺我已經講述的非常詳細了,其中關於如何自己在本地建立一個靜態的 html 檔案進行測試還沒有細說,下一節將會講到,其他如果還有不懂的地方,可以參看我的 demo。

四、js 呼叫 c++

在之前,我們已經實現了 c++ 呼叫 js 的功能,是通過 wke 封裝好的 runJS方法實現的。那麼 js 呼叫 c++ 應該如何實現呢?

甚至有些人會問, js 呼叫 c++ 應該如何測試呢?

1. 建立測試網頁

其實在上一節,我漏了很重要的一點沒有講述,那就是如何建立本地的網頁測試。為什麼要建立呢?因為我們要讓 js 與 c++ 互動,就必定需要一個本地的可以測試的介面。

這裡我在軟體執行路徑下,建立了一個資料夾 Html,在這個資料夾中建立了檔案 index.html,這個 html 檔案裡面主要就是有一個 img 元素,用來響應指定的 js 方法。

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />

至於這個 index.html檔案如何書寫,可以參看我的 demo 專案,這裡就不再贅述了(其實作為一個小白,這一塊我也是遇到坑了的,不過可以看程式碼解決的問題,就不在部落格裡說了,程式碼裡一切都有)。

需要說一下的是,如何使用 wke 載入本地的 html 檔案呢:

webView->loadFile(_T("Html/index.html"));
bool b = webView->isLoaded();

只需要讓 wke 物件呼叫 loadFile方法即可,注意這裡的路徑設定,需要將 Html 資料夾放到程式的當前執行目錄下(或許有些人會問,怎麼知道程式的當前執行目錄呢?一般都是 sln 的同級目錄,不過也可以使用 GetCurrentDirectory函式來獲取,我就是這麼獲取到的)。

到了現在,我們已經建立了測試的環境,並編寫好了測試的網頁元素,那麼讓我們開始吧!

2. 檢視 wke 相關介面

首先,讓我們看看 wke 提供的介面函式:

WKE_API void jsBindFunction(const char* name, jsNativeFunction fn, unsigned int argCount);
WKE_API void jsBindGetter(const char* name, jsNativeFunction fn); /*get property*/
WKE_API void jsBindSetter(const char* name, jsNativeFunction fn); /*set property*/

這裡 jsBindFunction就是用來繫結 c++ 函式的方法。那麼回撥函式應該怎麼寫呢?

#define JS_CALL __fastcall
typedef jsValue (JS_CALL *jsNativeFunction) (jsExecState es);

Got it!

我們現在有了繫結的函式,有了回撥函式,只要琢磨清除用法就可以使用了!

那麼該如何使用呢?

首先,在 index.html 中寫好我們觸發的 js 函式:

<script type="text/javascript">
  function CallCPlusPlus() {
      msgBox("點選圖片由 js 呼叫 C++ 彈窗", "提示");
  }
</script>

這裡需要解釋下的就是,msgBox我理解為就是一個標籤,是一個什麼標籤呢?是用來在繫結的時候,讓 wke 知道,哦,我在 js 中看到了 msgBox我就把它與某個 c++ 函式繫結起來,然後 msgBox函式裡面自然可以傳入引數。這樣,通過這個標籤,就完成了 js 和 c++ 對於同一個函式的認定響應。

然後,我們在 html 中編寫能夠觸發我們這個 js 函式的元素:

<img id="img_track_event_id" name="img_track_event_name" class="" src="images/bd_logo.png" width="300" height="128" onmousedown="CallCPlusPlus()" />

注意這裡選擇了 onmousedown也就是滑鼠單擊行為觸發此 js 函式,至此,我們已經完成了介面上需要做的事情。

再然後,我們到呼叫處,編寫我們需要繫結的全域性的 c++ 函式:

// 全域性的 js 呼叫 c++ 的函式
jsValue JS_CALL jsMsgBox(jsExecState es)
{
    const wchar_t *text = jsToStringW(es, jsArg(es, 0));
    const wchar_t *title = jsToStringW(es, jsArg(es, 1));

    SOUI::SMessageBox(NULL, text, title, MB_OK);

    return jsUndefined();
}

這裡需要解釋下,jsMsgBox並不是我們之前在 js 函式中寫的那個標籤 msgBox 。我們在 js 函式裡的只是一個繫結時 c++ 和 js 雙方都認定的一個標籤而已,c++ 函式的名稱是可以隨便取的,但是函式返回值和引數必須與 wke 宣告一致。

另外,傳入的引數只有一個es ,wke 通過 jsToStringW(es, jsArg(es, 0))來解析出函式所需要的引數。這裡我們解析出來了兩個引數,並使用 SMessageBox函式顯示出來,這裡的 SMessageBox是 SOUI 封裝的彈框類。

最後,我們將 js 中的標籤 msgBox 與 全域性函式 jsMsgBox 繫結起來即可:

// 繫結 js 函式,讓 js 主動呼叫 C++ 函式
jsBindFunction("msgBox", jsMsgBox, 2);

這裡 jsBindFunction函式,第一個引數的含義就是 js 函式中使用到的標籤,第二個引數是需要繫結的 c++ 函式名,第三個引數是引數個數。

至此,我們完成了 c++ 部分的編寫。

現在,我們執行程式,點選 run c++ ,進入測試介面,點選圖片,觸發 js 方法,呼叫 c++ 方法,彈框:

js呼叫c++

成功撒花!!!

五、總結

這些實現的方法都不是我做的。

SOUI 介面庫 是 SOUI 作者啟程寫的;
wke 瀏覽器核心是 BlzFans 寫的;
js 和 c++ 互調方法是 Redrain 寫的;

這裡我只是摸索著他們的後路,將自己同樣實現後的經驗和感想分享。

最後,附上我的 demo 程式碼的 github 地址,希望能對有需要的同學有點啟發,JsCplusplusInteractons