PPAPI插件与浏览器的交互过程
最后更新于:2022-04-01 16:02:49
[上一篇](http://blog.csdn.net/foruok/article/details/50486788)理解了一下PPAPI的设计,并从代码角度理解了一下相关主题,这篇文章关注下面几点:
- 插件实例对象的创建与使用流程
- 实例大小的确认
- 渲染(绘图)
- 处理输入事件
> foruok原创,如需转载请关注foruok的微信订阅号“程序视界”联系foruok。
# 插件实例对象的创建与使用流程
画了一个简单的调用流程图:
![pppapi_call_flow](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-22_56caac332a630.jpg "")
结合stub.c或graphics_2d_example.c的代码,应该比较容易理解。
前两步我们在上一篇文章“[**理解PPAPI的设计**]()”里已经介绍了。接着往下说。
当PPP_GetInterface()被调用后,浏览器拿到了PPP_Instance接口,会调用PPP_Instance::DidCreate()来创建插件实例对象。一般我们在这个函数里会分配一些数据结构来保存新创建的实例。graphics_2d_example.c是这么实现DidCreate函数的:
~~~
PP_Bool Instance_DidCreate(PP_Instance instance,
uint32_t argc,
const char* argn[],
const char* argv[]) {
struct InstanceInfo* info =
(struct InstanceInfo*)malloc(sizeof(struct InstanceInfo));
info->pp_instance = instance;
info->last_size.width = 0;
info->last_size.height = 0;
info->graphics = 0;
/* Insert into linked list of live instances. */
info->next = all_instances;
all_instances = info;
return PP_TRUE;
}
~~~
上面的代码,仅仅是malloc了struct InstanceInfo,做了初始化,保存插件实例对象的句柄,将新实例加入一个全局的实例对象链表。
一般的插件实例对象会对应网页所在视图(view)上的一个区域,这个区域会有坐标、大小,插件绘制的内容就显示在这个区域内,用户也通过这个区域和插件交互(比如鼠标、按键、触摸等)。这个区域的大小,是在插件实例对象与view关联起来时确定的。
当插件实例对象与view关联时,PPP_Instance::DidChangeView方法会被调用。下面是graphics_2d_example中的实现:
~~~
void Instance_DidChangeView(PP_Instance pp_instance,
PP_Resource view) {
struct PP_Rect position;
struct InstanceInfo* info = FindInstance(pp_instance);
if (!info)
return;
if (g_view_interface->GetRect(view, &position) == PP_FALSE)
return;
if (info->last_size.width != position.size.width ||
info->last_size.height != position.size.height) {
/* Got a resize, repaint the plugin. */
Repaint(info, &position.size);
info->last_size.width = position.size.width;
info->last_size.height = position.size.height;
}
}
~~~
上面的代码,先根据插件实例对象的句柄从全局链表找到插件实例对象,然后通过之前在PPP_InitializeModule()方法中获取到的PPP_View接口(g_view_interface)来获取由view所代表的视图(区域)的rect,再接着重绘,保存大小;
再接下来,就进入到了交互和渲染循环,直到Web页面被关闭时,PPP_Instance::DidDestroy被调用,再后来,PPP_ShutdownModule被调用。
# 渲染(绘图)
这里只说明PPAPI插件这一侧的渲染逻辑。实际上插件是在一块代表图像的内存上渲染,渲染完毕后通知浏览器,浏览器在合适的时候更新。
前面我们展示了graphics_2d_example中的DidChangeView函数实现。它在view尺寸变化时会调用Repaint方法来更新自己。Repaint方法里包含了基本的绘图逻辑,我们来分析一下。代码如下:
~~~
void Repaint(struct InstanceInfo* instance, const struct PP_Size* size) {
PP_Resource image;
struct PP_ImageDataDesc image_desc;
uint32_t* image_data;
int num_words, i;
/* Ensure the graphics 2d is ready. */
if (!instance->graphics) {
instance->graphics = MakeAndBindGraphics2D(instance->pp_instance, size);
if (!instance->graphics)
return;
}
/* Create image data to paint into. */
image = g_image_data_interface->Create(
instance->pp_instance, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, PP_TRUE);
if (!image)
return;
g_image_data_interface->Describe(image, &image_desc);
/* Fill the image with blue. */
image_data = (uint32_t*)g_image_data_interface->Map(image);
if (!image_data) {
g_core_interface->ReleaseResource(image);
return;
}
num_words = image_desc.stride * size->height / 4;
for (i = 0; i < num_words; i++)
image_data[i] = 0xFF0000FF;
/* Paint image to graphics 2d. */
g_graphics_2d_interface->ReplaceContents(instance->graphics, image);
g_graphics_2d_interface->Flush(instance->graphics,
PP_MakeCompletionCallback(&FlushCompletionCallback, NULL));
g_core_interface->ReleaseResource(image);
}
~~~
首先它会确保当前插件实例对象绑定了2D图形上下文。这是在MakeAndBindGraphics2D()函数内完成,代码如下:
~~~
PP_Resource MakeAndBindGraphics2D(PP_Instance instance,
const struct PP_Size* size) {
PP_Resource graphics;
graphics = g_graphics_2d_interface->Create(instance, size, PP_FALSE);
if (!graphics)
return 0;
if (!g_instance_interface->BindGraphics(instance, graphics)) {
g_core_interface->ReleaseResource(graphics);
return 0;
}
return graphics;
}
~~~
可以看到它使用了g_graphics_2d_interface接口指针(类型是PPP_Graphics2D),这个接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_GRAPHICS_2D_INTERFACE。
PPP_Graphics2D操作图形上下文(2D Graphics Context)的接口,它的Create方法可以创建一个2D Graphics Context。2D图形上下文与插件实例对象绑定后,插件实例对象就可以操作图形上下文,进行渲染。
图形上下文与插件实例对象的绑定是通过PPP_Instance接口的BindGraphics方法完成的(参见ppb_instance.h)。PPP_Instance接口在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_INSTANCE_INTERFACE。
PPP_Graphics2D能操作2D图形上下文,通知浏览器更新。但是更新的图像数据,是放在别处的(一个由句柄标识的内存区域),不在PPP_Graphics2D内。保存在内存中的图形数据,可以通过PPP_ImageData接口来操作。这个接口,同样也是在PPP_InitializeModule()中使用get_browser_interface获取,名字是PPP_IMAGEDATA_INTERFACE。接口的具体定义在ppb_image_data.h文件内。
PPP_ImageData的Create方法可以根据指定格式和大小分配一块代表图形数据的内存资源,Map方法可以把图形数据资源映射到插件模块所在的进程内,返回内存起始地址,我们可以操作内存进行绘图操作。前面的Repaint方法就是这么干的。
Repaint方法绘图完成后,调用PPP_Graphics2D的ReplaceContents方法来更新上下文,调用Flush来刷新(不调用不生效哦)。
刷新是异步的,你可以指定一个回调接口(PP_CompletionCallback,见pp_completion_callback.h),当刷新完成后,PP_CompletionCallback的func成员会被调用。有一个方便的内联函数PP_MakeCompletionCallback可以帮你创建一个PP_CompletionCallback实例,它接受一个回调函数和一个void*作为user_data。
graphics_2d_example中的回调函数FlushCompletionCallback嘛事儿没干。我们在实际开发时,可以在回调完成后继续复用我们的分配的图形资源,这样可以提高效率。
好啦,这就是绘图的基本流程了。
# 处理输入事件
输入事件在ppb_input_event.h中定义。看文件名开头,就明白输入事件接口是由浏览器这端提供的。
输入事件的处理也分浏览器和插件两侧。
浏览器侧实现了PPP_InputEvent接口,定义接口名字的宏是PPP_INPUT_EVENT_INTERFACE。插件可以在PPP_InitializeModule函数中通过get_browser_interface函数指针按名字获取到。
PPP_InputEvent是入口,通过这个接口,插件实例对象可以通过RequestInputEvents和RequestFilteringInputEvents两个函数来声明自己感兴趣的输入事件(鼠标、键盘、触摸、滚轮、输入法等,见ppb_input_event.h)。
浏览器发现有插件实例对象关心某一类输入事件,就会根据这个插件模块导出的PPP_GetInterface函数来获取名为PPP_INPUT_EVENT_INTERFACE的接口。PPP_GetInterface应该返回PPP_InputEvent接口。
插件侧负责实现PPP_InputEvent接口(见ppp_input_event.h),这个接口(结构体)定义了一个HandleInputEvent方法。浏览器通过调用HandleInputEvent方法把事件传递给插件实例对象。
PPP_InputEvent的HandleInputEvent函数原型如下:
~~~
PP_Bool (*HandleInputEvent)(PP_Instance instance, PP_Resource input_event);
~~~
第一个参数是插件实例对象句柄,第二个参数是输入事件句柄(32位整数)。使用PPP_InputEvent接口和输入事件句柄,可以判断事件类型、获取事件的其它信息。
但是某一类别的事件,有时需要专门的接口来获取特定的信息,比如鼠标事件对应的浏览器侧接口是PPP_MouseInputEvent,可以以PPP_MOUSE_INPUT_EVENT_INTERFACE为参数调用get_browser_interface函数指针获取。
PPP_MouseInputEvent定义了一些方法,比如GetButton可以获取是左键还是右键点击,GetPosition可以获取鼠标点击的坐标,这些方法允许我们获取完整的事件信息,根据事件信息做逻辑处理。
好啦,处理输入事件的流程就这样子了。
这次到这里,下篇我们结合绘图和交互,来完成一个简单的示例。
相关文章参考:
- [**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)
- [**理解PPAPI的设计**](http://blog.csdn.net/foruok/article/details/50486788)