上一篇文章讲述了如何配置 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.hstdafx.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

第二种方法是:

  1. 打开 $NACL_SDK_ROOT\tools\host_vc.mk ,将文件中所有的 x86_32 都替换为 x86_64
  2. Native Tools Command Prompt for VS 2017,进入 $NACL_SDK_ROOT\src\ppapi_cpp 目录
  3. 运行 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 插件的效果,根据上面的代码,插件加载成功会弹出了一个提示框。

ppapi_html

在使用浏览器加载插件过程中,按照上面步骤及代码,总数出现 插件未加载插件不支持 错误,最后发现是 64位浏览器不匹配 32 位 PPAPI 插件的问题,换成对应的浏览器成功解决。

Qt WebEngine 加载 Pepper Plugin

Qt WebEngine 如果设置了 WebEngineSettings::pluginsEnabledQWebEngineSettings::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开发

标签: Chromium, Pepper Plugin API, 插件, NPAPI, PPAPI, Chrome

已有 4 条评论

  1. j

    极好。

  2. 等风来

    按照你的方法制作了dll,但注册时应该是不成功。显示不支持插件,不知道要怎么解决。

    1. 在使用浏览器加载插件过程中,按照上面步骤及代码,总数出现 插件未加载 或 插件不支持 错误,最后发现是 64位浏览器不匹配 32 位 PPAPI 插件的问题,换成对应的浏览器成功解决。

  3. White

    按照你的方法,编译Debug版本是可行的,可是浏览器无法加载插件。。
    Release是直接编译都通不过,ppapi_cpp.lib一堆的无法解析的外部符号,我把所有的库都重新编译一遍也没用。。
    是为什么?

添加新评论