要理解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绘图