要理解PPAPI插件的設計,先仔細閱讀下面這些文章:
- [**Chromium的Process Models**](http://www.chromium.org/developers/design-documents/process-models)
- [**Chromium的Multi-process Architecture**](http://www.chromium.org/developers/design-documents/multi-process-architecture)
- [**Chromium的Plugin Architecture**](http://www.chromium.org/developers/design-documents/plugin-architecture)
- [**Pepper plugin implementation**](http://www.chromium.org/developers/design-documents/pepper-plugin-implementation)
理解了架構設計,再看代碼層面的文檔:
- [**Important concepts for working with PPAPI**](https://code.google.com/p/ppapi/wiki/Concepts)
- [**GettingStarted, Writing a simple plugin**](https://code.google.com/p/ppapi/wiki/GettingStarted)
有的鏈接需要翻墻,天朝的局域網,我愛死你了。
好啦,現在對PPAPI應該有基本的理解了。接下來我從代碼角度來理解一下。
# Module、Instance、Interface
HTML頁面可以通過embed標簽來嵌入一個插件,HTML頁面被加載時,解析到embed標簽,就會根據type屬性定位我們注冊的PPAPI插件,加載對應的插件庫(DLL)。
當PPAPI的庫文件(DLL)被加載到瀏覽器進程中時,一個Module就產生了。在代碼中,通過PP_Module(定義在pp_module.h中)來表示,用于標識一個Module的PP_Module類型實際上是一個int32。Module的標識符通過PPP_InitializeModule函數傳入。PPP_InitializeModule函數的原型如下:
~~~
int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface) ;
~~~
第一個參數就是瀏覽器分配給你的插件庫文件的標識符(handle),一般你需要保存它,后續的有些API會用到。
所以,一個Module,僅僅標識了library。要想在瀏覽器上顯示點什么,還需要從這個Module里創建一個實例,這個實例代表了我們可以看見并與之交互的網頁對象。一個插件實例對象又有兩個層面的屬性,一個就是標示符,通過PP_Instance(32位整型)來表示;另外一個是用來操作實例對象的接口,用PPP_Instance表示(聚合了各種函數指針的結構體)。
PPAPI的plugin會導出PPP_GetInterface函數,其原型如下:
~~~
const void* PPP_GetInterface(const char* interface_name);
~~~
當瀏覽器要為HTML頁面創建插件實例時,會先調用PPP_GetInterface函數獲取一個實例模板指針(可以理解為PPP_Instance的實例,類似一個類,實際上是一個定義了函數指針成員的結構體)。
PPP_GetInterface函數接受一個字符串名字,返回void*,瀏覽器拿到萬能的void*后會根據名字轉換為具體的PPP_instance接口,在后續使用中就通過PPP_instance接口來與插件實例交互(比如具體的創建、銷毀等動作)。
簡單的理解,就是PPP_GetInterface會返回能創建Instance的接口PPP_Instance,瀏覽器調用PPP_Instance來創建實例并與實例交互。
PPP_instance接口聲明如下:
~~~
struct PPP_Instance_1_1 {
PP_Bool (*DidCreate)(PP_Instance instance,
uint32_t argc,
const char* argn[],
const char* argv[]);
void (*DidDestroy)(PP_Instance instance);
void (*DidChangeView)(PP_Instance instance, PP_Resource view);
void (*DidChangeFocus)(PP_Instance instance, PP_Bool has_focus);
PP_Bool (*HandleDocumentLoad)(PP_Instance instance, PP_Resource url_loader);
};
~~~
如你所見,它就像一個類,DidCreate是構造函數指針,DidDestroy是析構函數指針。創建插件實例對象時,DidCreate會被調用,其第一個參數instance,就是瀏覽器分配給這個插件實例對象的句柄(32位整數),通常我們可以保存起來供后續的調用使用。具體的說明,可以看ppp_instance.h。
之前在[**VS2013編譯最簡單的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)中我們編譯了stub插件,它的PPP_GetInterface函數返回NULL,所以,其實瀏覽器可以加載stub庫文件,生成Module,但無法創建Instance。
要想實作一個有用的PPAPI plugin,必須在PPP_GetInterface中返回真實的PPP_instance接口。下面是graphics_2d_example.c里的PPP_GetInterface函數實現:
~~~
PP_EXPORT const void* PPP_GetInterface(const char* interface_name) {
if (strcmp(interface_name, PPP_INSTANCE_INTERFACE) == 0)
return &instance_interface;
return NULL;
}
~~~
它返回的instance_interface,是這么定義的:
~~~
static PPP_Instance instance_interface = {
&Instance_DidCreate,
&Instance_DidDestroy,
&Instance_DidChangeView,
&Instance_DidChangeFocus,
&Instance_HandleDocumentLoad
};
~~~
如你所見,它是一個PPP_Instance,在定義時進行了初始化,把文件內實現的幾個函數,賦值給了結構體的5個函數指針。
# 瀏覽器接口PPB_GetInterface
PPAPI插件要與瀏覽器交互,也得先有渠道來獲取瀏覽器的功能接口。瀏覽器提供了很多功能接口,比如PPB_INSTANCE_INTERFACE,PPB_IMAGEDATA_INTERFACE,PPB_GRAPHICS_2D_INTERFACE等。
這些宏都是字符串,瀏覽器提供的接口名字宏,以PPB_開頭,插件提供的,以PPP_開頭。
插件被加載時,模塊初始化函數PPP_InitializeModule會被調用,其原型如下:
~~~
int32_t PPP_InitializeModule(PP_Module module_id,
PPB_GetInterface get_browser_interface);
~~~
注意第二個參數,get_browser_interface,它的類型是PPB_GetInterface,是一個函數指針,定義如下:
~~~
typedef const void* (*PPB_GetInterface)(const char* interface_name);
~~~
如你所見,這是一個接受一個字符串參數返回void*的函數指針。插件可以在PPP_InitializeModule被調用時保存第一個參數,用它來獲取瀏覽器提供的各種接口。根據接口名字,把返回的void*強制轉換為對應的接口來使用。參看graphics_2d_example.c里的實現:
~~~
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module,
PPB_GetInterface get_browser_interface) {
g_get_browser_interface = get_browser_interface;
g_core_interface = (const PPB_Core*)
get_browser_interface(PPB_CORE_INTERFACE);
g_instance_interface = (const PPB_Instance*)
get_browser_interface(PPB_INSTANCE_INTERFACE);
g_image_data_interface = (const PPB_ImageData*)
get_browser_interface(PPB_IMAGEDATA_INTERFACE);
g_graphics_2d_interface = (const PPB_Graphics2D*)
get_browser_interface(PPB_GRAPHICS_2D_INTERFACE);
g_view_interface = (const PPB_View*)
get_browser_interface(PPB_VIEW_INTERFACE);
if (!g_core_interface || !g_instance_interface || g_image_data_interface ||
!g_graphics_2d_interface || !g_view_interface)
return -1;
return PP_OK;
}
~~~
當我們拿到了瀏覽器暴露的各種接口,就可以做想干的事情了。
對于PPAPI插件的設計,先理解到這里,下次我們看插件的加載與使用流程、如何繪圖、如何處理交互。
相關文章參考:
- [**CEF Windows開發環境搭建**](http://blog.csdn.net/foruok/article/details/50468642)
- [**CEF加載PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485448)
- [**VS2013編譯最簡單的PPAPI插件**](http://blog.csdn.net/foruok/article/details/50485461)
- 前言
- CEF Windows開發環境搭建
- CEF加載PPAPI插件
- VS2013編譯最簡單的PPAPI插件
- 理解PPAPI的設計
- PPAPI插件與瀏覽器的交互過程
- Windows下從源碼編譯CEF
- 編譯PPAPI的media_stream_video示例
- PPAPI插件的繪圖與輸入事件處理
- 在PPAPI插件中創建本地窗口
- PPAPI插件與瀏覽器的通信
- Windows下從源碼編譯Skia
- 在PPAPI插件中使用Skia繪圖
- 加載DLL中的圖片資源生成Skia中的SkBitmap對象
- PPAPI+Skia實現的涂鴉板
- PPAPI中使用Chromium的3D圖形接口
- PPAPI中使用OpenGL ES繪圖