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)
';