PPAPI插件开发指南-程序员宅基地

技术标签: ViewUI  ui  前端  c/c++  

转载请注明出处:http://www.cnblogs.com/fangkm/p/4401075.html

前言

插件一直是浏览器的重要组成部分,丰富浏览器的运行能力,实现一些HTML+JS实现不了本地应用(比如音视频、文件操作等)。早期广为熟知的是IE下的插件ActiveX,这是一项熟悉可能暴露年龄的技术,它基于COM规范,在IE占浏览器市场主流份额的时代,ActiveX可谓出尽了风头,但它并不是浏览器业内的插件标准。由网景发布的NPAPI长久以来是除了IE之外的其他浏览器共同支持的业内标准。ActiveX和NPAPI长久共存,如果某项业务要发布浏览器插件,一般都需要实现这两种插件规范。有个开源的插件开发框架FireBreath 抽象了这两种插件的接口细节,让开发者专注于业务逻辑(个人比较嫌弃FireBreath 开发出来的插件体积太大)。早期的银行开发网页安全控件都是使用ActiveX开发,所以他们都要求在IE浏览器或者其他双核浏览器的兼容模式下登录。现在很多银行已经支持NPAPI版本的安全控件,但NPAPI却做为不安全因素被主流浏览器(Chrome、FireFox)抛弃,好在国内的双核浏览器都保留有NPAPI功能的移植,让市面上大量的NPAPI插件得以正常运行。现在,随着IE逐渐退出历史舞台,ActiveX插件的运行范围越来越窄,NPAPI也成为过时的插件标准,虽然HTML5标准也在逐渐完善,很多之前只靠Web技术做不到的功能都能得到很好的支持,但是并不是所有的功能都能通过HTML5实现,本地插件依然是我们这个时代绕不开的功能应用,最典型的比如Flash。于是Chrome提出了名叫PPAPI的全新插件机制,运行在Chrome浏览器的沙箱环境。

插件类型

Chrome

基于Chromium核的浏览器

IE

ActiveX

No

No

Yes

NPAPI

No

Yes(暂时兼容)

No

PPAPI

Yes

Yes

No

从对比可以看出,PPAPI的运行场景完全覆盖NPAPI,以后开发插件完全可以只开发PPAPI+ActiveX两个版本。但是目前市面上,除了内置的PPAPI版本的Flash组件以及Pdf组件,没遇到其他的PPAPI版本的商业组件,可见再造一个平台是多么地不容易。

PPAPI组件介绍

在Chrome中,PPAPI组件分为两种存在形式:

可信组件:可信的PPAPI组件可以通过平台动态库的形式(Windows下为dll文件,Linux下是so文件)由浏览器直接加载,比如内置的Flash组件、Pdf组件,或者通过指定命令行参数--register-pepper-plugins来加载,比如:chrome --register-pepper-

plugins="D:\\ppapi\\ppapi_example_gles2.dll;application/x-ppapi-example-gles2" D:\\ppapi\\gles2.html,这种方案常用于调试。可信的PPAPI组件以平台动态库的形式存在,所以一般Chrome沙箱内允许的API(比如CreateThread)都可以调用。

不可信组件:不可信的PPAPI组件需要使用Native Client机制发布(以Native Client的形式发布到Chrome Web Store或者以Portable Native Client的形式发布了网址的Web服务器上),而Native Client需要维系跨平台可移植性,完全与系统API告别了,只能使用PPAPI提供的开发库和C/C++运行库,不过目前来看,基本的需求也是可以满足的。

下面贴一段PPAPI与JS交互的典型场景:

<object id="plugin" type="application/x-ppapi-post-message-example"  width="1" height="1"/>
  function HandleMessage(message_event) {
    if (message_event.data) {
      alert("The string was a palindrome.");
    } else {
      alert("The string was not a palindrome.");
    }
  }

  function AddListener() {
    var plugin = document.getElementById("plugin");
    plugin.addEventListener("message", HandleMessage, false);
  }
  document.addEventListener("DOMContentLoaded", AddListener, false);

  function SendString() {
    var plugin = document.getElementById("plugin");
    var inputBox = document.getElementById("inputBox");

    // Send the string to the plugin using postMessage.  This results in a call
    // to Instance::HandleMessage in C++ (or PPP_Messaging::HandleMessage in C).
    plugin.postMessage(inputBox.value);
  }

JS通过挂接插件的message事件来接收C/C++发送过来的通知,同时可以通过调用插件的postMessage方法给C/C++发送消息。

PPAPI的开发接口

无论是可信的还是不可信的PPAPI组件,一般都会提供三个导出函数:

int32_t PPP_InitializeModule(PP_Module module, PPB_GetInterface get_browser_interface);

初始化PPAPI模块,传入浏览器生成的模块句柄PP_Module,同时通过get_browser_interface参数给插件模块提供浏览器宿主常见的功能接口,比如PPB_Core、PPB_Messaging 、PPB_URLLoader、PPB_FileIO等接口。

void PPP_ShutdownModule(void);

关闭模块

const void* PPP_GetInterface(const char* interface_name);

提供给浏览器宿主的功能接口,比如PPP_Instance、PPP_InputEvent、PPP_Messaging等接口。

除了PPP_ShutdownModule是可选的,其他两个导出函数必须实现。PPB_GetInterface和PPP_GetInterface是插件(PPAPI)和宿主(浏览器的Plugin进程)功能交互的唯一切入口。

命名规范上,宿主Browser提供给插件(PPAPI)的接口以PPB命名开头,可以从导出函数PPP_InitializeModule 传入的PPB_GetInterface参数中根据接口ID查询对应的功能接口,下面列举一些常用的功能接口:

功能说明

接口ID

接口定义

重要成员方法说明

核心基础接口

PPB_CORE_INTERFACE

PPB_Core

AddRefResource、ReleaseResource:增加/减少资源的引用计数GetTime、GetTimeTicks:获取当前的时间CallOnMainThread、IsMainThread:主线程相关的处理

消息接口

PPB_MESSAGING_INTERFACE

PPB_Messaging

PostMessage:抛送消息(Plugin->JS,JS通过addEventListener挂接message事件)

RegisterMessageHandler、UnregisterMessageHandler:自定义消息处理对象,用于拦截PPP_Messaging接口的HandleMessage处理(应用场景未明)

HTTP请求接口

PPB_URLLOADER_INTERFACE

PPB_URLLoader

常用的HTTP请求操作:Open、GetResponseInfo、ReadResponseBody、Close等等

文件IO操作

PPB_FILEIO_INTERFACE

PPB_FileIO

File的基本操作:Create、Open、Read、Write、Close等

网络操作

PPB_UDPSOCKET_INTERFACE、

PPB_TCPSOCKET_INTERFACE、

PPB_HOSTRESOLVER_INTERFACE等

PPB_UDPSocket、

PPB_TCPSocket、

PPB_HostResolver等

UDP套接字、TCP套接字、域名解析服务等常用的操作接口。

音视频操作

PPB_AUDIO_INTERFACE、

PPB_VIDEODECODER_INTERFACE、

PPB_VIDEOENCODER_INTERFACE等

PPB_Audio、

PPB_VideoDecoder、

PPB_VideoEncoder等

封装音视频的编解码等操作。

 

此外还有Image、2D、3D等接口,基本上满足开发本地应用的所有功能。

插件(PPAPI)提供给宿主Browser的接口以PPP命名开头

功能说明

接口ID

接口定义

重要成员方法说明

插件实例对象

PPP_INSTANCE_INTERFACE

PPP_Instance

DidCreate、DidDestroy:插件创建销毁通知

DidChangeView、DidChangeFocus:插件尺寸、焦点改变通知

HandleDocumentLoad:Document加载完成通知

这些调用最终都映射到Instance实例的相关成员方法上。

输入接口

PPP_INPUT_EVENT_INTERFACE

PPP_InputEvent

HandleInputEvent:处理输入消息,最终转接到Instance的HandleInputEvent接口

消息接口

PPP_MESSAGING_INTERFACE

PPP_Messaging

HandleMessage:处理消息(JS->Plugin, JS调用插件的postMessage方法),最终映射到Instance实例的HandleMessage方法。

除了这三个基础的接口,Module对象(后面再细讲)的AddPluginInterface提供了扩展插件接口的功能,比如:

封装PDF插件功能的PPP_Pdf接口;

封装视频采集功能的PPP_VideoCapture_Dev接口;

封装视频硬件解码的PPP_VideoDecoder_Dev接口;

封装3D图形相关的通知事件的PPP_Graphics3D接口;

此外还有PPP_MouseLock、PPP_Find_Private、PPP_Instance_Private等私有接口。

 

插件实例: 每类插件都支持创建多个插件实例(比如浏览器打开多个带有Flash元素的页面),每个插件实例都对应一个PP_Instance句柄。

资源:由PP_Resource表示,这里的资源只是一种概念,因为前面提到的PPB、PPP接口都是模块级别,调用的时候需要传入PP_Instance句柄表示这次调用派往哪个插件实例,同时,某个功能的调用需要记录调用上下文,比如PPB_URLLoader,开启某个URL请求,读取某个已打开的URL请求多少字节、关闭某个已打开的URL请求,这个过程中“某个”就是用PP_Resource来表示,它是用来衔接插件端和宿主容器功能调用上下文的句柄。每个PP_Resource都隶属于一个PP_Instance。PP_Resource资源对象采用引用计数的方式维系生命周期,开发者可以使用PPB_Core接口的AddRefResource和ReleaseResource来对PP_Resource的引用计数进行增减。

PP_Instance、PP_Resource不仅是PPAPI插件与PPAPI Plugin宿主之间的交互桥梁,同时也是PPAPI Plugin宿主进程与浏览器Browser进程和Render进程之间的衔接,对于某个功能Resource,比如URLLoader,从插件模块的角度来看,插件模块封装接口调用的Proxy端,Plugin宿主进程是真正实现功能的Stub端,但Plugin进程也不是真正执行请求的地方,因为Plugin进程运行在沙箱里面,权限受限,文件操作、硬件访问等之类的功能都做不到。从Plugin进程的角度看,Plugin进程也只是个Proxy,Stub是Browser进程(有些功能Resource,Stub是Render进程,比如音视频编解码等)。他们之前都是通过PP_Instance和PP_Resource来做衔接标识。

介绍完基础概念,下面介绍开发框架。原生的开发接口都是C形式的,可以使用这些C接口直接开发你的应用:实现三个前面提到的导出函数,尤其是必须实现PPP_InitializeModule和PPP_GetInterface,保存宿主传过来的PPB_GetInterface功能接口,提供给宿主自己的PPP_GetInterface调用,代码示例如下:

PP_Module g_module_id;
PPB_GetInterface g_get_browser_interface = NULL;
PP_EXPORT int32_t PPP_InitializeModule(PP_Module module_id, PPB_GetInterface get_browser_interface) {
// Save the global module information for later. g_module_id = module_id; g_get_browser_interface = get_browser_interface; return PP_OK; }
PP_EXPORT
void PPP_ShutdownModule() { } PP_EXPORT const void* PPP_GetInterface(const char* interface_name) { // You will normally implement a getter for at least PPP_INSTANCE_INTERFACE // here. return NULL; }

C语言的相关接口请参见前面的章节。

除了C接口之外,PPAPI框架还提供了这些C接口的C++封装版本,使用C++版本可以忽略一些接口细节,比如PP_Resource。下面详细说下PPAPI的C++开发库。

首先,使用C++版本,开发者就无需关心三个导出函数的具体实现,框架在ppp_entrypoints.cc文件里对三个导出函数做了实现,创建Module对象标识整个插件模块:

 

开发者只需要提供自己的Module实现—MyModule,定制自己的插件实例MyInstance即可,示例代码:

class MyInstance : public pp::Instance {
 public:
  explicit MyInstance(PP_Instance instance) : pp::Instance(instance) {}
  virtual ~MyInstance() {}

  virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
    return true;
  }
};

// This object is the global object representing this plugin library as long
// as it is loaded.
class MyModule : public pp::Module {
 public:
  MyModule() : pp::Module() {}
  virtual ~MyModule() {}
// Override CreateInstance to create your customized Instance object. virtual pp::Instance* CreateInstance(PP_Instance instance) { return new MyInstance(instance); } }; namespace pp { // Factory function for your specialization of the Module object. Module* CreateModule() { return new MyModule(); } } // namespace pp

在Module的实现中,除了保存PPP_InitializeModule导出函数传送过来的PPB_GetInterface接口并通过GetBrowserInterface接口暴露出去,同时也对PPP_GetInterface导出函数做了实现,对PPP_INPUT_EVENT_INTERFACE、PPP_INSTANCE_INTERFACE、PPP_MESSAGING_INTERFACE三个PPP功能接口做了封装,把Plugin宿主的调用全部转接到Instance实现的对应成员方法上。除了支持这三个核心的PPP接口,Module还提供了AddPluginInterface方法让其它的功能组件定制其它的PPP接口实现,比如之前接口说明中提到的PPP_VideoCapture_Dev、PPP_Graphics3D等。

Instance主要成员方法说明

成员方法

功能说明

Init

初始化,在PPP_Instance接口的DidCreate创建Instance实例后调用

DidChangeView

同样来自PPP_Instance接口的通知,在插件显示区域创建或改变时触发,通知参数被封装成View类,View类通过PPB_View接口可以获取插件的区域、缩放比例等信息

HandleInputEvent

来自PPP_InputEvent接口的通知,处理用户输入事件(鼠标、键盘等),具体请查看InputEvent类的封装。

HandleMessage

来自PPP_Messaging接口,接收前端JS发送过来的消息。

PostMessage

调用PPB_Messaging接口的PostMessage接口,功能与HandleMessage相对,给前端JS发送通知。

BindGraphics

调用PPB_Instance的BindGraphics接口,指定渲染接口,有Graphics2D、Graphics3D、Compositor三种渲染方式。

 

前面说过,每个功能都对应一个PP_Resource句柄,PPAPI的C++库使用Resource类封装了PP_Resource句柄(主要封装PPB_Core接口对引用计数的调用),具体的功能类都派生自Resource。

 

上图列举了一些具有典型意义的功能类封装:

View:获取插件的界面显示区域信息,封装PPB_View接口。类似的UI封装还有Fullscreen

FileIO:文件IO操作(文件读取/写入),封装PPB_FileIO接口调用。FileIO类并不是我们平时理解的直接操作磁盘上某个目录读写操作,它使用HTML5 的本地文件系统FileSystem API,读写的文件路径都是在这个系统下的相对目录,File System API可以理解为Chromium沙箱内的文件系统,页面之间是隔离的。类似的还有DirectoryEntry(目录操作)。

VideoEncoder:视频编码操作,封装PPB_VideoEncoder接口调用,类似的还有VideoDecoder(视频解码)、VideoFrame(视频帧数据封装)、AudioEncoder(音频编码)、Audio(音频播放控制)、AudioBuffer(音频帧数据封装)、AudioInput_Dev(音频采集)、VideoCapture_Dev(视频采集)等等,PPAPI的音视频接口较多,有效地弥补了HTML5这方面的不足。从Chromium源码端分析,目前音频编码只支持OPUS,视频编解码支持H264、VP8/VP9。

编码方面:H264只支持硬件编码,VP8/VP9同时支持硬件和软件编码

解码方面:如果指定使用硬件加速,H264和VP8/VP9优先使用硬件解码(GPU),失败后再尝试软件解码(使用libvpx和FFmpeg)。

TCPSocket、URLLoader:网络相关的操作,同样的封装还有NetAddress、UDPSocket、HostResolver等。

Graphics3D:3D渲染逻辑,封装PPB_Graphics3D接口,类似的还有Graphics2D、Compositor方式。

 

PP_CompletionCallback概念:封装了编程中的任务操作,PPAPI中耗时的操作都采用异步的编程方式,比如音视频编码、文件读取、URL请求等操作,这些操作不会立马返回,需要传入一个回调接口来接受完成通知。这个回调接口的C语言定义格式如下:

struct PP_CompletionCallback {
  /**
   * This value is a callback function that will be called, or NULL if this is
   * a blocking completion callback.
   */
  PP_CompletionCallback_Func func;

  /**
   * This value is a pointer to user data passed to a callback function.
   */
  void* user_data;

  /**
   * Flags used to control how non-NULL callbacks are scheduled by
   * asynchronous methods.
   */
  int32_t flags;
};

PP_CompletionCallback_Func的原型:

typedef void (*PP_CompletionCallback_Func)(void* user_data, int32_t result); 

比如PPB_VideoEncoder接口的Encode方法,其原型如下

  int32_t (*Encode)(PP_Resource video_encoder,
                    PP_Resource video_frame,
                    PP_Bool force_keyframe,
                    struct PP_CompletionCallback callback);

当这一帧编码完成时,会回调传入的callback接口,user_data参数原封不动传回去,result指定编码的结果,成功值是PP_OK。user_data参数原封不动地一来一回,一个很重要的考虑就是兼容C++的成员方法做为回调函数的处理逻辑。关于如何将C++的成员函数转成PP_CompletionCallback_Func风格的C函数,请参考CompletionCallback和CompletionCallbackFactory的代码逻辑,CompletionCallbackFactory还实现了一套简陋的参数Bind机制,此外还有一个CompletionCallbackWithOutput模板类,用于支持带输出参数的CompletionCallback 版本,原理上也仅仅是在CompletionCallbackWithOutput内部创建一个成员变量作为PPB接口的参数。从个人角度看,这些看似复杂的玩意玩不转就不玩,不影响使用,再说CompletionCallbackWithOutput实现的也不够通用,只支持一个输出参数。 

PPB_MessageLoop:对应的C++封装类是MessageLoop,是线程环境执行的重要辅助类。将MessageLoop对象附加到当前线程,线程间投递任务非常方便。

此外还有一套模拟脚本变量声明的组件:Var、VarArray、VarDictionary等,主要是为了方便postMessage、HandleMessage与JS交互的接口数据,抽象具体的类型信息,避免过载,还有Point、Rect、Size等常用的UI基础封装,这些都没有细讲的必要。

转载于:https://www.cnblogs.com/fangkm/p/6628425.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_30741653/article/details/96826964

智能推荐

椭圆曲线密码学(ECC)简介_ecc椭圆曲线 原理 科普 csdn-程序员宅基地

文章浏览阅读4.6k次,点赞3次,收藏15次。这一节要聊的是 ECC ( Elliptic Curve Cryptography ),也就是椭圆曲线密码学。跟 RSA 一样,ECC 也属于公开密钥加密,ECC 算法也是用来生成公钥的私钥的。本节我们不会深入椭圆曲线算法本身,只是对 ECC 做一下简介。一. ECC 的作用之所以要解释这么一个生僻的名词,还是因为它很有用。ECC 跟 RSA 算法一样,都可以生成公钥和私钥,然后进行加密通信..._ecc椭圆曲线 原理 科普 csdn

ubuntu快速部署ftp服务器_ubuntu 快速 ftp-程序员宅基地

文章浏览阅读1.3k次。以ubuntu 14为例快速部署ftp服务器:①sudo apt-get install vsftpd②创建ftp文件存储目录: mkdir /home/ftp③添加ftp用户: sudo useradd -d /home/ftp -s /bin/bash ftpuser④设置ftpuser密码:passwd ftpuser 输入两次密码(回车确认)⑤配置vsftpd.c_ubuntu 快速 ftp

git代码冲突解决办法_git代码冲突怎么解决-程序员宅基地

文章浏览阅读5.9k次。git reset --hard origin/master 本地代码同步线上最新版本(会覆盖本地所有与远程仓库上同名的文件);git pull 再更新一次(其实也可以不用,第二步命令做过了其实)git fetch 拉取所有更新,不同步;_git代码冲突怎么解决

Python内置函数slice()的使用举例_python slice函数例子-程序员宅基地

文章浏览阅读146次。class slice(start, stop[, step])代码实验展示:Python 3.7.4 (tags/v3.7.4:e09359112e, Jul 8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32Type "help", "copyright", "credits" or "license()" for more information.>>> index = slice(4)>>>._python slice函数例子

【C语言程序内存泄露】_c语言检查内存泄漏代码-程序员宅基地

文章浏览阅读342次,点赞8次,收藏10次。这只是一个简单的示例程序,实际上可以使用更复杂的工具和技术来检测和调试内存泄露问题,比如使用动态内存分析工具(如Valgrind、AddressSanitizer等)或静态代码分析工具(如Coverity、Cppcheck等)。编写一个C语言程序来检测内存泄露,可以使用一些工具和技术来帮助实现这个目标。函数在堆上分配了一个整数的内存,并且没有释放它。的内存泄露报告,它告诉我们有4个字节的内存在程序结束时没有被释放。命令可以检测并报告内存泄露。在这个输出中,我们可以看到。来检测这个内存泄露。_c语言检查内存泄漏代码

计算机网络复习总结5_主动队列管理计算机网络-程序员宅基地

文章浏览阅读163次。运输层概述、用户数据报协议 UDP、传输控制协议 TCP、可靠传输、停止等待协议、连续 ARQ 协议、流量控制、拥塞控制、主动队列管理、TCP 的运输连接管理_主动队列管理计算机网络

随便推点

python录音pyaudio_『开发技巧』Python音频操作工具PyAudio上手教程-程序员宅基地

文章浏览阅读240次。『开发技巧』Python音频操作工具PyAudio上手教程​0.引子当需要使用Python处理音频数据时,使用python读取与播放声音必不可少,下面介绍一个好用的处理音频PyAudio工具包。PyAudio是Python开源工具包,由名思义,是提供对语音操作的工具包。提供录音播放处理等功能,可以视作语音领域的OpenCv。1.简介PyAudio为跨平台音频I / O库PortAudio提供Pyt..._pyaudio.pyaudio().open().write()

2023年全国职业院校技能大赛大数据应用开发赛题第02套_单信息对应表结构order_info,订单详细信息对应表结构order_detail-程序员宅基地

文章浏览阅读720次,点赞18次,收藏19次。根据ods_ds_hudi.user_info表中operate_time或create_time作为增量字段(即MySQL中每条数据取这两个时间中较大的那个时间作为增量字段去和ods里的这两个字段中较大的时间进行比较),只将新增的数据抽入,字段名称、类型不变,同时添加分区,若operate_time为空,则用create_time填充,分区字段为etl_date,类型为String,且值为当前比赛日的前一天日期(分区字段格式为yyyyMMdd)。大数据时代背景下,电商经营模式发生很大改变。_单信息对应表结构order_info,订单详细信息对应表结构order_detail

web期末作业设计网页:动漫网站设计——大鱼海棠(12页) HTML+CSS+JavaScript 学生DW网页设计作业成品 动漫网页设计作业 web网页设计与开发 html实训大作业_动态网页设计作业成品-程序员宅基地

文章浏览阅读7k次,点赞71次,收藏120次。网站布局方面:计划采用目前主流的、能兼容各大主流浏览器、显示效果稳定的浮动网页布局结构。网站程序方面:计划采用最新的网页编程语言HTML5+CSS3+JS程序语言完成网站的功能设计。并确保网站代码兼容目前市面上所有的主流浏览器,已达到打开后就能即时看到网站的效果。网站素材方面:计划收集各大平台好看的图片素材,并精挑细选适合网页风格的图片,然后使用PS做出适合网页尺寸的图片。_动态网页设计作业成品

论文简述 | EAO-SLAM:基于集成数据关联的单目半稠密物体级SLAM-程序员宅基地

文章浏览阅读475次。点击上方“3D视觉工坊”,选择“星标”干货第一时间送达1摘要对象级数据关联和姿态估计在语义SLAM中起着重要作用,但由于缺乏鲁棒和精确的算法,这一问题一直没有得到解决.在这项工作中,我们..._半稠密地图

7-zip图标_如何用外观更好的图标替换7-Zip的丑图标-程序员宅基地

文章浏览阅读7.6k次。7-zip图标7-Zip is a fantastic Windows program for advanced file zipping, whether you’re password-protecting your archives or just trying to compress them down even smaller. There’s just one problem: its..._7z 图标

编程游戏:划拳机器人比赛-{ 咱们园子是否应该设立一个算法挑战/编程竞赛的平台? }...-程序员宅基地

文章浏览阅读115次。网友王集鹄组织了一个编程比赛:划拳机器人比赛。类似“Robocode” 的游戏。早先Microsoft就推出过一款名为Terrarium的code game,其实这种游戏已经有相当久的历史了,很多人都将其看成是那些hacker们的游戏。-------比赛相关帖子如下: 【编程游戏】编写一个会划拳的机器人参加擂台赛,规则内详。 http://topic.csdn.net/u/20..._猜拳机器人博弈算法