NO IMAGE

要理解PPAPI外掛的設計,先仔細閱讀下面這些文章:

理解了架構設計,再看程式碼層面的文件:

有的連結需要翻牆,天朝的區域網,我愛死你了。

好啦,現在對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外掛中我們編譯了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外掛的設計,先理解到這裡,下次我們看外掛的載入與使用流程、如何繪圖、如何處理互動。

相關文章參考: