教程之二、第2部分:SDK构建系统和Chrome应用程序

原文链接:https://developer.chrome.com/native-client/devguide/tutorial/tutorial-part2

C ++教程:入门(第2部分)

概观

本教程介绍如何将完成的PNaCl Web应用程序从第1部分转换 为使用Native Client SDK构建系统和常用JavaScript文件。它还演示了一些使您的Web应用程序符合内容安全策略(CSP)的技术,这是Chrome应用程序所必需的。

使用Native Client SDK构建系统可以轻松地使用所有SDK工具链进行构建,并在Debug和Release配置之间切换。它还简化了项目的makefile,我们将在下一节中看到。最后,它添加了一些用于运行调试 应用程序的有用命令。

可以pepper_$(VERSION)/getting_started/part2在Native Client SDK下载的目录中找到此示例的完成代码 。

使用Native Client SDK构建系统

本节介绍如何使用SDK构建系统。为此,我们将在makefile中进行更改。因为part1和part2中的makefile是如此不同,所以从头开始更容易。这是新makefile的内容。以下部分将更详细地描述它。

简化Makefile

part1的makefile只支持一个工具链(PNaCl)和一个配置(Release)。它也只支持一个源文件。它相对简单,但如果我们想要添加对多个工具链,配置,源文件或构建步骤的支持,它将变得越来越复杂。SDK构建系统使用一组变量和宏来实现这一点,而不会显着增加makefile的复杂性。

这是新的makefile,支持三个工具链(PNaCl,Newlib NaCl,Glibc NaCl)和两个配置(Debug,Release)。

VALID_TOOLCHAINS := pnacl clang-newlib glibc

NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..)
include $(NACL_SDK_ROOT)/tools/common.mk

TARGET = part2
LIBS = ppapi_cpp ppapi

CFLAGS = -Wall
SOURCES = hello_tutorial.cc

# Build rules generated by macros from common.mk:

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

# The PNaCl workflow uses both an unstripped and finalized/stripped binary.
# On NaCl, only produce a stripped binary for Release configs (not Debug).
ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

$(eval $(call NMF_RULE,$(TARGET),))

选择有效的工具链,包括common.mk

makefile首先指定对此项目有效的工具链。Native Client SDK构建系统支持其示例和库的多工具链项目,但通常您在开始项目时选择一个工具链,而不会更改它。有关详细信息,请参阅Native Client概述的 工具链部分

在这个例子中,我们支持pnaclclang-newlib以及glibc 工具链。

VALID_TOOLCHAINS := pnacl clang-newlib glibc

接下来,为方便起见,我们指定要查找的位置NACL_SDK_ROOT。由于此示例位于pepper_$(VERSION)/getting_started/part2,因此SDK的根目录是两个目录。

NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../..)

在您自己的项目中,您可以在此处使用已安装SDK的绝对路径。您还可以通过设置NACL_SDK_ROOT 环境变量来覆盖此默认值。有关更多详细信息,请参阅本教程第1部分的步骤5

接下来,我们包含该文件tools/common.mk。此文件提供Native Client SDK构建系统的功能,包括用于编译和链接项目的新构建规则,我们将在下面使用。

include $(NACL_SDK_ROOT)/tools/common.mk

配置项目

包含之后tools/common.mk,我们通过指定项目名称,它使用的源和库来配置项目:

TARGET = part2
LIBS = ppapi_cpp ppapi

CFLAGS = -Wall
SOURCES = hello_tutorial.cc

这些变量名称不是必需的,也不是SDK构建系统使用的; 它们仅用于下述规则。按照惯例,所有SDK makefile都使用以下变量:

目标

要构建的项目的名称。此变量确定将生成的库或可执行文件的名称。在上面的例子中,我们调用目标part2,它将生成一个名为part2.pexePNaCl 的可执行文件 。对于NaCl工具链,可执行文件的文件名将为其体系结构提供后缀。例如,调用ARM可执行文件part2_arm.nexe

LIBS

此可执行文件需要链接的库列表。库搜索路径已设置为仅查看当前工具链和体系结构的目录。在这个例子中,我们链接ppapi_cpp 和ppapippapi_cpp需要使用Pepper C ++接口ppapi需要与浏览器通信。

CFLAGS

要传递给编译器的额外标志列表。在这个例子中,我们传递 -Wall,它打开所有警告。

LDFLAGS

要传递给链接器的其他标志的列表。此示例不需要任何特殊的链接器标志,因此省略此变量。

来源

要编译的C或C ++源列表,用空格分隔。如果您有很长的源列表,如果您将每个文件放在它自己的行上,并且\用作行继续符,则可能更容易阅读。这是一个例子:

SOURCES = foo.cc \
          bar.cc \
          baz.cc \
          quux.cc

构建宏

对于许多项目,不需要更改以下构建宏; 他们将使用我们上面定义的变量。

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

$(eval $(call NMF_RULE,$(TARGET),))

第一行定义了SOURCES使用以下标志编译每个源的规则CFLAGS

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

接下来的六行定义了将目标文件链接到一个或多个可执行文件的规则。当TOOLCHAINpnacl,仅存在一个生成的可执行:在上面的例子中,part2.pexe。当使用NaCl工具链时,将生成三个可执行文件,每个体系结构对应一个体系结构:在上面的示例中part2_arm.nexepart2_x86_32.nexe和 part2_x86_64.nexe

如果CONFIGRelease,每个可执行文件也被脱除调试信息,减少文件大小。否则,当TOOLCHAIN 是pnacl,工作流程涉及创建一个未经剥离的二进制文件以进行调试,然后完成它并剥离它以进行发布。

ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

最后,NMF规则生成一个NaCl清单文件(.nmf),它引用上一步中生成的每个可执行文件:

$(eval $(call NMF_RULE,$(TARGET),))

使index.html适用于Chrome应用

本节介绍了使part1与CSP兼容的HTML和JavaScript所需的更改。如果您要构建Chrome应用程序,则需要这样做,但如果您想在开放网络上使用PNaCl,则不需要这样做。

CSP规则

Chrome Apps CSP限制您执行以下操作:

  • 您无法在Chrome应用页面中使用内联脚本。限制禁止<script>块和事件处理程序(<button onclick="...">)。
  • 您无法在任何应用文件中引用任何外部资源(视频和音频资源除外)。您无法在iframe中嵌入外部资源。
  • 你不能使用像eval()和那样的字符串到JavaScript的方法new Function()

使index.html符合CSP标准

为了使我们的应用程序符合CSP,我们必须删除内联脚本。如上所述,我们不能使用内联<script>块或事件处理程序。这很容易 - 我们只会从脚本标记中引用一些新文件,并删除所有内联脚本:

<head>
  ...
  <script type="text/javascript" src="common.js"></script>
  <script type="text/javascript" src="example.js"></script>
</head>

common.js具有所有SDK示例使用的共享代码,稍后将在本文档中进行介绍。example.js是一个脚本,具有特定于此示例的代码。

我们还需要删除body标签上的内联事件处理程序:

<body onload="pageDidLoad()">
...

这个逻辑现在由common.js。处理。

使index.html支持不同的工具链和配置

最后,index.html对于CSP合规性而言,有一些更改是不必要的,但有助于使SDK示例更通用。

首先,我们 向body元素添加一些数据属性,以指定名称,支持的工具链,支持的配置和.nmf文件路径:

<body data-name="part2"
    data-tools="clang-newlib glibc pnacl"
    data-configs="Debug Release"
    data-path="{tc}/{config}">
...

common.js将读取这些数据属性,以允许您通过更改URL的查询字符串来加载具有不同工具链的相同示例。例如,您可以通过导航到加载此示例的glibc Debug版本 index.html?tc=glibc&config=Debug。例如../,路径URI 不适用于data-path参数或其对应的查询字符串。

接下来,我们删除embedHTML中描述的元素。这将common.js根据当前的工具链/配置组合自动添加:

<!--
Just as in part1, the <embed> element will be wrapped inside the <div>
element with the id "listener". In part1, the embed was specified in HTML,
here the common.js module creates a new <embed> element and adds it to the
<div> for us.
-->
<div id="listener"></div>

与common.js共享公共代码

common.js包含JavaScript代码,每个示例用于创建NaCl模块,处理来自该模块的消息以及其他常见任务,如显示模块加载状态和记录消息。解释所有 common.js内容超出了本文档的范围,但请查看该文件中的文档以获取更多信息。

加载页面并创建模块

既然我们已经添加<script>标签common.js,并example.js给 head元素,他们将被载入和文档的其余部分被解析之前执行。因此,在尝试创建embed元素并将其添加到页面之前,我们必须等待页面完成加载。

我们可以通过呼叫addEventListener和收听 DOMContentLoaded事件来做到这一点:

// Listen for the DOM content to be loaded. This event is fired when parsing of
// the page's document has finished.
document.addEventListener('DOMContentLoaded', function() {
  ...
});

在此函数中,我们解析URL查询字符串,并将其与数据属性进行比较:

// From https://developer.mozilla.org/en-US/docs/DOM/window.location
var searchVars = {};
if (window.location.search.length > 1) {
  var pairs = window.location.search.substr(1).split('&');
  for (var key_ix = 0; key_ix < pairs.length; key_ix++) {
    var keyValue = pairs[key_ix].split('=');
    searchVars[unescape(keyValue[0])] =
        keyValue.length > 1 ? unescape(keyValue[1]) : '';
  }
}

...

var toolchains = body.dataset.tools.split(' ');
var configs = body.dataset.configs.split(' ');

...

var tc = toolchains.indexOf(searchVars.tc) !== -1 ?
    searchVars.tc : toolchains[0];

// If the config value is included in the search vars, use that.
// Otherwise default to Release if it is valid, or the first value if
// Release is not valid.
if (configs.indexOf(searchVars.config) !== -1)
  var config = searchVars.config;
else if (configs.indexOf('Release') !== -1)
  var config = 'Release';
else
  var config = configs[0];

然后domContentLoaded调用,执行一些检查以查看浏览器是否支持Native Client,然后创建NaCl模块。

function domContentLoaded(name, tool, path, width, height, attrs) {
  updateStatus('Page loaded.');
  if (!browserSupportsNaCl(tool)) {
    updateStatus(
        'Browser does not support NaCl (' + tool + '), or NaCl is disabled');
  } else if (common.naclModule == null) {
    updateStatus('Creating embed: ' + tool);

    // We use a non-zero sized embed to give Chrome space to place the bad
    // plug-in graphic, if there is a problem.
    width = typeof width !== 'undefined' ? width : 200;
    height = typeof height !== 'undefined' ? height : 200;
    attachDefaultListeners();
    createNaClModule(name, tool, path, width, height, attrs);
  } 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('Waiting.');
  }
}

attachDefaultListeners在创建模块之前添加,以确保没有消息丢失。注意,window.attachListeners也称为; 这是common.js允许每个示例以不同方式配置自身的方式。如果一个例子定义了该attachListeners函数,它将被调用common.js

function attachDefaultListeners() {
  var listenerDiv = document.getElementById('listener');
  listenerDiv.addEventListener('load', moduleDidLoad, true);
  listenerDiv.addEventListener('message', handleMessage, true);
  listenerDiv.addEventListener('crash', handleCrash, true);
  if (typeof window.attachListeners !== 'undefined') {
    window.attachListeners();
  }
}

最后,createNaClModule实际上创建了embed,并将其作为元素的子项追加到id listener

function createNaClModule(name, tool, path, width, height, attrs) {
  var moduleEl = document.createElement('embed');
  moduleEl.setAttribute('name', 'nacl_module');
  moduleEl.setAttribute('id', 'nacl_module');
  moduleEl.setAttribute('width', width);
  moduleEl.setAttribute('height', height);
  moduleEl.setAttribute('path', path);
  moduleEl.setAttribute('src', path + '/' + name + '.nmf');

  ...

  var mimetype = mimeTypeForTool(tool);
  moduleEl.setAttribute('type', mimetype);

  var listenerDiv = document.getElementById('listener');
  listenerDiv.appendChild(moduleEl);
  ...
}

当模块完成加载时,它将调度一个load事件,并moduleDidLoad调用上面()注册的事件监听器函数。请注意,common.js允许每个示例定义一个 window.moduleDidLoad函数,该函数也将在此处调用。

function moduleDidLoad() {
  common.naclModule = document.getElementById('nacl_module');
  updateStatus('RUNNING');

  if (typeof window.moduleDidLoad !== 'undefined') {
    window.moduleDidLoad();
  }
}

example.js的特定于示例的行为

如上一节所述,common.js将在模块加载过程中调用某些函数。这个例子只需要响应两个: moduleDidLoadhandleMessage

// This function is called by common.js when the NaCl module is
// loaded.
function moduleDidLoad() {
  // Once we load, hide the plugin. In this example, we don't display anything
  // in the plugin, so it is fine to hide it.
  common.hideModule();

  // After the NaCl module has loaded, common.naclModule is a reference to the
  // NaCl module's <embed> element.
  //
  // postMessage sends a message to it.
  common.naclModule.postMessage('hello');
}

// This function is called by common.js when a message is received from the
// NaCl module.
function handleMessage(message) {
  var logEl = document.getElementById('log');
  logEl.textContent += message.data;
}

编译Native Client模块并再次运行该应用程序

  1. 通过make再次运行该命令来编译Native Client模块。

  2. 通过运行启动SDK Web服务器make server

  3. 通过http://localhost:5103/part2在Chrome中重新加载来重新运行该应用程序。

    Chrome加载Native Client模块后,您应该会看到从模块发送的消息。

CC-By 3.0许可下提供的内容

posted @ 2018-07-19 16:48  SunkingYang  阅读(224)  评论(0编辑  收藏  举报