PPAPI插件開發指南

轉載請註明出處:http://www.cnblogs.com/fangkm/p/4401075.htmljavascript

前言

插件一直是瀏覽器的重要組成部分,豐富瀏覽器的運行能力,實現一些HTML+JS實現不了本地應用(好比音視頻、文件操做等)。早期廣爲熟知的是IE下的插件ActiveX,這是一項熟悉可能暴露年齡的技術,它基於COM規範,在IE佔瀏覽器市場主流份額的時代,ActiveX可謂出盡了風頭,但它並非瀏覽器業內的插件標準。由網景發佈的NPAPI長久以來是除了IE以外的其餘瀏覽器共同支持的業內標準。ActiveX和NPAPI長久共存,若是某項業務要發佈瀏覽器插件,通常都須要實現這兩種插件規範。有個開源的插件開發框架FireBreath 抽象了這兩種插件的接口細節,讓開發者專一於業務邏輯(我的比較嫌棄FireBreath 開發出來的插件體積太大)。早期的銀行開發網頁安全控件都是使用ActiveX開發,因此他們都要求在IE瀏覽器或者其餘雙核瀏覽器的兼容模式下登陸。如今不少銀行已經支持NPAPI版本的安全控件,但NPAPI卻作爲不安全因素被主流瀏覽器(Chrome、FireFox)拋棄,好在國內的雙核瀏覽器都保留有NPAPI功能的移植,讓市面上大量的NPAPI插件得以正常運行。如今,隨着IE逐漸退出歷史舞臺,ActiveX插件的運行範圍愈來愈窄,NPAPI也成爲過期的插件標準,雖然HTML5標準也在逐漸完善,不少以前只靠Web技術作不到的功能都能獲得很好的支持,可是並非全部的功能都能經過HTML5實現,本地插件依然是咱們這個時代繞不開的功能應用,最典型的好比Flash。因而Chrome提出了名叫PPAPI的全新插件機制,運行在Chrome瀏覽器的沙箱環境。html

插件類型前端

Chromejava

基於Chromium核的瀏覽器chrome

IE編程

ActiveXapi

No瀏覽器

No安全

Yes服務器

NPAPI

No

Yes(暫時兼容)

No

PPAPI

Yes

Yes

No

從對比能夠看出,PPAPI的運行場景徹底覆蓋NPAPI,之後開發插件徹底能夠只開發PPAPI+ActiveX兩個版本。可是目前市面上,除了內置的PPAPI版本的Flash組件以及Pdf組件,沒遇到其餘的PPAPI版本的商業組件,可見再造一個平臺是多麼地不容易。

PPAPI組件介紹

在Chrome中,PPAPI組件分爲兩種存在形式:

可信組件:可信的PPAPI組件能夠經過平臺動態庫的形式(Windows下爲dll文件,Linux下是so文件)由瀏覽器直接加載,好比內置的Flash組件、Pdf組件,或者經過指定命令行參數--register-pepper-plugins來加載,好比:chrome --register-pepper-

plugins="D:\\ppapi\\ppapi_example_gles2.dll;application/x-ppapi-example-gles2" D:\\ppapi\\gles2.html,這種方案經常使用於調試。可信的PPAPI組件以平臺動態庫的形式存在,因此通常Chrome沙箱內容許的API(好比CreateThread)均可以調用。

不可信組件:不可信的PPAPI組件須要使用Native Client機制發佈(以Native Client的形式發佈到Chrome Web Store或者以Portable Native Client的形式發佈了網址的Web服務器上),而Native Client須要維繫跨平臺可移植性,徹底與系統API告別了,只能使用PPAPI提供的開發庫和C/C++運行庫,不過目前來看,基本的需求也是能夠知足的。

下面貼一段PPAPI與JS交互的典型場景:

<object id="plugin" type="application/x-ppapi-post-message-example"  width="1" height="1"/>
  function HandleMessage(message_event) {
    if (message_event.data) {
      alert("The string was a palindrome.");
    } else {
      alert("The string was not a palindrome.");
    }
  }

  function AddListener() {
    var plugin = document.getElementById("plugin");
    plugin.addEventListener("message", HandleMessage, false);
  }
  document.addEventListener("DOMContentLoaded", AddListener, false);

  function SendString() {
    var plugin = document.getElementById("plugin");
    var inputBox = document.getElementById("inputBox");

    // Send the string to the plugin using postMessage.  This results in a call
    // to Instance::HandleMessage in C++ (or PPP_Messaging::HandleMessage in C).
    plugin.postMessage(inputBox.value);
  }

JS經過掛接插件的message事件來接收C/C++發送過來的通知,同時能夠經過調用插件的postMessage方法給C/C++發送消息。

PPAPI的開發接口

不管是可信的仍是不可信的PPAPI組件,通常都會提供三個導出函數:

int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface);

初始化PPAPI模塊,傳入瀏覽器生成的模塊句柄PP_Module,同時經過get_browser_interface參數給插件模塊提供瀏覽器宿主常見的功能接口,好比PPB_Core、PPB_Messaging 、PPB_URLLoader、PPB_FileIO等接口。

void PPP_ShutdownModule(void);

關閉模塊

const void* PPP_GetInterface(const char* interface_name);

提供給瀏覽器宿主的功能接口,好比PPP_Instance、PPP_InputEvent、PPP_Messaging等接口。

除了PPP_ShutdownModule是可選的,其餘兩個導出函數必須實現。PPB_GetInterface和PPP_GetInterface是插件(PPAPI)和宿主(瀏覽器的Plugin進程)功能交互的惟一切入口。

命名規範上,宿主Browser提供給插件(PPAPI)的接口以PPB命名開頭,能夠從導出函數PPP_InitializeModule 傳入的PPB_GetInterface參數中根據接口ID查詢對應的功能接口,下面列舉一些經常使用的功能接口:

功能說明

接口ID

接口定義

重要成員方法說明

核心基礎接口

PPB_CORE_INTERFACE

PPB_Core

AddRefResource、ReleaseResource:增長/減小資源的引用計數GetTime、GetTimeTicks:獲取當前的時間CallOnMainThread、IsMainThread:主線程相關的處理

消息接口

PPB_MESSAGING_INTERFACE

PPB_Messaging

PostMessage:拋送消息(Plugin->JS,JS經過addEventListener掛接message事件)

RegisterMessageHandler、UnregisterMessageHandler:自定義消息處理對象,用於攔截PPP_Messaging接口的HandleMessage處理(應用場景未明)

HTTP請求接口

PPB_URLLOADER_INTERFACE

PPB_URLLoader

經常使用的HTTP請求操做:Open、GetResponseInfo、ReadResponseBody、Close等等

文件IO操做

PPB_FILEIO_INTERFACE

PPB_FileIO

File的基本操做:Create、Open、Read、Write、Close等

網絡操做

PPB_UDPSOCKET_INTERFACE、

PPB_TCPSOCKET_INTERFACE、

PPB_HOSTRESOLVER_INTERFACE等

PPB_UDPSocket、

PPB_TCPSocket、

PPB_HostResolver等

UDP套接字、TCP套接字、域名解析服務等經常使用的操做接口。

音視頻操做

PPB_AUDIO_INTERFACE、

PPB_VIDEODECODER_INTERFACE、

PPB_VIDEOENCODER_INTERFACE等

PPB_Audio、

PPB_VideoDecoder、

PPB_VideoEncoder等

封裝音視頻的編解碼等操做。

 

此外還有Image、2D、3D等接口,基本上知足開發本地應用的全部功能。

插件(PPAPI)提供給宿主Browser的接口以PPP命名開頭

功能說明

接口ID

接口定義

重要成員方法說明

插件實例對象

PPP_INSTANCE_INTERFACE

PPP_Instance

DidCreate、DidDestroy:插件建立銷燬通知

DidChangeView、DidChangeFocus:插件尺寸、焦點改變通知

HandleDocumentLoad:Document加載完成通知

這些調用最終都映射到Instance實例的相關成員方法上。

輸入接口

PPP_INPUT_EVENT_INTERFACE

PPP_InputEvent

HandleInputEvent:處理輸入消息,最終轉接到Instance的HandleInputEvent接口

消息接口

PPP_MESSAGING_INTERFACE

PPP_Messaging

HandleMessage:處理消息(JS->Plugin, JS調用插件的postMessage方法),最終映射到Instance實例的HandleMessage方法。

除了這三個基礎的接口,Module對象(後面再細講)的AddPluginInterface提供了擴展插件接口的功能,好比:

封裝PDF插件功能的PPP_Pdf接口;

封裝視頻採集功能的PPP_VideoCapture_Dev接口;

封裝視頻硬件解碼的PPP_VideoDecoder_Dev接口;

封裝3D圖形相關的通知事件的PPP_Graphics3D接口;

此外還有PPP_MouseLock、PPP_Find_Private、PPP_Instance_Private等私有接口。

 

插件實例: 每類插件都支持建立多個插件實例(好比瀏覽器打開多個帶有Flash元素的頁面),每一個插件實例都對應一個PP_Instance句柄。

資源:由PP_Resource表示,這裏的資源只是一種概念,由於前面提到的PPB、PPP接口都是模塊級別,調用的時候須要傳入PP_Instance句柄表示此次調用派往哪一個插件實例,同時,某個功能的調用須要記錄調用上下文,好比PPB_URLLoader,開啓某個URL請求,讀取某個已打開的URL請求多少字節、關閉某個已打開的URL請求,這個過程當中「某個」就是用PP_Resource來表示,它是用來銜接插件端和宿主容器功能調用上下文的句柄。每一個PP_Resource都隸屬於一個PP_Instance。PP_Resource資源對象採用引用計數的方式維繫生命週期,開發者可使用PPB_Core接口的AddRefResource和ReleaseResource來對PP_Resource的引用計數進行增減。

PP_Instance、PP_Resource不只是PPAPI插件與PPAPI Plugin宿主之間的交互橋樑,同時也是PPAPI Plugin宿主進程與瀏覽器Browser進程和Render進程之間的銜接,對於某個功能Resource,好比URLLoader,從插件模塊的角度來看,插件模塊封裝接口調用的Proxy端,Plugin宿主進程是真正實現功能的Stub端,但Plugin進程也不是真正執行請求的地方,由於Plugin進程運行在沙箱裏面,權限受限,文件操做、硬件訪問等之類的功能都作不到。從Plugin進程的角度看,Plugin進程也只是個Proxy,Stub是Browser進程(有些功能Resource,Stub是Render進程,好比音視頻編解碼等)。他們以前都是經過PP_Instance和PP_Resource來作銜接標識。

介紹完基礎概念,下面介紹開發框架。原生的開發接口都是C形式的,可使用這些C接口直接開發你的應用:實現三個前面提到的導出函數,尤爲是必須實現PPP_InitializeModule和PPP_GetInterface,保存宿主傳過來的PPB_GetInterface功能接口,提供給宿主本身的PPP_GetInterface調用,代碼示例以下:

PP_Module g_module_id; PPB_GetInterface g_get_browser_interface = NULL;
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface) {
// Save the global module information for later. g_module_id = module_id; g_get_browser_interface = get_browser_interface; return PP_OK; }
PP_EXPORT
void PPP_ShutdownModule() { } PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { // You will normally implement a getter for at least PPP_INSTANCE_INTERFACE // here. return NULL; }

C語言的相關接口請參見前面的章節。

除了C接口以外,PPAPI框架還提供了這些C接口的C++封裝版本,使用C++版本能夠忽略一些接口細節,好比PP_Resource。下面詳細說下PPAPI的C++開發庫。

首先,使用C++版本,開發者就無需關心三個導出函數的具體實現,框架在ppp_entrypoints.cc文件裏對三個導出函數作了實現,建立Module對象標識整個插件模塊:

 

開發者只須要提供本身的Module實現—MyModule,定製本身的插件實例MyInstance便可,示例代碼:

class MyInstance : public pp::Instance { public: explicit MyInstance(PP_Instance instance) : pp::Instance(instance) {} virtual ~MyInstance() {} virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { return true; } }; // This object is the global object representing this plugin library as long // as it is loaded.
class MyModule : public pp::Module { public: MyModule() : pp::Module() {} virtual ~MyModule() {}
// Override CreateInstance to create your customized Instance object. virtual pp::Instance* CreateInstance(PP_Instance instance) { return new MyInstance(instance); } }; namespace pp { // Factory function for your specialization of the Module object. Module* CreateModule() { return new MyModule(); } } // namespace pp

在Module的實現中,除了保存PPP_InitializeModule導出函數傳送過來的PPB_GetInterface接口並經過GetBrowserInterface接口暴露出去,同時也對PPP_GetInterface導出函數作了實現,對PPP_INPUT_EVENT_INTERFACE、PPP_INSTANCE_INTERFACE、PPP_MESSAGING_INTERFACE三個PPP功能接口作了封裝,把Plugin宿主的調用所有轉接到Instance實現的對應成員方法上。除了支持這三個核心的PPP接口,Module還提供了AddPluginInterface方法讓其它的功能組件定製其它的PPP接口實現,好比以前接口說明中提到的PPP_VideoCapture_Dev、PPP_Graphics3D等。

Instance主要成員方法說明

成員方法

功能說明

Init

初始化,在PPP_Instance接口的DidCreate建立Instance實例後調用

DidChangeView

一樣來自PPP_Instance接口的通知,在插件顯示區域建立或改變時觸發,通知參數被封裝成View類,View類經過PPB_View接口能夠獲取插件的區域、縮放比例等信息

HandleInputEvent

來自PPP_InputEvent接口的通知,處理用戶輸入事件(鼠標、鍵盤等),具體請查看InputEvent類的封裝。

HandleMessage

來自PPP_Messaging接口,接收前端JS發送過來的消息。

PostMessage

調用PPB_Messaging接口的PostMessage接口,功能與HandleMessage相對,給前端JS發送通知。

BindGraphics

調用PPB_Instance的BindGraphics接口,指定渲染接口,有Graphics2D、Graphics3D、Compositor三種渲染方式。

 

前面說過,每一個功能都對應一個PP_Resource句柄,PPAPI的C++庫使用Resource類封裝了PP_Resource句柄(主要封裝PPB_Core接口對引用計數的調用),具體的功能類都派生自Resource。

 

上圖列舉了一些具備典型意義的功能類封裝:

View:獲取插件的界面顯示區域信息,封裝PPB_View接口。相似的UI封裝還有Fullscreen

FileIO:文件IO操做(文件讀取/寫入),封裝PPB_FileIO接口調用。FileIO類並非咱們平時理解的直接操做磁盤上某個目錄讀寫操做,它使用HTML5 的本地文件系統FileSystem API,讀寫的文件路徑都是在這個系統下的相對目錄,File System API能夠理解爲Chromium沙箱內的文件系統,頁面之間是隔離的。相似的還有DirectoryEntry(目錄操做)。

VideoEncoder:視頻編碼操做,封裝PPB_VideoEncoder接口調用,相似的還有VideoDecoder(視頻解碼)、VideoFrame(視頻幀數據封裝)、AudioEncoder(音頻編碼)、Audio(音頻播放控制)、AudioBuffer(音頻幀數據封裝)、AudioInput_Dev(音頻採集)、VideoCapture_Dev(視頻採集)等等,PPAPI的音視頻接口較多,有效地彌補了HTML5這方面的不足。從Chromium源碼端分析,目前音頻編碼只支持OPUS,視頻編解碼支持H26四、VP8/VP9。

編碼方面:H264只支持硬件編碼,VP8/VP9同時支持硬件和軟件編碼

解碼方面:若是指定使用硬件加速,H264和VP8/VP9優先使用硬件解碼(GPU),失敗後再嘗試軟件解碼(使用libvpx和FFmpeg)。

TCPSocket、URLLoader:網絡相關的操做,一樣的封裝還有NetAddress、UDPSocket、HostResolver等。

Graphics3D:3D渲染邏輯,封裝PPB_Graphics3D接口,相似的還有Graphics2D、Compositor方式。

 

PP_CompletionCallback概念:封裝了編程中的任務操做,PPAPI中耗時的操做都採用異步的編程方式,好比音視頻編碼、文件讀取、URL請求等操做,這些操做不會立馬返回,須要傳入一個回調接口來接受完成通知。這個回調接口的C語言定義格式以下:

struct PP_CompletionCallback { /** * This value is a callback function that will be called, or NULL if this is * a blocking completion callback. */ PP_CompletionCallback_Func func; /** * This value is a pointer to user data passed to a callback function. */
  void* user_data; /** * Flags used to control how non-NULL callbacks are scheduled by * asynchronous methods. */ int32_t flags; };

PP_CompletionCallback_Func的原型:

typedef void (*PP_CompletionCallback_Func)(void* user_data, int32_t result); 

好比PPB_VideoEncoder接口的Encode方法,其原型以下

  int32_t (*Encode)(PP_Resource video_encoder, PP_Resource video_frame, PP_Bool force_keyframe, struct PP_CompletionCallback callback);

當這一幀編碼完成時,會回調傳入的callback接口,user_data參數原封不動傳回去,result指定編碼的結果,成功值是PP_OK。user_data參數原封不動地一來一回,一個很重要的考慮就是兼容C++的成員方法作爲回調函數的處理邏輯。關於如何將C++的成員函數轉成PP_CompletionCallback_Func風格的C函數,請參考CompletionCallback和CompletionCallbackFactory的代碼邏輯,CompletionCallbackFactory還實現了一套簡陋的參數Bind機制,此外還有一個CompletionCallbackWithOutput模板類,用於支持帶輸出參數的CompletionCallback 版本,原理上也僅僅是在CompletionCallbackWithOutput內部建立一個成員變量做爲PPB接口的參數。從我的角度看,這些看似複雜的玩意玩不轉就不玩,不影響使用,再說CompletionCallbackWithOutput實現的也不夠通用,只支持一個輸出參數。 

PPB_MessageLoop:對應的C++封裝類是MessageLoop,是線程環境執行的重要輔助類。將MessageLoop對象附加到當前線程,線程間投遞任務很是方便。

此外還有一套模擬腳本變量聲明的組件:Var、VarArray、VarDictionary等,主要是爲了方便postMessage、HandleMessage與JS交互的接口數據,抽象具體的類型信息,避免過載,還有Point、Rect、Size等經常使用的UI基礎封裝,這些都沒有細講的必要。

相關文章
相關標籤/搜索