PPAPI 插件开发 - 插件编写
上一篇文章讲述了如何配置 PPAPI 插件开发环境(也就是配置 Google Native Client 环境),这篇文章将讲述如何编写一个简单的 PPAPI 插件并加载它,以及如何实现 PPAPI 插件和 Javascript 的交互。
在此之前,先简单介绍下 Native Client 的三种 embed 类型:
- “application/x-ppapi”:平台相关,唯一能直接使用 Win32 API 的 platfrom(有功能上的限制),文件后缀名为 dll, 不允许通过 Chrome Web Sore 发布。Chrome 版本的 Flash 插件就是这种格式,分为 32 位和 64 位版本。
- “application/x-nacl”:平台无关,cpu相关,文件后缀名为 nexe,可以不通过 Chrome Web Sore 发布。
- “application/x-pnacl”:平台无关,cpu无关,文件后缀名为 pexe,可以不通过 Chrome Web Sore 发布。
这里我们讲述的是 application/x-ppapi
类型的插件。
创建 C++ 项目
首先新建一个 Win32 项目,类型选则 DLL,这里我将项目命名为 “ppapi_test”。然后将预编译头文件stdafx.h
和 stdafx.cpp
删除,并在 “项目属性” –> “配置属性” –> “C/C++” –> “预编译头” 中,将预编译头选项的值设置为 “不使用预编译头”。
将 ppapitest.cpp
中的代码替换为下面的代码:
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @file hello_tutorial.cc
/// This example demonstrates loading, running and scripting a very simple NaCl
/// module. To load the NaCl module, the browser first looks for the
/// CreateModule() factory method (at the end of this file). It calls
/// CreateModule() once to load the module code. After the code is loaded,
/// CreateModule() is not called again.
///
/// Once the code is loaded, the browser calls the CreateInstance()
/// method on the object returned by CreateModule(). It calls CreateInstance()
/// each time it encounters an <embed> tag that references your NaCl module.
///
/// The browser can talk to your NaCl module via the postMessage() Javascript
/// function. When you call postMessage() on your NaCl module from the browser,
/// this becomes a call to the HandleMessage() method of your pp::Instance
/// subclass. You can send messages back to the browser by calling the
/// PostMessage() method on your pp::Instance. Note that these two methods
/// (postMessage() in Javascript and PostMessage() in C++) are asynchronous.
/// This means they return immediately - there is no waiting for the message
/// to be handled. This has implications in your program design, particularly
/// when mutating property values that are exposed to both the browser and the
/// NaCl module.
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"
/// The Instance class. One of these exists for each instance of your NaCl
/// module on the web page. The browser will ask the Module object to create
/// a new Instance for each occurrence of the <embed> tag that has these
/// attributes:
/// src="hello_tutorial.nmf"
/// type="application/x-pnacl"
/// To communicate with the browser, you must override HandleMessage() to
/// receive messages from the browser, and use PostMessage() to send messages
/// back to the browser. Note that this interface is asynchronous.
class HelloTutorialInstance : public pp::Instance {
public:
/// The constructor creates the plugin-side instance.
/// @param[in] instance the handle to the browser-side plugin instance.
explicit HelloTutorialInstance(PP_Instance instance) : pp::Instance(instance)
{
// 在插件加载/创建后向 JavaScript 发送一条消息
pp::Var message("this is a meessage from Pepper Plugin");
PostMessage(message);
}
virtual ~HelloTutorialInstance() {}
/// Handler for messages coming in from the browser via postMessage(). The
/// @a var_message can contain be any pp:Var type; for example int, string
/// Array or Dictinary. Please see the pp:Var documentation for more details.
/// @param[in] var_message The message posted by the browser.
virtual void HandleMessage(const pp::Var& var_message) {
// TODO(sdk_user): 1. Make this function handle the incoming message.
// 接收到来自 JavaScript 的消息
if (!var_message.is_string())
return;
std::string message = var_message.AsString();
}
};
/// The Module class. The browser calls the CreateInstance() method to create
/// an instance of your NaCl module on the web page. The browser creates a new
/// instance for each <embed> tag with type="application/x-pnacl".
class HelloTutorialModule : public pp::Module {
public:
HelloTutorialModule() : pp::Module() {}
virtual ~HelloTutorialModule() {}
/// Create and return a HelloTutorialInstance object.
/// @param[in] instance The browser-side instance.
/// @return the plugin-side instance.
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new HelloTutorialInstance(instance);
}
};
namespace pp {
/// Factory function called by the browser when the module is first loaded.
/// The browser keeps a singleton of this module. It calls the
/// CreateInstance() method on the object you return to make instances. There
/// is one instance per <embed> tag on the page. This is the main binding
/// point for your NaCl module with the browser.
Module* CreateModule() {
return new HelloTutorialModule();
}
} // namespace pp
上面的代码来自官方教程 C++ Tutorial: Getting Started ,我做了部分修改,其中 PostMessage(message)
用于向 JavaScript 端发送消息,HandleMessage
用于接收来自 JavaScript 的消息。
在 “项目属性” –> “配置属性” –> “C/C++” –> “常规” -> “附加包含目录” 中插入所需头文件路径,我这里用的是 D:\Thirdparty\nacl_sdk\pepper_49\include
;在 “项目属性” –> “配置属性” –> “链接器” –> “常规” -> “附加库目录” 中插入库文件路径,我这里用的是 D:\Thirdparty\nacl_sdk\pepper_49\lib\win_x86_32_host\Release
;
由于代码中使用的是 C++ PPAPI,所以需要引用 ppapi_cpp.lib
。
最后执行项目编译,生成 ppapitest.dll
文件。
由于我使用的是 Visual Studio 2017,但是官方提供的 ppapi_cpp.lib
是用 Visual Studio 2013 编译的,所以在编译时遇到了 error LNK2038: mismatch detected for '_MSC_VER': value '1800' doesn't match value '1900' in
错误,只需要打开 Native Tools Command Prompt for VS 2017
重新编译 ppapi_cpp.lib
即可解决这个问题。编译命令如下:
$ cd $NACL_SDK_ROOT\src\ppapi_cpp
$ make TOOLCHAIN=win CONFIG=Debug
$ make TOOLCHAIN=win CONFIG=Release
如果想要编译 64 位的 ppapi_cpp.lib
,有两种方法。
第一种方法是:修改 $NACL_SDK_ROOT\src\ppapi_cpp\Makefile
文件,在相应的位置添加下面的代码,再执行编译即可。
CFLAGS += -m64
LFLAGS += -m64
第二种方法是:
- 打开
$NACL_SDK_ROOT\tools\host_vc.mk
,将文件中所有的x86_32
都替换为x86_64
Native Tools Command Prompt for VS 2017
,进入$NACL_SDK_ROOT\src\ppapi_cpp
目录- 运行
make TOOLCHAIN=win
命令
生成的 ppapi_cpp.lib
文件会被创建到 $NACL_SDK_ROOT\lib\win_x86_64_host\Release
目录下。
编写 HTML 代码
新建一个 HTML 文件,这里我们命名为 index.html
,文件内容为:
<!DOCTYPE html>
<html>
<!--
Copyright (c) 2013 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<head>
<title>hello_tutorial</title>
<script type="text/javascript">
HelloTutorialModule = null; // Global application object.
statusText = 'NO-STATUS';
// Indicate load success.
function moduleDidLoad() {
console.log("moduleDidLoad")
HelloTutorialModule = document.getElementById('hello_tutorial');
updateStatus('SUCCESS');
// Send a message to the Native Client module
HelloTutorialModule.postMessage('hello');
}
// The 'message' event handler. This handler is fired when the NaCl module
// posts a message to the browser by calling PPB_Messaging.PostMessage()
// (in C) or pp::Instance.PostMessage() (in C++). This implementation
// simply displays the content of the message in an alert panel.
function handleMessage(message_event) {
alert(message_event.data);
}
// If the page loads before the Native Client module loads, then set the
// status message indicating that the module is still loading. Otherwise,
// do not change the status message.
function pageDidLoad() {
console.log("pageDidLoad")
if (HelloTutorialModule == null) {
updateStatus('LOADING...');
} else {
// It's possible that the Native Client module onload event fired
// before the page's onload event. In this case, the status message
// will reflect 'SUCCESS', but won't be displayed. This call will
// display the current message.
updateStatus();
}
}
// Set the global status message. If the element with id 'statusField'
// exists, then set its HTML to the status message as well.
// opt_message The message test. If this is null or undefined, then
// attempt to set the element with id 'statusField' to the value of
// |statusText|.
function updateStatus(opt_message) {
if (opt_message)
statusText = opt_message;
var statusField = document.getElementById('statusField');
if (statusField) {
statusField.innerHTML = statusText;
}
}
</script>
</head>
<body onload="pageDidLoad()">
<h1>NaCl C++ Tutorial: Getting Started</h1>
<p>
<!--
Load the published pexe.
Note: Since this module does not use any real-estate in the browser, its
width and height are set to 0.
Note: The <embed> element is wrapped inside a <div>, which has both a 'load'
and a 'message' event listener attached. This wrapping method is used
instead of attaching the event listeners directly to the <embed> element to
ensure that the listeners are active before the NaCl module 'load' event
fires. This also allows you to use PPB_Messaging.PostMessage() (in C) or
pp::Instance.PostMessage() (in C++) from within the initialization code in
your module.
-->
<Element id="listener">
<script type="text/javascript">
var listener = document.getElementById('listener');
listener.addEventListener('load', moduleDidLoad, true);
listener.addEventListener('message', handleMessage, true);
</script>
<embed id="hello_tutorial" width=100 height=100 type="application/x-ppapi-hello" />
</div>
</p>
<h2>Status
<code id="statusField">NO-STATUS</code>
</h2>
</body>
</html>
其中 PostMessage(message)
用于向 C++ 端发送消息,HandleMessage
用于接收来自 C++ 端的消息。
加载插件
想要加载 PPAPI 插件,需要使用 Chrome 内核的浏览器,360 极速浏览器和 Chrome 都可以。
在命令行提示符中执行下列命令,用以打开 Chrome 浏览器并加载 PPAPI 插件:
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --register-pepper-plugins="D:/Workspace/ppapitest/Release/ppapitest.dll;application/x-ppapi-hello" "D:/Workspace/ppapitest/index.html"
--register-pepper-plugins
参数表示注册 PPAPI 插件,其中 application/x-ppapi-hello
为插件类型,注意这个类型名要和 HTML 中指定的 embed
元素的类型名要完全匹配。
页面启动后弹出就会看到 PPAPI 插件的效果,根据上面的代码,插件加载成功会弹出了一个提示框。
在使用浏览器加载插件过程中,按照上面步骤及代码,总数出现 插件未加载
或 插件不支持
错误,最后发现是 64位浏览器不匹配 32 位 PPAPI 插件的问题,换成对应的浏览器成功解决。
Qt WebEngine 加载 Pepper Plugin
Qt WebEngine 如果设置了 WebEngineSettings::pluginsEnabled
或 QWebEngineSettings::PluginsEnabled
参数,那么将支持加载 Pepper Plugin API (PPAPI) 插件。
除了 Adobe Flash Player 插件外,其他插件必须使用 Chromium 命令行 --register-pepper-plugins
参数手动加载。参数值是由逗号分隔的条目列表,其中包含文件路径和一个或多个 MIME 类型名,由分号分隔:
<file-path-plugin1>;<mime-type-plugin1>,<file-path-plugin2>;<mime-type1-plugin2>;<mime-type2-plugin2>
例如:
--register-pepper-plugins="libppapi_example.so;application/x-ppapi-example"
MIME 类型很重要,因为它决定了嵌入插件所使用的功能。
在 Qt/C++ 中实际使用的代码为:
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--args --explicitly-allowed-ports=6666 --ignore-certificate-errors --disable-pinch --disable-embedded-switches --register-pepper-plugins=D:/Workspace/ppapitest/x64/Release/ppapitest.dll;application/x-ppapi-hello");
值得说明的是,指定的插件路径中不能保护空格,如果有空格也不能使用引号分隔(试过加引号等方式没有成功)。
参考文章:NPAPI和PPAPI开发
极好。
按照你的方法制作了dll,但注册时应该是不成功。显示不支持插件,不知道要怎么解决。
在使用浏览器加载插件过程中,按照上面步骤及代码,总数出现 插件未加载 或 插件不支持 错误,最后发现是 64位浏览器不匹配 32 位 PPAPI 插件的问题,换成对应的浏览器成功解决。
按照你的方法,编译Debug版本是可行的,可是浏览器无法加载插件。。
Release是直接编译都通不过,ppapi_cpp.lib一堆的无法解析的外部符号,我把所有的库都重新编译一遍也没用。。
是为什么?
您好,按照您的方法,32位编译成功了,能正常使用,但是64位编译成功,却无法使用,能将64位的编译过程详细说下吗?
问题已经解决,您给的方法很正确!
我修改完编译出来还是32位, 请问是怎么解决的
已经解决,但是怎么指定运行时的mt和md
修改文件$NACL_SDK_ROOTtoolshost_vc.mk, 将WIN_OPT_FLAGS中/MT,/MTd分别改为/MD,/MDd
怎么编译64位的lib的
大家好,我按照上面的方法成功编译了32位的ppapi插件并能在32位谷歌浏览器中运行。但是在编译64位ppapi_cpp.lib的时候报错了,这要怎么解决呢?错误如下:
CXX win/Release/array_output.o
F:vstestnacl_sdkpepper_49includeppapi/cpp/array_output.h(8) : fatal error C1083: 无法打开包括文件: “stddef.h”: No such file or directory
Makefile:86: recipe for target 'win/Release/array_output.o' failed
make: * [win/Release/array_output.o] Error 2
没有找到stddef.h文件,可能是你的项目配置有问题,被改坏了吧?
$(VCInstallDir)PlatformSDK\include $(VC_IncludePath) $(WindowsSDK_IncludePath)看下,项目,属性,配置属性,VC++目录,包含目录中是否包含下面三项:
注意:答案仅供参考